Skip to content

Instantly share code, notes, and snippets.

@andrey-mikhailov
Last active September 12, 2022 13:57
Show Gist options
  • Save andrey-mikhailov/5448ccda70454d4f48a1a4cdea82c517 to your computer and use it in GitHub Desktop.
Save andrey-mikhailov/5448ccda70454d4f48a1a4cdea82c517 to your computer and use it in GitHub Desktop.
url_fixer.py
FROM apache/superset:1.2.0
COPY ./valamis/ /app/valamis/
USER root
# fix urls
RUN python /app/valamis/replace_url.py | tee /var/log/replace_url.log
USER superset
from valamis.url_fixer import UrlFixer
prefix: str = "analytics"
# keyword - replacement - useQuotes
replacementsEverywhere: list = [
("static", prefix), # js and css files
("api/v1", prefix), # superset APIs
("superset", prefix), # menu plus sign
("chart", prefix), # menu plus sign and menu Chart
("dashboard", prefix), # menu plus sign and menu Dashboard
("user", prefix), # menu Settings
("roles", prefix), # menu Settings
("logmodelview", prefix), # menu Settings
("annotationlayermodelview", prefix), # menu Settings
("csstemplatemodelview", prefix), # menu Settings
("dynamic-plugins", prefix), # menu Settings
("databaseview", prefix), # menu Data
("tablemodelview", prefix), # menu Data
("csvtodatabaseview", prefix), # menu Data
("exceltodatabaseview", prefix), # menu Data
("savedqueryview", prefix), # menu SQL Lab
("csstemplateasyncmodelview", prefix) # when creating a new dashboard
]
replacementsInPython: list = [
('label=__("Databases"),', f'label=__("Databases"), href="/{prefix}/databaseview/list/",'),
('label=__("Annotation Layers"),',
f'label=__("Annotation Layers"), href="/{prefix}/annotationlayermodelview/list/",'),
('label=__("Charts"),', f'label=__("Charts"), href="/{prefix}/chart/list/",'),
('label=__("Dashboards"),', f'label=__("Dashboards"), href="/{prefix}/dashboard/list/",'),
('label=__("Plugins"),', f'label=__("Plugins"), href="/{prefix}/dynamic-plugins/list/",'),
('label=__("CSS Templates"),', f'label=__("CSS Templates"), href="/{prefix}/csstemplatemodelview/list/",'),
('label=__("Row level security"),',
f'label=__("Row level security"), href="/{prefix}/rowlevelsecurityfiltersmodelview/list/",'),
('label=__("Action Log"),', f'label=__("Action Log"), href="/{prefix}/logmodelview/list/",')
]
replacementsInJavascript: list = [
("datasource", prefix),
("savedqueryviewapi", prefix),
("annotationmodelview", prefix),
("dashboardasync", prefix),
("sliceasync", prefix),
("tableschemaview", prefix)
]
simpleReplacementsInJavascript: list = [
('window.location.pathname.split("/")[3]', 'window.location.pathname.split("/")[4]'),
('${window.location.origin}/superset/', '${window.location.origin}/' + prefix + '/superset/')
]
# TODO: override links for these elements as well. By default the features are disabled
# label = __("Dashboard Emails"),
# label = __("Chart Email Schedules"),
# label = __("Alerts"),
# label = __("Alerts & Reports"),
# label = __("Access requests"),
BASE_DIR = "/app/superset/"
ASSET_DIR = f"{BASE_DIR}static/assets/"
TEMPLATES_DIR = f"{BASE_DIR}templates/"
urlFixer: UrlFixer = UrlFixer()
urlFixer.smart_replace(replacementsEverywhere, ASSET_DIR, ".js")
urlFixer.smart_replace(replacementsInJavascript, ASSET_DIR, ".js")
urlFixer.simple_replace(simpleReplacementsInJavascript, ASSET_DIR, ".js")
urlFixer.smart_replace(replacementsEverywhere, ASSET_DIR, ".json")
urlFixer.smart_replace(replacementsEverywhere, TEMPLATES_DIR, ".html")
urlFixer.smart_replace(replacementsEverywhere, BASE_DIR, ".py")
urlFixer.simple_replace(replacementsInPython, BASE_DIR, ".py")
import filecmp
import os
import unittest
import pathlib
import shutil
from valamis.url_fixer import UrlFixer
class TestUrlFixer(unittest.TestCase):
urlFixer: UrlFixer = UrlFixer
prefix: str = "my-superset"
search_dir = f"{pathlib.Path(__file__).parent.absolute()}/examples/"
# replace keywords in a link
def test_replace_keywords(self):
replacements = [("static", self.prefix)]
# test double quotes
result = self.urlFixer.combine_with_prefix(replacements, '<a href="/static/logo"/>')
self.assertEqual(result, '<a href="/my-superset/static/logo"/>')
# test backtick quote
result = self.urlFixer.combine_with_prefix(replacements, '<a href=`/static/logo`/>')
self.assertEqual(result, '<a href=`/my-superset/static/logo`/>')
result = self.urlFixer.combine_with_prefix(replacements, "<a href='/static/logo'/>")
self.assertEqual(result, "<a href='/my-superset/static/logo'/>")
# ignore keywords if they are in the middle of string
def test_ignore_keywords(self):
replacements = [("static", self.prefix)]
orig = 'UPLOAD_FOLDER = BASE_DIR + "/app/static/uploads/"'
result = self.urlFixer.combine_with_prefix(replacements, orig)
self.assertEqual(result, orig)
orig = "UPLOAD_FOLDER = BASE_DIR + '/app/static/uploads/'"
result = self.urlFixer.combine_with_prefix(replacements, orig)
self.assertEqual(result, orig)
def test_ignore_lines_with_expose_decorator(self):
replacements = [("static", self.prefix)]
orig = '@expose("/static/")'
self.assertEqual(
self.urlFixer.combine_with_prefix(replacements, orig),
orig
)
def test_find_files(self):
self.assertListEqual(
sorted(self.urlFixer.find_files(self.search_dir, ".txt")),
[f"{self.search_dir}1.txt", f"{self.search_dir}2.txt"]
)
def test_replace_in_files(self):
replacements = [("static", self.prefix)]
temp_dir = "/tmp/superset-test/"
if not os.path.exists(temp_dir):
os.mkdir(temp_dir)
shutil.copyfile(f"{self.search_dir}1.txt", f"{temp_dir}1.txt")
#self.urlFixer.combine(replacements, temp_dir, ".txt")
self.urlFixer.smart_replace(replacements, temp_dir, ".txt")
self.assertTrue(
filecmp.cmp(f"{temp_dir}1.txt", f"{self.search_dir}2.txt")
)
def test_replace_appbuilder_names(self):
replacements = [("{{appbuilder.get_url_for_logout}}", "/my-superset/logout/")]
orig = '<li><a href="{{appbuilder.get_url_for_logout}}"><span class="fa fa-fw fa-sign-out"></span>{{_("Logout")}}</a></li>'
expected = '<li><a href="/my-superset/logout/"><span class="fa fa-fw fa-sign-out"></span>{{_("Logout")}}</a></li>'
self.assertEqual(self.urlFixer.replace_keyword(replacements, orig), expected)
def test_redirect_url(self):
replacements = [("appbuilder.get_url_for_login", '"/my-superset"' + " + appbuilder.get_url_for_login")]
orig = 'return redirect(appbuilder.get_url_for_login)'
expected = 'return redirect("/my-superset" + appbuilder.get_url_for_login)'
self.assertEqual(self.urlFixer.replace_keyword(replacements, orig), expected)
if __name__ == '__main__':
unittest.main()
import glob
class UrlFixer:
# replace all string starting with a single or double quote and keywords
@staticmethod
def combine_with_prefix(replacements: list, data: str) -> str:
for orig, prefix in replacements:
# skip lines with the word @expose
newdata = ""
for line in data.splitlines(True):
if "@expose" not in line:
# replace all occurrences of the required string but skip lines with @expose word
line = line.replace(f"'/{orig}/", f"'/{prefix}/{orig}/")
line = line.replace(f"`/{orig}/", f"`/{prefix}/{orig}/")
line = line.replace(f'"/{orig}/', f'"/{prefix}/{orig}/')
newdata += line
data = newdata
return data
@staticmethod
def replace_keyword(replacements: list, data: str) -> str:
for orig, dest in replacements:
data = data.replace(orig, dest)
return data
@staticmethod
def find_files(path: str, ext: str) -> list:
if not ext.startswith('.'):
raise Exception("extension must be start wit a dot")
if not path.endswith('/'):
raise Exception("path must be ended with a slash")
return glob.glob(f'{path}**/*{ext}', recursive=True)
@staticmethod
def simple_replace(replacements: list, path: str, ext: str) -> None:
UrlFixer._run(replacements, path, ext, UrlFixer.replace_keyword)
@staticmethod
# Wrap the string with slashes and add double, single quoutes or
# tick to the beginning of the string for searching
def smart_replace(replacements: list, path: str, ext: str) -> None:
UrlFixer._run(replacements, path, ext, UrlFixer.combine_with_prefix)
@staticmethod
def _run(replacements: list, path: str, ext: str, func) -> None:
for filename in UrlFixer.find_files(path, ext):
fin = open(filename, "rt")
content = fin.read()
new_content = func(replacements, content)
fin.close()
if content == new_content:
continue
print(f'Writing new content to the file: {filename}')
fin = open(filename, "wt")
fin.write(new_content)
fin.close()
@andrey-mikhailov
Copy link
Author

I published these codes to github project https://github.com/andrey-mikhailov/superset-custom-path

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment