blob: 945207aed8b69fddc7fd71ef0a91ac79b45cee90 [file] [log] [blame]
<!doctype html>
<html lang="en" dir="ltr" class="docs-wrapper plugin-docs plugin-id-default docs-version-current docs-doc-page docs-doc-id-contributing/howtos" data-has-hydrated="false">
<head>
<meta charset="UTF-8">
<meta name="generator" content="Docusaurus v3.8.1">
<title data-rh="true">Development How-tos | Superset</title><meta data-rh="true" name="viewport" content="width=device-width,initial-scale=1"><meta data-rh="true" name="twitter:card" content="summary_large_image"><meta data-rh="true" property="og:url" content="https://superset.apache.org/docs/contributing/howtos"><meta data-rh="true" property="og:locale" content="en"><meta data-rh="true" name="docusaurus_locale" content="en"><meta data-rh="true" name="docsearch:language" content="en"><meta data-rh="true" name="docusaurus_version" content="current"><meta data-rh="true" name="docusaurus_tag" content="docs-default-current"><meta data-rh="true" name="docsearch:version" content="current"><meta data-rh="true" name="docsearch:docusaurus_tag" content="docs-default-current"><meta data-rh="true" property="og:title" content="Development How-tos | Superset"><meta data-rh="true" name="description" content="Contributing to Documentation"><meta data-rh="true" property="og:description" content="Contributing to Documentation"><link data-rh="true" rel="icon" href="/img/favicon.ico"><link data-rh="true" rel="canonical" href="https://superset.apache.org/docs/contributing/howtos"><link data-rh="true" rel="alternate" href="https://superset.apache.org/docs/contributing/howtos" hreflang="en"><link data-rh="true" rel="alternate" href="https://superset.apache.org/docs/contributing/howtos" hreflang="x-default"><link data-rh="true" rel="preconnect" href="https://WR5FASX5ED-dsn.algolia.net" crossorigin="anonymous"><script data-rh="true" type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Development How-tos","item":"https://superset.apache.org/docs/contributing/howtos"}]}</script><link rel="search" type="application/opensearchdescription+xml" title="Superset" href="/opensearch.xml">
<script src="https://widget.kapa.ai/kapa-widget.bundle.js" async data-website-id="c6a8a8b8-3127-48f9-97a7-51e9e10d20d0" data-project-name="Apache Superset" data-project-color="#FFFFFF" data-project-logo="https://images.seeklogo.com/logo-png/50/2/superset-icon-logo-png_seeklogo-500354.png" data-modal-override-open-id="ask-ai-input" data-modal-override-open-class="search-input" data-modal-disclaimer="This is a custom LLM for Apache Superset with access to all [documentation](superset.apache.org/docs/intro/), [GitHub Open Issues, PRs and READMEs](github.com/apache/superset).&amp;#10;&amp;#10;Companies deploy assistants like this ([built by kapa.ai](https://kapa.ai)) on docs via [website widget](https://docs.kapa.ai/integrations/website-widget) (Docker, Reddit), in [support forms](https://docs.kapa.ai/integrations/support-form-deflector) for ticket deflection (Monday.com, Mapbox), or as [Slack bots](https://docs.kapa.ai/integrations/slack-bot) with private sources." data-modal-example-questions="How do I install Superset?,How can I contribute to Superset?" data-button-text-color="rgb(81,166,197)" data-modal-header-bg-color="#ffffff" data-modal-title-color="rgb(81,166,197)" data-modal-title="Apache Superset AI" data-modal-disclaimer-text-color="#000000" data-consent-required="true" data-consent-screen-disclaimer="By clicking &quot;I agree, let&#39;s chat&quot;, you consent to the use of the AI assistant in accordance with kapa.ai&#39;s [Privacy Policy](https://www.kapa.ai/content/privacy-policy). This service uses reCAPTCHA, which requires your consent to Google&#39;s [Privacy Policy](https://policies.google.com/privacy) and [Terms of Service](https://policies.google.com/terms). By proceeding, you explicitly agree to both kapa.ai&#39;s and Google&#39;s privacy policies."></script><link rel="stylesheet" href="/assets/css/styles.f7e4fcdd.css">
<script src="/assets/js/runtime~main.6c7cf69f.js" defer="defer"></script>
<script src="/assets/js/main.a3befe85.js" defer="defer"></script>
</head>
<body class="navigation-with-keyboard">
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;"><defs>
<symbol id="theme-svg-external-link" viewBox="0 0 24 24"><path fill="currentColor" d="M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z"/></symbol>
</defs></svg>
<script>!function(){var t=function(){try{return new URLSearchParams(window.location.search).get("docusaurus-theme")}catch(t){}}()||function(){try{return window.localStorage.getItem("theme")}catch(t){}}();document.documentElement.setAttribute("data-theme",t||(window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light")),document.documentElement.setAttribute("data-theme-choice",t||"system")}(),function(){try{const c=new URLSearchParams(window.location.search).entries();for(var[t,e]of c)if(t.startsWith("docusaurus-data-")){var a=t.replace("docusaurus-data-","data-");document.documentElement.setAttribute(a,e)}}catch(t){}}()</script><div id="__docusaurus"><div role="region" aria-label="Skip to main content"><a class="skipToContent_fXgn" href="#__docusaurus_skipToContent_fallback">Skip to main content</a></div><nav aria-label="Main" class="theme-layout-navbar navbar navbar--fixed-top"><div class="navbar__inner"><div class="theme-layout-navbar-left navbar__items"><button aria-label="Toggle navigation bar" aria-expanded="false" class="navbar__toggle clean-btn" type="button"><svg width="30" height="30" viewBox="0 0 30 30" aria-hidden="true"><path stroke="currentColor" stroke-linecap="round" stroke-miterlimit="10" stroke-width="2" d="M4 7h22M4 15h22M4 23h22"></path></svg></button><a class="navbar__brand" href="/"><div class="navbar__logo"><img src="/img/superset-logo-horiz.svg" alt="Superset Logo" class="themedComponent_mlkZ themedComponent--light_NVdE"><img src="/img/superset-logo-horiz-dark.svg" alt="Superset Logo" class="themedComponent_mlkZ themedComponent--dark_xIcU"></div></a><div class="navbar__item dropdown dropdown--hoverable"><a href="#" aria-haspopup="true" aria-expanded="false" role="button" class="navbar__link">Documentation</a><ul class="dropdown__menu"><li><a aria-current="page" class="dropdown__link dropdown__link--active" href="/docs/intro">Getting Started</a></li><li><a aria-current="page" class="dropdown__link dropdown__link--active" href="/docs/faq">FAQ</a></li></ul></div><div class="navbar__item dropdown dropdown--hoverable"><a class="navbar__link" aria-haspopup="true" aria-expanded="false" role="button" href="/community">Community Resources</a><ul class="dropdown__menu"><li><a class="dropdown__link" href="/community">Resources</a></li><li><a href="https://github.com/apache/superset" target="_blank" rel="noopener noreferrer" class="dropdown__link">GitHub<svg width="12" height="12" aria-hidden="true" class="iconExternalLink_nPIU"><use href="#theme-svg-external-link"></use></svg></a></li><li><a href="http://bit.ly/join-superset-slack" target="_blank" rel="noopener noreferrer" class="dropdown__link">Slack<svg width="12" height="12" aria-hidden="true" class="iconExternalLink_nPIU"><use href="#theme-svg-external-link"></use></svg></a></li><li><a href="https://lists.apache.org/list.html?dev@superset.apache.org" target="_blank" rel="noopener noreferrer" class="dropdown__link">Mailing List<svg width="12" height="12" aria-hidden="true" class="iconExternalLink_nPIU"><use href="#theme-svg-external-link"></use></svg></a></li><li><a href="https://stackoverflow.com/questions/tagged/apache-superset" target="_blank" rel="noopener noreferrer" class="dropdown__link">Stack Overflow<svg width="12" height="12" aria-hidden="true" class="iconExternalLink_nPIU"><use href="#theme-svg-external-link"></use></svg></a></li></ul></div></div><div class="theme-layout-navbar-right navbar__items navbar__items--right"><a class="navbar__item navbar__link default-button-theme get-started-button" href="/docs/intro">Get Started</a><a href="https://github.com/apache/superset" target="_blank" rel="noopener noreferrer" class="navbar__item navbar__link github-button"></a><div class="toggle_vylO colorModeToggle_DEke"><button class="clean-btn toggleButton_gllP toggleButtonDisabled_aARS" type="button" disabled="" title="system mode" aria-label="Switch between dark and light mode (currently system mode)"><svg viewBox="0 0 24 24" width="24" height="24" aria-hidden="true" class="toggleIcon_g3eP lightToggleIcon_pyhR"><path fill="currentColor" d="M12,9c1.65,0,3,1.35,3,3s-1.35,3-3,3s-3-1.35-3-3S10.35,9,12,9 M12,7c-2.76,0-5,2.24-5,5s2.24,5,5,5s5-2.24,5-5 S14.76,7,12,7L12,7z M2,13l2,0c0.55,0,1-0.45,1-1s-0.45-1-1-1l-2,0c-0.55,0-1,0.45-1,1S1.45,13,2,13z M20,13l2,0c0.55,0,1-0.45,1-1 s-0.45-1-1-1l-2,0c-0.55,0-1,0.45-1,1S19.45,13,20,13z M11,2v2c0,0.55,0.45,1,1,1s1-0.45,1-1V2c0-0.55-0.45-1-1-1S11,1.45,11,2z M11,20v2c0,0.55,0.45,1,1,1s1-0.45,1-1v-2c0-0.55-0.45-1-1-1C11.45,19,11,19.45,11,20z M5.99,4.58c-0.39-0.39-1.03-0.39-1.41,0 c-0.39,0.39-0.39,1.03,0,1.41l1.06,1.06c0.39,0.39,1.03,0.39,1.41,0s0.39-1.03,0-1.41L5.99,4.58z M18.36,16.95 c-0.39-0.39-1.03-0.39-1.41,0c-0.39,0.39-0.39,1.03,0,1.41l1.06,1.06c0.39,0.39,1.03,0.39,1.41,0c0.39-0.39,0.39-1.03,0-1.41 L18.36,16.95z M19.42,5.99c0.39-0.39,0.39-1.03,0-1.41c-0.39-0.39-1.03-0.39-1.41,0l-1.06,1.06c-0.39,0.39-0.39,1.03,0,1.41 s1.03,0.39,1.41,0L19.42,5.99z M7.05,18.36c0.39-0.39,0.39-1.03,0-1.41c-0.39-0.39-1.03-0.39-1.41,0l-1.06,1.06 c-0.39,0.39-0.39,1.03,0,1.41s1.03,0.39,1.41,0L7.05,18.36z"></path></svg><svg viewBox="0 0 24 24" width="24" height="24" aria-hidden="true" class="toggleIcon_g3eP darkToggleIcon_wfgR"><path fill="currentColor" d="M9.37,5.51C9.19,6.15,9.1,6.82,9.1,7.5c0,4.08,3.32,7.4,7.4,7.4c0.68,0,1.35-0.09,1.99-0.27C17.45,17.19,14.93,19,12,19 c-3.86,0-7-3.14-7-7C5,9.07,6.81,6.55,9.37,5.51z M12,3c-4.97,0-9,4.03-9,9s4.03,9,9,9s9-4.03,9-9c0-0.46-0.04-0.92-0.1-1.36 c-0.98,1.37-2.58,2.26-4.4,2.26c-2.98,0-5.4-2.42-5.4-5.4c0-1.81,0.89-3.42,2.26-4.4C12.92,3.04,12.46,3,12,3L12,3z"></path></svg><svg viewBox="0 0 24 24" width="24" height="24" aria-hidden="true" class="toggleIcon_g3eP systemToggleIcon_QzmC"><path fill="currentColor" d="m12 21c4.971 0 9-4.029 9-9s-4.029-9-9-9-9 4.029-9 9 4.029 9 9 9zm4.95-13.95c1.313 1.313 2.05 3.093 2.05 4.95s-0.738 3.637-2.05 4.95c-1.313 1.313-3.093 2.05-4.95 2.05v-14c1.857 0 3.637 0.737 4.95 2.05z"></path></svg></button></div><div class="navbarSearchContainer_Bca1"><button type="button" class="DocSearch DocSearch-Button" aria-label="Search (Command+K)"><span class="DocSearch-Button-Container"><svg width="20" height="20" class="DocSearch-Search-Icon" viewBox="0 0 20 20" aria-hidden="true"><path d="M14.386 14.386l4.0877 4.0877-4.0877-4.0877c-2.9418 2.9419-7.7115 2.9419-10.6533 0-2.9419-2.9418-2.9419-7.7115 0-10.6533 2.9418-2.9419 7.7115-2.9419 10.6533 0 2.9419 2.9418 2.9419 7.7115 0 10.6533z" stroke="currentColor" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"></path></svg><span class="DocSearch-Button-Placeholder">Search</span></span><span class="DocSearch-Button-Keys"></span></button></div></div></div><div role="presentation" class="navbar-sidebar__backdrop"></div></nav><div id="__docusaurus_skipToContent_fallback" class="theme-layout-main main-wrapper mainWrapper_z2l0"><div class="docsWrapper_hBAB"><button aria-label="Scroll back to top" class="clean-btn theme-back-to-top-button backToTopButton_sjWU" type="button"></button><div class="docRoot_UBD9"><aside class="theme-doc-sidebar-container docSidebarContainer_YfHR"><div class="sidebarViewport_aRkj"><div class="sidebar_njMd"><nav aria-label="Docs sidebar" class="menu thin-scrollbar menu_SIkG"><ul class="theme-doc-sidebar-menu menu__list"><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-1 menu__list-item"><a class="menu__link" href="/docs/intro">Introduction</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-1 menu__list-item"><a class="menu__link" href="/docs/quickstart">Quickstart</a></li><li class="theme-doc-sidebar-item-category theme-doc-sidebar-item-category-level-1 menu__list-item menu__list-item--collapsed"><div class="menu__list-item-collapsible"><a class="menu__link menu__link--sublist menu__link--sublist-caret" role="button" aria-expanded="false" href="/docs/installation/architecture">Installation</a></div></li><li class="theme-doc-sidebar-item-category theme-doc-sidebar-item-category-level-1 menu__list-item menu__list-item--collapsed"><div class="menu__list-item-collapsible"><a class="menu__link menu__link--sublist menu__link--sublist-caret" role="button" aria-expanded="false" href="/docs/configuration/configuring-superset">Configuration</a></div></li><li class="theme-doc-sidebar-item-category theme-doc-sidebar-item-category-level-1 menu__list-item menu__list-item--collapsed"><div class="menu__list-item-collapsible"><a class="menu__link menu__link--sublist menu__link--sublist-caret" role="button" aria-expanded="false" href="/docs/using-superset/creating-your-first-dashboard">Using Superset</a></div></li><li class="theme-doc-sidebar-item-category theme-doc-sidebar-item-category-level-1 menu__list-item"><div class="menu__list-item-collapsible"><a class="menu__link menu__link--sublist menu__link--sublist-caret menu__link--active" role="button" aria-expanded="true" href="/docs/contributing/">Contributing</a></div><ul class="menu__list"><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-2 menu__list-item"><a class="menu__link" tabindex="0" href="/docs/contributing/">Contributing to Superset</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-2 menu__list-item"><a class="menu__link" tabindex="0" href="/docs/contributing/guidelines">Guidelines</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-2 menu__list-item"><a class="menu__link" tabindex="0" href="/docs/contributing/development">Setting up a Development Environment</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-2 menu__list-item"><a class="menu__link menu__link--active" aria-current="page" tabindex="0" href="/docs/contributing/howtos">Development How-tos</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-2 menu__list-item"><a class="menu__link" tabindex="0" href="/docs/contributing/resources">Resources</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-2 menu__list-item"><a class="menu__link" tabindex="0" href="/docs/contributing/misc">Miscellaneous</a></li></ul></li><li class="theme-doc-sidebar-item-category theme-doc-sidebar-item-category-level-1 menu__list-item menu__list-item--collapsed"><div class="menu__list-item-collapsible"><a class="menu__link menu__link--sublist menu__link--sublist-caret" role="button" aria-expanded="false" href="/docs/security/">Security</a></div></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-1 menu__list-item"><a class="menu__link" href="/docs/faq">FAQ</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-1 menu__list-item"><a class="menu__link" href="/docs/api">API</a></li></ul></nav><button type="button" title="Collapse sidebar" aria-label="Collapse sidebar" class="button button--secondary button--outline collapseSidebarButton_PEFL"><svg width="20" height="20" aria-hidden="true" class="collapseSidebarButtonIcon_kv0_"><g fill="#7a7a7a"><path d="M9.992 10.023c0 .2-.062.399-.172.547l-4.996 7.492a.982.982 0 01-.828.454H1c-.55 0-1-.453-1-1 0-.2.059-.403.168-.551l4.629-6.942L.168 3.078A.939.939 0 010 2.528c0-.548.45-.997 1-.997h2.996c.352 0 .649.18.828.45L9.82 9.472c.11.148.172.347.172.55zm0 0"></path><path d="M19.98 10.023c0 .2-.058.399-.168.547l-4.996 7.492a.987.987 0 01-.828.454h-3c-.547 0-.996-.453-.996-1 0-.2.059-.403.168-.551l4.625-6.942-4.625-6.945a.939.939 0 01-.168-.55 1 1 0 01.996-.997h3c.348 0 .649.18.828.45l4.996 7.492c.11.148.168.347.168.55zm0 0"></path></g></svg></button></div></div></aside><main class="docMainContainer_TBSr"><div class="container padding-top--md padding-bottom--lg"><div class="row"><div class="col docItemCol_VOVn"><div class="theme-doc-version-banner alert alert--warning margin-bottom--md" role="alert"><div>This is unreleased documentation for <!-- -->Superset<!-- --> <b>Next</b> version.</div><div class="margin-top--md">For up-to-date documentation, see the <b><a href="/docs/6.0.0/contributing/howtos">latest version</a></b> (<!-- -->6.0.0<!-- -->).</div></div><div class="docItemContainer_Djhp"><article><nav class="theme-doc-breadcrumbs breadcrumbsContainer_Z_bl" aria-label="Breadcrumbs"><ul class="breadcrumbs"><li class="breadcrumbs__item"><a aria-label="Home page" class="breadcrumbs__link" href="/"><svg viewBox="0 0 24 24" class="breadcrumbHomeIcon_YNFT"><path d="M10 19v-5h4v5c0 .55.45 1 1 1h3c.55 0 1-.45 1-1v-7h1.7c.46 0 .68-.57.33-.87L12.67 3.6c-.38-.34-.96-.34-1.34 0l-8.36 7.53c-.34.3-.13.87.33.87H5v7c0 .55.45 1 1 1h3c.55 0 1-.45 1-1z" fill="currentColor"></path></svg></a></li><li class="breadcrumbs__item"><span class="breadcrumbs__link">Contributing</span></li><li class="breadcrumbs__item breadcrumbs__item--active"><span class="breadcrumbs__link">Development How-tos</span></li></ul></nav><span class="versionBadge_QOso">Version:<!-- --> <a class="ant-dropdown-trigger versionSelector_oOvN">Next<!-- --> <span role="img" aria-label="down" class="anticon anticon-down"><svg viewBox="64 64 896 896" focusable="false" data-icon="down" width="1em" height="1em" fill="currentColor" aria-hidden="true"><path d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"></path></svg></span></a></span><div class="tocCollapsible_ETCw theme-doc-toc-mobile tocMobile_ITEo"><button type="button" class="clean-btn tocCollapsibleButton_TO0P">On this page</button></div><div class="theme-doc-markdown markdown"><header><h1>Development How-tos</h1></header>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="contributing-to-documentation">Contributing to Documentation<a href="#contributing-to-documentation" class="hash-link" aria-label="Direct link to Contributing to Documentation" title="Direct link to Contributing to Documentation"></a></h2>
<p>The latest documentation and tutorial are available at <a href="https://superset.apache.org/" target="_blank" rel="noopener noreferrer">https://superset.apache.org/</a>.</p>
<p>The documentation site is built using <a href="https://docusaurus.io/" target="_blank" rel="noopener noreferrer">Docusaurus 3</a>, a modern
static website generator, the source for which resides in <code>./docs</code>.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="local-development">Local Development<a href="#local-development" class="hash-link" aria-label="Direct link to Local Development" title="Direct link to Local Development"></a></h3>
<p>To set up a local development environment with hot reloading for the documentation site:</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#9CDCFE;--prism-background-color:#1E1E1E"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="color:#9CDCFE;background-color:#1E1E1E"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#9CDCFE"><span class="token plain">cd docs</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain">yarn install # Installs NPM dependencies</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain">yarn start # Starts development server at http://localhost:3000</span><br></span></code></pre></div></div>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="build">Build<a href="#build" class="hash-link" aria-label="Direct link to Build" title="Direct link to Build"></a></h3>
<p>To create and serve a production build of the documentation site:</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#9CDCFE;--prism-background-color:#1E1E1E"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="color:#9CDCFE;background-color:#1E1E1E"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#9CDCFE"><span class="token plain">yarn build</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain">yarn serve</span><br></span></code></pre></div></div>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="deployment">Deployment<a href="#deployment" class="hash-link" aria-label="Direct link to Deployment" title="Direct link to Deployment"></a></h3>
<p>Commits to <code>master</code> trigger a rebuild and redeploy of the documentation site. Submit pull requests that modify the documentation with the <code>docs:</code> prefix.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="creating-visualization-plugins">Creating Visualization Plugins<a href="#creating-visualization-plugins" class="hash-link" aria-label="Direct link to Creating Visualization Plugins" title="Direct link to Creating Visualization Plugins"></a></h2>
<p>Visualizations in Superset are implemented in JavaScript or TypeScript. Superset
comes preinstalled with several visualizations types (hereafter &quot;viz plugins&quot;) that
can be found under the <code>superset-frontend/plugins</code> directory. Viz plugins are added to
the application in the <code>superset-frontend/src/visualizations/presets/MainPreset.js</code>.
The Superset project is always happy to review proposals for new high quality viz
plugins. However, for highly custom viz types it is recommended to maintain a fork
of Superset, and add the custom built viz plugins by hand.</p>
<p><strong>Note:</strong> Additional community-generated resources about creating and deploying custom visualization plugins can be found on the <a href="https://github.com/apache/superset/wiki/Community-Resource-Library#creating-custom-data-visualizations" target="_blank" rel="noopener noreferrer">Superset Wiki</a></p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="prerequisites">Prerequisites<a href="#prerequisites" class="hash-link" aria-label="Direct link to Prerequisites" title="Direct link to Prerequisites"></a></h3>
<p>In order to create a new viz plugin, you need the following:</p>
<ul>
<li>Run MacOS or Linux (Windows is not officially supported, but may work)</li>
<li>Node.js 16</li>
<li>npm 7 or 8</li>
</ul>
<p>A general familiarity with <a href="https://reactjs.org/" target="_blank" rel="noopener noreferrer">React</a> and the npm/Node system is
also recommended.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="creating-a-simple-hello-world-viz-plugin">Creating a simple Hello World viz plugin<a href="#creating-a-simple-hello-world-viz-plugin" class="hash-link" aria-label="Direct link to Creating a simple Hello World viz plugin" title="Direct link to Creating a simple Hello World viz plugin"></a></h3>
<p>To get started, you need the Superset Yeoman Generator. It is recommended to use the
version of the template that ships with the version of Superset you are using. This
can be installed by doing the following:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#9CDCFE;--prism-background-color:#1E1E1E"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#9CDCFE;background-color:#1E1E1E"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#9CDCFE"><span class="token plain">npm i -g yo</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain">cd superset-frontend/packages/generator-superset</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain">npm i</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain">npm link</span><br></span></code></pre></div></div>
<p>After this you can proceed to create your viz plugin. Create a new directory for your
viz plugin with the prefix <code>superset-plugin-chart</code> and run the Yeoman generator:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#9CDCFE;--prism-background-color:#1E1E1E"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#9CDCFE;background-color:#1E1E1E"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#9CDCFE"><span class="token plain">mkdir /tmp/superset-plugin-chart-hello-world</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain">cd /tmp/superset-plugin-chart-hello-world</span><br></span></code></pre></div></div>
<p>Initialize the viz plugin:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#9CDCFE;--prism-background-color:#1E1E1E"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#9CDCFE;background-color:#1E1E1E"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#9CDCFE"><span class="token plain">yo @superset-ui/superset</span><br></span></code></pre></div></div>
<p>After that the generator will ask a few questions (the defaults should be fine):</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#9CDCFE;--prism-background-color:#1E1E1E"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#9CDCFE;background-color:#1E1E1E"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#9CDCFE"><span class="token plain">$ yo @superset-ui/superset</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"> _-----_ ╭──────────────────────────╮</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"> | | │ Welcome to the │</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"> |--(o)--| │ generator-superset │</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"> `---------´ │ generator! │</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"> ( _´U`_ ) ╰──────────────────────────╯</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"> /___A___\ /</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"> | ~ |</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"> __&#x27;.___.&#x27;__</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"> ´ ` |° ´ Y `</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain">? Package name: superset-plugin-chart-hello-world</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain">? Description: Hello World</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain">? What type of chart would you like? Time-series chart</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"> create package.json</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"> create .gitignore</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"> create babel.config.js</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"> create jest.config.js</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"> create README.md</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"> create tsconfig.json</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"> create src/index.ts</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"> create src/plugin/buildQuery.ts</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"> create src/plugin/controlPanel.ts</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"> create src/plugin/index.ts</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"> create src/plugin/transformProps.ts</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"> create src/types.ts</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"> create src/SupersetPluginChartHelloWorld.tsx</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"> create test/index.test.ts</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"> create test/__mocks__/mockExportString.js</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"> create test/plugin/buildQuery.test.ts</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"> create test/plugin/transformProps.test.ts</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"> create types/external.d.ts</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"> create src/images/thumbnail.png</span><br></span></code></pre></div></div>
<p>To build the viz plugin, run the following commands:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#9CDCFE;--prism-background-color:#1E1E1E"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#9CDCFE;background-color:#1E1E1E"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#9CDCFE"><span class="token plain">npm i --force</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain">npm run build</span><br></span></code></pre></div></div>
<p>Alternatively, to run the viz plugin in development mode (=rebuilding whenever changes
are made), start the dev server with the following command:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#9CDCFE;--prism-background-color:#1E1E1E"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#9CDCFE;background-color:#1E1E1E"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#9CDCFE"><span class="token plain">npm run dev</span><br></span></code></pre></div></div>
<p>To add the package to Superset, go to the <code>superset-frontend</code> subdirectory in your
Superset source folder run</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#9CDCFE;--prism-background-color:#1E1E1E"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#9CDCFE;background-color:#1E1E1E"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#9CDCFE"><span class="token plain">npm i -S /tmp/superset-plugin-chart-hello-world</span><br></span></code></pre></div></div>
<p>If you publish your package to npm, you can naturally install directly from there, too.
After this edit the <code>superset-frontend/src/visualizations/presets/MainPreset.js</code>
and make the following changes:</p>
<div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#9CDCFE;--prism-background-color:#1E1E1E"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#9CDCFE;background-color:#1E1E1E"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#9CDCFE"><span class="token keyword module" style="color:rgb(86, 156, 214)">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:rgb(212, 212, 212)">{</span><span class="token imports"> </span><span class="token imports maybe-class-name">SupersetPluginChartHelloWorld</span><span class="token imports"> </span><span class="token imports punctuation" style="color:rgb(212, 212, 212)">}</span><span class="token plain"> </span><span class="token keyword module" style="color:rgb(86, 156, 214)">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(206, 145, 120)">&#x27;superset-plugin-chart-hello-world&#x27;</span><span class="token punctuation" style="color:rgb(212, 212, 212)">;</span><br></span></code></pre></div></div>
<p>to import the viz plugin and later add the following to the array that&#x27;s passed to the
<code>plugins</code> property:</p>
<div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#9CDCFE;--prism-background-color:#1E1E1E"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#9CDCFE;background-color:#1E1E1E"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#9CDCFE"><span class="token keyword" style="color:rgb(86, 156, 214)">new</span><span class="token plain"> </span><span class="token class-name" style="color:rgb(78, 201, 176)">SupersetPluginChartHelloWorld</span><span class="token punctuation" style="color:rgb(212, 212, 212)">(</span><span class="token punctuation" style="color:rgb(212, 212, 212)">)</span><span class="token punctuation" style="color:rgb(212, 212, 212)">.</span><span class="token method function property-access" style="color:rgb(220, 220, 170)">configure</span><span class="token punctuation" style="color:rgb(212, 212, 212)">(</span><span class="token punctuation" style="color:rgb(212, 212, 212)">{</span><span class="token plain"> </span><span class="token literal-property property">key</span><span class="token operator" style="color:rgb(212, 212, 212)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(206, 145, 120)">&#x27;ext-hello-world&#x27;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(212, 212, 212)">}</span><span class="token punctuation" style="color:rgb(212, 212, 212)">)</span><span class="token punctuation" style="color:rgb(212, 212, 212)">,</span><br></span></code></pre></div></div>
<p>After that the viz plugin should show up when you run Superset, e.g. the development
server:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#9CDCFE;--prism-background-color:#1E1E1E"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#9CDCFE;background-color:#1E1E1E"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#9CDCFE"><span class="token plain">npm run dev-server</span><br></span></code></pre></div></div>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="testing">Testing<a href="#testing" class="hash-link" aria-label="Direct link to Testing" title="Direct link to Testing"></a></h2>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="python-testing">Python Testing<a href="#python-testing" class="hash-link" aria-label="Direct link to Python Testing" title="Direct link to Python Testing"></a></h3>
<p><code>pytest</code>, backend by docker-compose is how we recommend running tests locally.</p>
<p>For a more complex test matrix (against different database backends, python versions, ...) you
can rely on our GitHub Actions by simply opening a draft pull request.</p>
<p>Note that the test environment uses a temporary directory for defining the
SQLite databases which will be cleared each time before the group of test
commands are invoked.</p>
<p>There is also a utility script included in the Superset codebase to run python integration tests. The <a href="https://github.com/apache/superset/tree/master/scripts/tests" target="_blank" rel="noopener noreferrer">readme can be
found here</a></p>
<p>To run all integration tests for example, run this script from the root directory:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#9CDCFE;--prism-background-color:#1E1E1E"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#9CDCFE;background-color:#1E1E1E"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#9CDCFE"><span class="token plain">scripts/tests/run.sh</span><br></span></code></pre></div></div>
<p>You can run unit tests found in &#x27;./tests/unit_tests&#x27; for example with pytest. It is a simple way to run an isolated test that doesn&#x27;t need any database setup</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#9CDCFE;--prism-background-color:#1E1E1E"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#9CDCFE;background-color:#1E1E1E"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#9CDCFE"><span class="token plain">pytest ./link_to_test.py</span><br></span></code></pre></div></div>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="testing-with-local-presto-connections">Testing with local Presto connections<a href="#testing-with-local-presto-connections" class="hash-link" aria-label="Direct link to Testing with local Presto connections" title="Direct link to Testing with local Presto connections"></a></h4>
<p>If you happen to change db engine spec for Presto/Trino, you can run a local Presto cluster with Docker:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#9CDCFE;--prism-background-color:#1E1E1E"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#9CDCFE;background-color:#1E1E1E"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#9CDCFE"><span class="token plain">docker run -p 15433:15433 starburstdata/presto:350-e.6</span><br></span></code></pre></div></div>
<p>Then update <code>SUPERSET__SQLALCHEMY_EXAMPLES_URI</code> to point to local Presto cluster:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#9CDCFE;--prism-background-color:#1E1E1E"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#9CDCFE;background-color:#1E1E1E"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#9CDCFE"><span class="token plain">export SUPERSET__SQLALCHEMY_EXAMPLES_URI=presto://localhost:15433/memory/default</span><br></span></code></pre></div></div>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="frontend-testing">Frontend Testing<a href="#frontend-testing" class="hash-link" aria-label="Direct link to Frontend Testing" title="Direct link to Frontend Testing"></a></h3>
<p>We use <a href="https://jestjs.io/" target="_blank" rel="noopener noreferrer">Jest</a> and <a href="https://airbnb.io/enzyme/" target="_blank" rel="noopener noreferrer">Enzyme</a> to test TypeScript/JavaScript. Tests can be run with:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#9CDCFE;--prism-background-color:#1E1E1E"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#9CDCFE;background-color:#1E1E1E"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#9CDCFE"><span class="token plain">cd superset-frontend</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain">npm run test</span><br></span></code></pre></div></div>
<p>To run a single test file:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#9CDCFE;--prism-background-color:#1E1E1E"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#9CDCFE;background-color:#1E1E1E"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#9CDCFE"><span class="token plain">npm run test -- path/to/file.js</span><br></span></code></pre></div></div>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="e2e-integration-testing">E2E Integration Testing<a href="#e2e-integration-testing" class="hash-link" aria-label="Direct link to E2E Integration Testing" title="Direct link to E2E Integration Testing"></a></h3>
<p>For E2E testing, we recommend that you use a <code>docker compose</code> backend</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#9CDCFE;--prism-background-color:#1E1E1E"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#9CDCFE;background-color:#1E1E1E"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#9CDCFE"><span class="token plain">CYPRESS_CONFIG=true docker compose up --build</span><br></span></code></pre></div></div>
<p><code>docker compose</code> will get to work and expose a Cypress-ready Superset app.
This app uses a different database schema (<code>superset_cypress</code>) to keep it isolated from
your other dev environmen(s)t, a specific set of examples, and a set of configurations that
aligns with the expectations within the end-to-end tests. Also note that it&#x27;s served on a
different port than the default port for the backend (<code>8088</code>).</p>
<p>Now in another terminal, let&#x27;s get ready to execute some Cypress commands. First, tell cypress
to connect to the Cypress-ready Superset backend.</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#9CDCFE;--prism-background-color:#1E1E1E"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#9CDCFE;background-color:#1E1E1E"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#9CDCFE"><span class="token plain">CYPRESS_BASE_URL=http://localhost:8081</span><br></span></code></pre></div></div>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#9CDCFE;--prism-background-color:#1E1E1E"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#9CDCFE;background-color:#1E1E1E"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#9CDCFE"><span class="token plain"># superset-frontend/cypress-base is the base folder for everything Cypress-related</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"># It&#x27;s essentially its own npm app, with its own dependencies, configurations and utilities</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain">cd superset-frontend/cypress-base</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain">npm install</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"># use interactive mode to run tests, while keeping memory usage contained</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"># this will fire up an interactive Cypress UI</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"># as you alter the code, the tests will re-run automatically, and you can visualize each</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"># and every step for debugging purposes</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain">npx cypress open --config numTestsKeptInMemory=5</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"># to run the test suite on the command line using chrome (same as CI)</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain">npm run cypress-run-chrome</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"># run tests from a specific file</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain">npm run cypress-run-chrome -- --spec cypress/e2e/explore/link.test.ts</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"># run specific file with video capture</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain">npm run cypress-run-chrome -- --spec cypress/e2e/dashboard/index.test.js --config video=true</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"># to open the cypress ui</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain">npm run cypress-debug</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain" style="display:inline-block"></span><br></span></code></pre></div></div>
<p>See <a href="https://github.com/apache/superset/blob/master/superset-frontend/cypress_build.sh" target="_blank" rel="noopener noreferrer"><code>superset-frontend/cypress_build.sh</code></a>.</p>
<p>As an alternative you can use docker compose environment for testing:</p>
<p>Make sure you have added below line to your /etc/hosts file:
<code>127.0.0.1 db</code></p>
<p>If you already have launched Docker environment please use the following command to assure a fresh database instance:
<code>docker compose down -v</code></p>
<p>Launch environment:</p>
<p><code>CYPRESS_CONFIG=true docker compose up</code></p>
<p>It will serve backend and frontend on port 8088.</p>
<p>Run Cypress tests:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#9CDCFE;--prism-background-color:#1E1E1E"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#9CDCFE;background-color:#1E1E1E"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#9CDCFE"><span class="token plain">cd cypress-base</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain">npm install</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain">npm run cypress open</span><br></span></code></pre></div></div>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="debugging-server-app">Debugging Server App<a href="#debugging-server-app" class="hash-link" aria-label="Direct link to Debugging Server App" title="Direct link to Debugging Server App"></a></h3>
<p>Follow these instructions to debug the Flask app running inside a docker container.</p>
<p>First add the following to the ./docker-compose.yaml file</p>
<div class="language-diff codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#9CDCFE;--prism-background-color:#1E1E1E"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-diff codeBlock_bY9V thin-scrollbar" style="color:#9CDCFE;background-color:#1E1E1E"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#9CDCFE"><span class="token plain">superset:</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"> env_file: docker/.env</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"> image: *superset-image</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"> container_name: superset_app</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"> command: [&quot;/app/docker/docker-bootstrap.sh&quot;, &quot;app&quot;]</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"> restart: unless-stopped</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain">+ cap_add:</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain">+ - SYS_PTRACE</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"> ports:</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"> - 8088:8088</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain">+ - 5678:5678</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"> user: &quot;root&quot;</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"> depends_on: *superset-depends-on</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"> volumes: *superset-volumes</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"> environment:</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"> CYPRESS_CONFIG: &quot;${CYPRESS_CONFIG}&quot;</span><br></span></code></pre></div></div>
<p>Start Superset as usual</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#9CDCFE;--prism-background-color:#1E1E1E"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#9CDCFE;background-color:#1E1E1E"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#9CDCFE"><span class="token plain">docker compose up</span><br></span></code></pre></div></div>
<p>Install the required libraries and packages to the docker container</p>
<p>Enter the superset_app container</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#9CDCFE;--prism-background-color:#1E1E1E"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#9CDCFE;background-color:#1E1E1E"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#9CDCFE"><span class="token plain">docker exec -it superset_app /bin/bash</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain">root@39ce8cf9d6ab:/app#</span><br></span></code></pre></div></div>
<p>Run the following commands inside the container</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#9CDCFE;--prism-background-color:#1E1E1E"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#9CDCFE;background-color:#1E1E1E"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#9CDCFE"><span class="token plain">apt update</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain">apt install -y gdb</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain">apt install -y net-tools</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain">pip install debugpy</span><br></span></code></pre></div></div>
<p>Find the PID for the Flask process. Make sure to use the first PID. The Flask app will re-spawn a sub-process every time you change any of the python code. So it&#x27;s important to use the first PID.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#9CDCFE;--prism-background-color:#1E1E1E"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#9CDCFE;background-color:#1E1E1E"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#9CDCFE"><span class="token plain">ps -ef</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain">UID PID PPID C STIME TTY TIME CMD</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain">root 1 0 0 14:09 ? 00:00:00 bash /app/docker/docker-bootstrap.sh app</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain">root 6 1 4 14:09 ? 00:00:04 /usr/local/bin/python /usr/bin/flask run -p 8088 --with-threads --reload --debugger --host=0.0.0.0</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain">root 10 6 7 14:09 ? 00:00:07 /usr/local/bin/python /usr/bin/flask run -p 8088 --with-threads --reload --debugger --host=0.0.0.0</span><br></span></code></pre></div></div>
<p>Inject debugpy into the running Flask process. In this case PID 6.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#9CDCFE;--prism-background-color:#1E1E1E"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#9CDCFE;background-color:#1E1E1E"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#9CDCFE"><span class="token plain">python3 -m debugpy --listen 0.0.0.0:5678 --pid 6</span><br></span></code></pre></div></div>
<p>Verify that debugpy is listening on port 5678</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#9CDCFE;--prism-background-color:#1E1E1E"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#9CDCFE;background-color:#1E1E1E"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#9CDCFE"><span class="token plain">netstat -tunap</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain">Active Internet connections (servers and established)</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain">Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain">tcp 0 0 0.0.0.0:5678 0.0.0.0:* LISTEN 462/python</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain">tcp 0 0 0.0.0.0:8088 0.0.0.0:* LISTEN 6/python</span><br></span></code></pre></div></div>
<p>You are now ready to attach a debugger to the process. Using VSCode you can configure a launch configuration file .vscode/launch.json like so.</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#9CDCFE;--prism-background-color:#1E1E1E"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#9CDCFE;background-color:#1E1E1E"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#9CDCFE"><span class="token punctuation" style="color:rgb(212, 212, 212)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"> </span><span class="token property">&quot;version&quot;</span><span class="token operator" style="color:rgb(212, 212, 212)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(206, 145, 120)">&quot;0.2.0&quot;</span><span class="token punctuation" style="color:rgb(212, 212, 212)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"> </span><span class="token property">&quot;configurations&quot;</span><span class="token operator" style="color:rgb(212, 212, 212)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(212, 212, 212)">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(212, 212, 212)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"> </span><span class="token property">&quot;name&quot;</span><span class="token operator" style="color:rgb(212, 212, 212)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(206, 145, 120)">&quot;Attach to Superset App in Docker Container&quot;</span><span class="token punctuation" style="color:rgb(212, 212, 212)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"> </span><span class="token property">&quot;type&quot;</span><span class="token operator" style="color:rgb(212, 212, 212)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(206, 145, 120)">&quot;python&quot;</span><span class="token punctuation" style="color:rgb(212, 212, 212)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"> </span><span class="token property">&quot;request&quot;</span><span class="token operator" style="color:rgb(212, 212, 212)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(206, 145, 120)">&quot;attach&quot;</span><span class="token punctuation" style="color:rgb(212, 212, 212)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"> </span><span class="token property">&quot;connect&quot;</span><span class="token operator" style="color:rgb(212, 212, 212)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(212, 212, 212)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"> </span><span class="token property">&quot;host&quot;</span><span class="token operator" style="color:rgb(212, 212, 212)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(206, 145, 120)">&quot;127.0.0.1&quot;</span><span class="token punctuation" style="color:rgb(212, 212, 212)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"> </span><span class="token property">&quot;port&quot;</span><span class="token operator" style="color:rgb(212, 212, 212)">:</span><span class="token plain"> </span><span class="token number" style="color:rgb(181, 206, 168)">5678</span><span class="token plain"></span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(212, 212, 212)">}</span><span class="token punctuation" style="color:rgb(212, 212, 212)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"> </span><span class="token property">&quot;pathMappings&quot;</span><span class="token operator" style="color:rgb(212, 212, 212)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(212, 212, 212)">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(212, 212, 212)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"> </span><span class="token property">&quot;localRoot&quot;</span><span class="token operator" style="color:rgb(212, 212, 212)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(206, 145, 120)">&quot;${workspaceFolder}&quot;</span><span class="token punctuation" style="color:rgb(212, 212, 212)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"> </span><span class="token property">&quot;remoteRoot&quot;</span><span class="token operator" style="color:rgb(212, 212, 212)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(206, 145, 120)">&quot;/app&quot;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(212, 212, 212)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(212, 212, 212)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(212, 212, 212)">}</span><span class="token punctuation" style="color:rgb(212, 212, 212)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(212, 212, 212)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"></span><span class="token punctuation" style="color:rgb(212, 212, 212)">}</span><br></span></code></pre></div></div>
<p>VSCode will not stop on breakpoints right away. We&#x27;ve attached to PID 6 however it does not yet know of any sub-processes. In order to &quot;wakeup&quot; the debugger you need to modify a python file. This will trigger Flask to reload the code and create a new sub-process. This new sub-process will be detected by VSCode and breakpoints will be activated.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="debugging-server-app-in-kubernetes-environment">Debugging Server App in Kubernetes Environment<a href="#debugging-server-app-in-kubernetes-environment" class="hash-link" aria-label="Direct link to Debugging Server App in Kubernetes Environment" title="Direct link to Debugging Server App in Kubernetes Environment"></a></h3>
<p>To debug Flask running in POD inside a kubernetes cluster, you&#x27;ll need to make sure the pod runs as root and is granted the <code>SYS_TRACE</code> capability. These settings should not be used in production environments.</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#9CDCFE;--prism-background-color:#1E1E1E"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#9CDCFE;background-color:#1E1E1E"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#9CDCFE"><span class="token plain"> </span><span class="token key atrule">securityContext</span><span class="token punctuation" style="color:rgb(212, 212, 212)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"> </span><span class="token key atrule">capabilities</span><span class="token punctuation" style="color:rgb(212, 212, 212)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"> </span><span class="token key atrule">add</span><span class="token punctuation" style="color:rgb(212, 212, 212)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(212, 212, 212)">[</span><span class="token string" style="color:rgb(206, 145, 120)">&quot;SYS_PTRACE&quot;</span><span class="token punctuation" style="color:rgb(212, 212, 212)">]</span><br></span></code></pre></div></div>
<p>See <a href="https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-capabilities-for-a-container" target="_blank" rel="noopener noreferrer">set capabilities for a container</a> for more details.</p>
<p>Once the pod is running as root and has the <code>SYS_PTRACE</code> capability it will be able to debug the Flask app.</p>
<p>You can follow the same instructions as in <code>docker compose</code>. Enter the pod and install the required library and packages: gdb, netstat and debugpy.</p>
<p>Often in a Kubernetes environment nodes are not addressable from outside the cluster. VSCode will thus be unable to remotely connect to port 5678 on a Kubernetes node. In order to do this you need to create a tunnel that port forwards 5678 to your local machine.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#9CDCFE;--prism-background-color:#1E1E1E"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#9CDCFE;background-color:#1E1E1E"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#9CDCFE"><span class="token plain">kubectl port-forward pod/superset-&lt;some random id&gt; 5678:5678</span><br></span></code></pre></div></div>
<p>You can now launch your VSCode debugger with the same config as above. VSCode will connect to to 127.0.0.1:5678 which is forwarded by kubectl to your remote kubernetes POD.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="storybook">Storybook<a href="#storybook" class="hash-link" aria-label="Direct link to Storybook" title="Direct link to Storybook"></a></h3>
<p>Superset includes a <a href="https://storybook.js.org/" target="_blank" rel="noopener noreferrer">Storybook</a> to preview the layout/styling of various Superset components, and variations thereof. To open and view the Storybook:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#9CDCFE;--prism-background-color:#1E1E1E"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#9CDCFE;background-color:#1E1E1E"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#9CDCFE"><span class="token plain">cd superset-frontend</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain">npm run storybook</span><br></span></code></pre></div></div>
<p>When contributing new React components to Superset, please try to add a Story alongside the component&#x27;s <code>jsx/tsx</code> file.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="contributing-translations">Contributing Translations<a href="#contributing-translations" class="hash-link" aria-label="Direct link to Contributing Translations" title="Direct link to Contributing Translations"></a></h2>
<p>We use <a href="https://python-babel.github.io/flask-babel/" target="_blank" rel="noopener noreferrer">Flask-Babel</a> to translate Superset.
In Python files, we use the following
<a href="https://python-babel.github.io/flask-babel/#using-translations" target="_blank" rel="noopener noreferrer">translation functions</a>
from <code>Flask-Babel</code>:</p>
<ul>
<li><code>gettext</code> and <code>lazy_gettext</code> (usually aliased to <code>_</code>): for translating singular
strings.</li>
<li><code>ngettext</code>: for translating strings that might become plural.</li>
</ul>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#9CDCFE;--prism-background-color:#1E1E1E"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#9CDCFE;background-color:#1E1E1E"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#9CDCFE"><span class="token keyword" style="color:rgb(86, 156, 214)">from</span><span class="token plain"> flask_babel </span><span class="token keyword" style="color:rgb(86, 156, 214)">import</span><span class="token plain"> lazy_gettext </span><span class="token keyword" style="color:rgb(86, 156, 214)">as</span><span class="token plain"> _</span><br></span></code></pre></div></div>
<p>then wrap the translatable strings with it, e.g. <code>_(&#x27;Translate me&#x27;)</code>.
During extraction, string literals passed to <code>_</code> will be added to the
generated <code>.po</code> file for each language for later translation.</p>
<p>At runtime, the <code>_</code> function will return the translation of the given
string for the current language, or the given string itself
if no translation is available.</p>
<p>In TypeScript/JavaScript, the technique is similar:
we import <code>t</code> (simple translation), <code>tn</code> (translation containing a number).</p>
<div class="language-javascript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#9CDCFE;--prism-background-color:#1E1E1E"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-javascript codeBlock_bY9V thin-scrollbar" style="color:#9CDCFE;background-color:#1E1E1E"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#9CDCFE"><span class="token keyword module" style="color:rgb(86, 156, 214)">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:rgb(212, 212, 212)">{</span><span class="token imports"> t</span><span class="token imports punctuation" style="color:rgb(212, 212, 212)">,</span><span class="token imports"> tn </span><span class="token imports punctuation" style="color:rgb(212, 212, 212)">}</span><span class="token plain"> </span><span class="token keyword module" style="color:rgb(86, 156, 214)">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(206, 145, 120)">&quot;@superset-ui/translation&quot;</span><span class="token punctuation" style="color:rgb(212, 212, 212)">;</span><br></span></code></pre></div></div>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="enabling-language-selection">Enabling language selection<a href="#enabling-language-selection" class="hash-link" aria-label="Direct link to Enabling language selection" title="Direct link to Enabling language selection"></a></h3>
<p>Add the <code>LANGUAGES</code> variable to your <code>superset_config.py</code>. Having more than one
option inside will add a language selection dropdown to the UI on the right side
of the navigation bar.</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#9CDCFE;--prism-background-color:#1E1E1E"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#9CDCFE;background-color:#1E1E1E"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#9CDCFE"><span class="token plain">LANGUAGES </span><span class="token operator" style="color:rgb(212, 212, 212)">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(212, 212, 212)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"> </span><span class="token string" style="color:rgb(206, 145, 120)">&#x27;en&#x27;</span><span class="token punctuation" style="color:rgb(212, 212, 212)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(212, 212, 212)">{</span><span class="token string" style="color:rgb(206, 145, 120)">&#x27;flag&#x27;</span><span class="token punctuation" style="color:rgb(212, 212, 212)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(206, 145, 120)">&#x27;us&#x27;</span><span class="token punctuation" style="color:rgb(212, 212, 212)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(206, 145, 120)">&#x27;name&#x27;</span><span class="token punctuation" style="color:rgb(212, 212, 212)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(206, 145, 120)">&#x27;English&#x27;</span><span class="token punctuation" style="color:rgb(212, 212, 212)">}</span><span class="token punctuation" style="color:rgb(212, 212, 212)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"> </span><span class="token string" style="color:rgb(206, 145, 120)">&#x27;fr&#x27;</span><span class="token punctuation" style="color:rgb(212, 212, 212)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(212, 212, 212)">{</span><span class="token string" style="color:rgb(206, 145, 120)">&#x27;flag&#x27;</span><span class="token punctuation" style="color:rgb(212, 212, 212)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(206, 145, 120)">&#x27;fr&#x27;</span><span class="token punctuation" style="color:rgb(212, 212, 212)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(206, 145, 120)">&#x27;name&#x27;</span><span class="token punctuation" style="color:rgb(212, 212, 212)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(206, 145, 120)">&#x27;French&#x27;</span><span class="token punctuation" style="color:rgb(212, 212, 212)">}</span><span class="token punctuation" style="color:rgb(212, 212, 212)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"> </span><span class="token string" style="color:rgb(206, 145, 120)">&#x27;zh&#x27;</span><span class="token punctuation" style="color:rgb(212, 212, 212)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(212, 212, 212)">{</span><span class="token string" style="color:rgb(206, 145, 120)">&#x27;flag&#x27;</span><span class="token punctuation" style="color:rgb(212, 212, 212)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(206, 145, 120)">&#x27;cn&#x27;</span><span class="token punctuation" style="color:rgb(212, 212, 212)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(206, 145, 120)">&#x27;name&#x27;</span><span class="token punctuation" style="color:rgb(212, 212, 212)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(206, 145, 120)">&#x27;Chinese&#x27;</span><span class="token punctuation" style="color:rgb(212, 212, 212)">}</span><span class="token punctuation" style="color:rgb(212, 212, 212)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"></span><span class="token punctuation" style="color:rgb(212, 212, 212)">}</span><br></span></code></pre></div></div>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="creating-a-new-language-dictionary">Creating a new language dictionary<a href="#creating-a-new-language-dictionary" class="hash-link" aria-label="Direct link to Creating a new language dictionary" title="Direct link to Creating a new language dictionary"></a></h3>
<p>First check if the language code for your target language already exists. Check if the
<a href="https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes" target="_blank" rel="noopener noreferrer">two letter ISO 639-1 code</a>
for your target language already exists in the <code>superset/translations</code> directory:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#9CDCFE;--prism-background-color:#1E1E1E"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#9CDCFE;background-color:#1E1E1E"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#9CDCFE"><span class="token plain">ls superset/translations | grep -E &quot;^[a-z]{2}\/&quot;</span><br></span></code></pre></div></div>
<p>If your language already has a preexisting translation, skip to the next section</p>
<p>The following languages are already supported by Flask AppBuilder, and will make it
easier to translate the application to your target language:
<a href="https://flask-appbuilder.readthedocs.io/en/latest/i18n.html" target="_blank" rel="noopener noreferrer">Flask AppBuilder i18n documentation</a></p>
<p>To create a dictionary for a new language, first make sure the necessary dependencies are installed:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#9CDCFE;--prism-background-color:#1E1E1E"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#9CDCFE;background-color:#1E1E1E"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#9CDCFE"><span class="token plain">pip install -r superset/translations/requirements.txt</span><br></span></code></pre></div></div>
<p>Then run the following, where <code>LANGUAGE_CODE</code> is replaced with the language code for your target
language:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#9CDCFE;--prism-background-color:#1E1E1E"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#9CDCFE;background-color:#1E1E1E"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#9CDCFE"><span class="token plain">pybabel init -i superset/translations/messages.pot -d superset/translations -l LANGUAGE_CODE</span><br></span></code></pre></div></div>
<p>For instance, to add a translation for Finnish (language code <code>fi</code>), run the following:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#9CDCFE;--prism-background-color:#1E1E1E"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#9CDCFE;background-color:#1E1E1E"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#9CDCFE"><span class="token plain">pybabel init -i superset/translations/messages.pot -d superset/translations -l fi</span><br></span></code></pre></div></div>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="extracting-new-strings-for-translation">Extracting new strings for translation<a href="#extracting-new-strings-for-translation" class="hash-link" aria-label="Direct link to Extracting new strings for translation" title="Direct link to Extracting new strings for translation"></a></h3>
<p>Periodically, when working on translations, we need to extract the strings from both the
backend and the frontend to compile a list of all strings to be translated. It doesn&#x27;t
happen automatically and is a required step to gather the strings and get them into the
<code>.po</code> files where they can be translated, so that they can then be compiled.</p>
<p>This script does just that:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#9CDCFE;--prism-background-color:#1E1E1E"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#9CDCFE;background-color:#1E1E1E"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#9CDCFE"><span class="token plain">./scripts/translations/babel_update.sh</span><br></span></code></pre></div></div>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="updating-language-files">Updating language files<a href="#updating-language-files" class="hash-link" aria-label="Direct link to Updating language files" title="Direct link to Updating language files"></a></h3>
<p>Run the following command to update the language files with the new extracted strings.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#9CDCFE;--prism-background-color:#1E1E1E"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#9CDCFE;background-color:#1E1E1E"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#9CDCFE"><span class="token plain"> pybabel update -i superset/translations/messages.pot -d superset/translations --ignore-obsolete</span><br></span></code></pre></div></div>
<p>You can then translate the strings gathered in files located under
<code>superset/translation</code>, where there&#x27;s one folder per language. You can use <a href="https://poedit.net/features" target="_blank" rel="noopener noreferrer">Poedit</a>
to translate the <code>po</code> file more conveniently.
Here is <a href="https://web.archive.org/web/20220517065036/https://wiki.lxde.org/en/Translate_*.po_files_with_Poedit" target="_blank" rel="noopener noreferrer">a tutorial</a>.</p>
<p>To perform the translation on MacOS, you can install <code>poedit</code> via Homebrew:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#9CDCFE;--prism-background-color:#1E1E1E"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#9CDCFE;background-color:#1E1E1E"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#9CDCFE"><span class="token plain">brew install poedit</span><br></span></code></pre></div></div>
<p>After this, just start the <code>poedit</code> application and open the <code>messages.po</code> file. In the
case of the Finnish translation, this would be <code>superset/translations/fi/LC_MESSAGES/messages.po</code>.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="applying-translations">Applying translations<a href="#applying-translations" class="hash-link" aria-label="Direct link to Applying translations" title="Direct link to Applying translations"></a></h3>
<p>To make the translations available on the frontend, we need to convert the PO file into
a collection of JSON files. To convert all PO files to formatted JSON files you can use
the <code>build-translation</code> script</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#9CDCFE;--prism-background-color:#1E1E1E"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#9CDCFE;background-color:#1E1E1E"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#9CDCFE"><span class="token plain"># Install dependencies if you haven&#x27;t already</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain">cd superset-frontend/ &amp;&amp; npm ci</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"># Compile translations for the frontend</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain">npm run build-translation</span><br></span></code></pre></div></div>
<p>Finally, for the translations to take effect we need to compile translation catalogs into
binary MO files for the backend using <code>pybabel</code>.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#9CDCFE;--prism-background-color:#1E1E1E"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#9CDCFE;background-color:#1E1E1E"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#9CDCFE"><span class="token plain"># inside the project root</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain">pybabel compile -d superset/translations</span><br></span></code></pre></div></div>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="linting">Linting<a href="#linting" class="hash-link" aria-label="Direct link to Linting" title="Direct link to Linting"></a></h2>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="python">Python<a href="#python" class="hash-link" aria-label="Direct link to Python" title="Direct link to Python"></a></h3>
<p>We use <a href="https://github.com/astral-sh/ruff" target="_blank" rel="noopener noreferrer">ruff</a> for linting which can be invoked via:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#9CDCFE;--prism-background-color:#1E1E1E"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#9CDCFE;background-color:#1E1E1E"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#9CDCFE"><span class="token plain"># auto-reformat using ruff</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain">ruff format</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"># lint check with ruff</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain">ruff check</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"># lint fix with ruff</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain">ruff check --fix</span><br></span></code></pre></div></div>
<p>Ruff configuration is located in our
(pyproject.toml)[<a href="https://github.com/apache/superset/blob/master/pyproject.toml" target="_blank" rel="noopener noreferrer">https://github.com/apache/superset/blob/master/pyproject.toml</a>]<!-- --> file</p>
<p>All this is configured to run in pre-commit hooks, which we encourage you to setup
with <code>pre-commit install</code></p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="typescript">TypeScript<a href="#typescript" class="hash-link" aria-label="Direct link to TypeScript" title="Direct link to TypeScript"></a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#9CDCFE;--prism-background-color:#1E1E1E"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#9CDCFE;background-color:#1E1E1E"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#9CDCFE"><span class="token plain">cd superset-frontend</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain">npm ci</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"># run eslint checks</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain">npm run eslint -- .</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"># run tsc (typescript) checks</span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain">npm run type</span><br></span></code></pre></div></div>
<p>If using the eslint extension with vscode, put the following in your workspace <code>settings.json</code> file:</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#9CDCFE;--prism-background-color:#1E1E1E"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#9CDCFE;background-color:#1E1E1E"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#9CDCFE"><span class="token property">&quot;eslint.workingDirectories&quot;</span><span class="token operator" style="color:rgb(212, 212, 212)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(212, 212, 212)">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"> </span><span class="token string" style="color:rgb(206, 145, 120)">&quot;superset-frontend&quot;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#9CDCFE"><span class="token plain"></span><span class="token punctuation" style="color:rgb(212, 212, 212)">]</span><br></span></code></pre></div></div>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="github-ephemeral-environments">GitHub Ephemeral Environments<a href="#github-ephemeral-environments" class="hash-link" aria-label="Direct link to GitHub Ephemeral Environments" title="Direct link to GitHub Ephemeral Environments"></a></h2>
<p>On any given pull request on GitHub, it&#x27;s possible to create a temporary environment/deployment
by simply adding the label <code>testenv-up</code> to the PR. Once you add the <code>testenv-up</code> label, a
GitHub Action will be triggered that will:</p>
<ul>
<li>build a docker image</li>
<li>deploy it in EC2 (sponsored by the folks at <a href="https://preset.io" target="_blank" rel="noopener noreferrer">Preset</a>)</li>
<li>write a comment on the PR with a link to the ephemeral environment</li>
</ul>
<p>For more advanced use cases, it&#x27;s possible to set a feature flag on the PR body, which will
take effect on the ephemeral environment. For example, if you want to set the <code>TAGGING_SYSTEM</code>
feature flag to <code>true</code>, you can add the following line to the PR body/description:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#9CDCFE;--prism-background-color:#1E1E1E"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#9CDCFE;background-color:#1E1E1E"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#9CDCFE"><span class="token plain">FEATURE_TAGGING_SYSTEM=true</span><br></span></code></pre></div></div>
<p>Simarly, it&#x27;s possible to disable feature flags with:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#9CDCFE;--prism-background-color:#1E1E1E"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#9CDCFE;background-color:#1E1E1E"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#9CDCFE"><span class="token plain">FEATURE_TAGGING_SYSTEM=false</span><br></span></code></pre></div></div></div><footer class="theme-doc-footer docusaurus-mt-lg"><div class="row margin-top--sm theme-doc-footer-edit-meta-row"><div class="col"><a href="https://github.com/apache/superset/edit/master/docs/docs/contributing/howtos.mdx" target="_blank" rel="noopener noreferrer" class="theme-edit-this-page"><svg fill="currentColor" height="20" width="20" viewBox="0 0 40 40" class="iconEdit_Z9Sw" aria-hidden="true"><g><path d="m34.5 11.7l-3 3.1-6.3-6.3 3.1-3q0.5-0.5 1.2-0.5t1.1 0.5l3.9 3.9q0.5 0.4 0.5 1.1t-0.5 1.2z m-29.5 17.1l18.4-18.5 6.3 6.3-18.4 18.4h-6.3v-6.2z"></path></g></svg>Edit this page</a></div><div class="col lastUpdated_JAkA"><span class="theme-last-updated">Last updated<!-- --> on <b><time datetime="2025-09-02T13:38:03.000Z" itemprop="dateModified">Sep 2, 2025</time></b> by <b>dependabot[bot]</b></span></div></div></footer></article><nav class="docusaurus-mt-lg pagination-nav" aria-label="Docs pages"><a class="pagination-nav__link pagination-nav__link--prev" href="/docs/contributing/development"><div class="pagination-nav__sublabel">Previous</div><div class="pagination-nav__label">Setting up a Development Environment</div></a><a class="pagination-nav__link pagination-nav__link--next" href="/docs/contributing/resources"><div class="pagination-nav__sublabel">Next</div><div class="pagination-nav__label">Resources</div></a></nav></div></div><div class="col col--3"><div class="tableOfContents_bqdL thin-scrollbar theme-doc-toc-desktop"><ul class="table-of-contents table-of-contents__left-border"><li><a href="#contributing-to-documentation" class="table-of-contents__link toc-highlight">Contributing to Documentation</a><ul><li><a href="#local-development" class="table-of-contents__link toc-highlight">Local Development</a></li><li><a href="#build" class="table-of-contents__link toc-highlight">Build</a></li><li><a href="#deployment" class="table-of-contents__link toc-highlight">Deployment</a></li></ul></li><li><a href="#creating-visualization-plugins" class="table-of-contents__link toc-highlight">Creating Visualization Plugins</a><ul><li><a href="#prerequisites" class="table-of-contents__link toc-highlight">Prerequisites</a></li><li><a href="#creating-a-simple-hello-world-viz-plugin" class="table-of-contents__link toc-highlight">Creating a simple Hello World viz plugin</a></li></ul></li><li><a href="#testing" class="table-of-contents__link toc-highlight">Testing</a><ul><li><a href="#python-testing" class="table-of-contents__link toc-highlight">Python Testing</a></li><li><a href="#frontend-testing" class="table-of-contents__link toc-highlight">Frontend Testing</a></li><li><a href="#e2e-integration-testing" class="table-of-contents__link toc-highlight">E2E Integration Testing</a></li><li><a href="#debugging-server-app" class="table-of-contents__link toc-highlight">Debugging Server App</a></li><li><a href="#debugging-server-app-in-kubernetes-environment" class="table-of-contents__link toc-highlight">Debugging Server App in Kubernetes Environment</a></li><li><a href="#storybook" class="table-of-contents__link toc-highlight">Storybook</a></li></ul></li><li><a href="#contributing-translations" class="table-of-contents__link toc-highlight">Contributing Translations</a><ul><li><a href="#enabling-language-selection" class="table-of-contents__link toc-highlight">Enabling language selection</a></li><li><a href="#creating-a-new-language-dictionary" class="table-of-contents__link toc-highlight">Creating a new language dictionary</a></li><li><a href="#extracting-new-strings-for-translation" class="table-of-contents__link toc-highlight">Extracting new strings for translation</a></li><li><a href="#updating-language-files" class="table-of-contents__link toc-highlight">Updating language files</a></li><li><a href="#applying-translations" class="table-of-contents__link toc-highlight">Applying translations</a></li></ul></li><li><a href="#linting" class="table-of-contents__link toc-highlight">Linting</a><ul><li><a href="#python" class="table-of-contents__link toc-highlight">Python</a></li><li><a href="#typescript" class="table-of-contents__link toc-highlight">TypeScript</a></li></ul></li><li><a href="#github-ephemeral-environments" class="table-of-contents__link toc-highlight">GitHub Ephemeral Environments</a></li></ul></div></div></div></div></main></div></div></div><footer class="theme-layout-footer footer"><div class="container container-fluid"><div class="footer__bottom text--center"><div class="footer__copyright">
<div class="footer__applitools">
We use &nbsp;<a href="https://applitools.com/" target="_blank" rel="nofollow"><img src="/img/applitools.png" title="Applitools"></a>
</div>
<p>Copyright © 2024,
The <a href="https://www.apache.org/" target="_blank" rel="noreferrer">Apache Software Foundation</a>,
Licensed under the Apache <a href="https://apache.org/licenses/LICENSE-2.0" target="_blank" rel="noreferrer">License</a>.</p>
<p><small>Apache Superset, Apache, Superset, the Superset logo, and the Apache feather logo are either registered trademarks or trademarks of The Apache Software Foundation. All other products or name brands are trademarks of their respective holders, including The Apache Software Foundation.
<a href="https://www.apache.org/" target="_blank">Apache Software Foundation</a> resources</small></p>
<img class="footer__divider" src="/img/community/line.png" alt="Divider">
<p>
<small>
<a href="/docs/security/" target="_blank" rel="noreferrer">Security</a>&nbsp;|&nbsp;
<a href="https://www.apache.org/foundation/sponsorship.html" target="_blank" rel="noreferrer">Donate</a>&nbsp;|&nbsp;
<a href="https://www.apache.org/foundation/thanks.html" target="_blank" rel="noreferrer">Thanks</a>&nbsp;|&nbsp;
<a href="https://apache.org/events/current-event" target="_blank" rel="noreferrer">Events</a>&nbsp;|&nbsp;
<a href="https://apache.org/licenses/" target="_blank" rel="noreferrer">License</a>&nbsp;|&nbsp;
<a href="https://privacy.apache.org/policies/privacy-policy-public.html" target="_blank" rel="noreferrer">Privacy</a>
</small>
</p>
<!-- telemetry/analytics pixel: -->
<img referrerpolicy="no-referrer-when-downgrade" src="https://static.scarf.sh/a.png?x-pxid=39ae6855-95fc-4566-86e5-360d542b0a68">
</div></div></div></footer></div>
</body>
</html>