Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Can superset fromat currency dynamically according to the user's setting #18938

Closed
dorachen123 opened this issue Feb 25, 2022 · 6 comments
Closed
Labels
enhancement:request Enhancement request submitted by anyone from the community i18n Namespace | Anything related to localization

Comments

@dorachen123
Copy link

Is your feature request related to a problem? Please describe.
When I create a report on superset and share to different customers using different currencies, except customers from US could see money being format correctly, other can not. Even though we can add more money format to superset. But it doesn't change the fact that one report could only use one kind of currency format which is frustrated.

Describe the solution you'd like
What we need is if superset could dynamically apply currency format to report's columns according to login user's setting. So different user could see one report in different currency format.

@zhaoyongjie zhaoyongjie added enhancement:request Enhancement request submitted by anyone from the community i18n Namespace | Anything related to localization labels Feb 25, 2022
@zhaoyongjie
Copy link
Member

zhaoyongjie commented Feb 25, 2022

@dorachen123 This is a good request and reminds me of MSTR's experience. Unfortunately, Superset doesn't have these settings right now.

BTW, You can try to customize d3 locale in number-format.

@stale
Copy link

stale bot commented Apr 29, 2022

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. For admin, please label this issue .pinned to prevent stale bot from closing the issue.

@stale stale bot added the inactive Inactive for >= 30 days label Apr 29, 2022
@ypankovych
Copy link

ypankovych commented Jun 21, 2022

We actually managed to do that with a bunch of tweaks & hacks.

First of all, you'll need a custom /static view to catch the static that's being accessed.

def custom_static(self, filename):  # vendored from flask and modified to support our static files
    # Ensure get_send_file_max_age is called in all cases.
    # Here, we ensure get_send_file_max_age is called for Blueprints.
    cache_timeout = self.get_send_file_max_age(filename)
    with contextlib.suppress(NotFound):
        # here you mutate static files
        mutated, render, mutated_filename = get_mutated_static(self.static_folder, filename)
        response = send_from_directory(
            self.static_folder, mutated_filename, cache_timeout=0 if mutated else cache_timeout
        )
        if mutated:
            os.remove(os.path.join(self.static_folder, mutated_filename))
        return response
    raise NotFound

Then you set it as current static views in your flask_app_mutator function:

app.view_functions["static"] = MethodType(custom_static, app)  # hack

Here's the mutated_static:

def get_mutated_static(static_dir, filename):
    file_path = os.path.join(static_dir, filename)
    for mutator_spec in MUTATED_STATIC:
        if static_dir in mutator_spec.static_dirs and mutator_spec.predicate(file_path):
            file_content = mutator_spec.mutator(file_path)
            break
    else:
        return False, False, filename
    _, suffix = os.path.splitext(filename)
    with tempfile.NamedTemporaryFile(dir=static_dir, mode="w", delete=False, suffix=suffix) as temp_fp:
        temp_fp.write(file_content)
    return True, mutator_spec.render, os.path.relpath(temp_fp.name, static_dir)

In a nutshell, you mutate your static, write it to a temp file, make a response based on a temp file, you remove the temp file.

Here we register our mutated static:

class MutatedStaticSpec(NamedTuple):
    predicate: Callable[[str], bool]
    mutator: Callable[[str], Union[str, bytes]]
    static_dirs: List[str] = ["default dir here (the superset's one)"]

# mutate static file at runtime
# you match the file in `predicate` within the `static_dirs` folders, mutate the content in `mutator`,
MUTATED_STATIC = (
    MutatedStaticSpec(
        predicate=lambda file_path: (
            # we don't know the exact filename, as it may change any time
            file_path.endswith(".js")
            and any(placeholder in open(file_path).read() for placeholder in JS_CURRENCY_LOCALE_PLACEHOLDERS)
        ),
        mutator=_set_currency_placeholder,
        static_dirs=[os.path.join("/app/superset", "static")],
    ),
)

Here's js currency locale placeholder (you don't need to change it):

# [0] minimized js version, [1] formatted (we need this for compatibility between superset 1.4 and 1.5)
JS_CURRENCY_LOCALE_PLACEHOLDERS = ("currency:[\"$\",\"\"]", "currency: [\"$\", \"\"]")

And here's _set_currency_placeholder:

def _set_currency_placeholder(file_path):
    with open(file_path) as fp:
        file_content = fp.read()
        for placeholder in JS_CURRENCY_LOCALE_PLACEHOLDERS:
            file_content = file_content.replace(
                placeholder,
                render_template_string("currency:[\"{{ currency_symbol }}\",\"\"]")
            )
    return file_content

As you can see here, we render a single string render_template_string("currency:[\"{{ currency_symbol }}\",\"\"]"), so we need to have a currency in our general app context. And as we're using the flask app, we can simply add to the global context as follows:

def get_currency():
    # use any currency symbol here, you'd probably want to get it somewhere from the settings
    return {"currency_symbol": "$"}

Then register that context processor in superset_config.py as follows:

CONTEXT_PROCESSORS = (get_client_currency,)

When you access a chart/dashboard, static will mutate during that process, so the currency symbol gets replaced, and you'll see it in the UI.

Here's an example of how it looks if you change the currency to Euros:

image

Keep in mind that the currency symbol will get changed everywhere, no further actions are needed.

@stale stale bot removed the inactive Inactive for >= 30 days label Jun 21, 2022
@maZahaca
Copy link

Thanks for sharing such a way of customization @ypankovych !

I wonder if this is possible to have a truly dynamic currency on the dashboard, let's say we have different currencies attached to the Organisation.

And a dashboard which is shown for multiple Organizations.
Example:
Org 1, EUR - it should be showing € on dashboard charts
Org 2, USD - it should be showing $ on dashboard charts

Any ideas on how can we achieve this?

@Rudra665
Copy link

hey @ypankovych, i am struggling to implement the method you came up can you share the screenshots of the code with file you put it in so that i can also implement it, a quick answer would be appreciated.

@rusackas
Copy link
Member

rusackas commented Apr 9, 2024

Closing this out since it's gone silent for a good long while. Also since we're moving feature requests over to GitHub discussions, and leaving Issues for bugs (which this isn't). We'd love to see proposals or PRs for this (perhaps another formatted input in the dataset editor like we do for time formatters?) but to answer your question - nope, it's not (yet) supported. Happy to migrate this whole thread to a Discussion if folks want to keep it going. Thanks!

@rusackas rusackas closed this as completed Apr 9, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement:request Enhancement request submitted by anyone from the community i18n Namespace | Anything related to localization
Projects
None yet
Development

No branches or pull requests

6 participants