-
Notifications
You must be signed in to change notification settings - Fork 4.4k
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
Dashboard URL does not show new name when dashboard name is updated #1009
Conversation
This is sort of intentional to make sure the URL doesn't break. I see two possible solutions here:
|
Frankly, I am not sure which one would be better! I think second option is better in terms of giving ability to user to guess what the dashboard is for (from the slugs). |
I agree. And because we have the "technology" it's not a big cost to keep slugs, while improving their implementation. |
@susodapop worth opening a separate issue for this then. Probably an easy fix. |
…unless there is no number in it - in that case, take the entire slug as id
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Worth taking a moment and clearing the code a bit -- there are some places where we expect a dashboard ID, but call it slug in the code 😖
redash/handlers/dashboards.py
Outdated
dashboard = get_object_or_404(models.Dashboard.get_by_slug_and_org, dashboard_slug, self.current_org) | ||
if dashboard_slug.split('-')[0].isdigit(): | ||
identifier = int(dashboard_slug.split('-')[0]) | ||
fn = models.Dashboard.get_by_id_and_org |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have 185 dashboards in our database that match this pattern already ("28 Days Growth Metrics"), so old URLs will stop working. We need a fallback here.
Some ideas:
- Try with
get_by_slug_and_org
ifget_by_id_and_org
fails. - Test if the slug matches the dashboards slug.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Preferred and implemented the first, but then realized that it breaks when you have a dashboard called "28 Days Growth Metrics" (available through /dashboards/28-days-growth-metrics
) and then you create another dashboard called "I like trains" which gets assigned with id 28 (available through /dashboards/28-i-like-trains
).
In that case, get_by_id_and_org
wouldn't fail, and your previous links would break.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why?
When you request 28-days...
it will work with get_by_slug_and_org
, and when you request 28-i-like...
it will fail get_by_slug_and_org
and then try get_by_id_and_org
which will work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hehe, that's the opposite direction of what you suggested in the first option :)
Yeah, that might work, but it would add an extra query for most dashboard loads. I went ahead and implemented the second option which seems to work fine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rethinking it, there's no possible way I can think that we can remain agnostic to the slug if we don't do get_by_slug_and_org
and fallback to get_by_id_and_org
(i.e. if we don't go down this path, renaming dashboards would always break existing links). aba6e3b
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hehe, that's the opposite direction of what you suggested in the first option :)
😳 when I first read your reply I thought that's what happened, read my suggestion again but still parsed it in reverse order. 🤦♂️
Yeah, that might work, but it would add an extra query for most dashboard loads. I went ahead and implemented the second option which seems to work fine.
🤔 how about the following:
Currently the dashboard URLs are /dashboard/<slug>
. But they should actually be /dashboards/<id>-<whatever>
. So what if as part of this change we will change the URLs and keep the old ones for backward compatibility.
This way if we get a request for /dashboard/...
we know it's an old URL and we should pass a slug but if it's /dashboards/...
we use the new logic?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 a851402 😈
client/app/services/dashboard.js
Outdated
.replace(/[^\w-]+/g, '') | ||
.replace(/--+/g, '-') | ||
.replace(/^-+/, '') | ||
.replace(/-+$/, '')}`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should still create the slug in the backend, it will just update when you update the dashboard name.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, but that might break existing links, if anyone kept them.
My intention was to have existing slugs continue to work, while new links would be dashboards/123-pretty-much-the-name-of-the-dashboard-here-but-could-be-anything-youd-like-cause-we-only-care-about-the-id-from-now
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn't have to:
- We will keep the current
slug
value in the database as is, so we can do lookups for existing dashboards. - We will no longer update it, but compute it when returning the API response based on the current name.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
name_as_slug 😬😬😬
url which contains their new name
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Applied some of the updates, for tomorrow it will be missing:
Make the old url for dashboard change redirect (I think I'll just use thelocation.update
hack as we do when the dashboard changes name)Update other api calls to use id(need to confirm the "break all existing api usages" part)Update backend testsUpdate Cypress tests
redash/serializers/__init__.py
Outdated
@@ -246,6 +246,7 @@ def serialize_dashboard(obj, with_widgets=False, user=None, with_favorite_state= | |||
d = { | |||
"id": obj.id, | |||
"slug": obj.slug, | |||
"name_as_slug": obj.name_as_slug, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Backward compatibility reasons + the other routes needed the slug. So the delete button wouldn't work if you had changed the dashboard url for example (considering route as /api/dashboards/:slug
as the api would rely in the actual "slug".
redash/models/__init__.py
Outdated
cls.query.filter( | ||
cast(cls.id, db.String) == id_or_slug, cls.org == org | ||
).one_or_none() | ||
or cls.query.filter(cls.slug == id_or_slug, cls.org == org).one() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks good to me, but need to keep in mind that all existing api usage outside of our Frontend will be broken.
if (has(data, "name")) { | ||
navigateTo(urlForDashboard(updatedDashboard), true); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, correct
self.assertEqual(rv.status_code, 200) | ||
self.assertTrue(rv.json["widgets"][0]["restricted"]) | ||
self.assertNotIn("restricted", rv.json["widgets"][1]) | ||
|
||
def test_get_non_existing_dashboard(self): | ||
rv = self.make_request("get", "/api/dashboards/not_existing") | ||
rv = self.make_request("get", "/api/dashboards/-1") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This actually doesn't happen only to the dashboard, but anything that's /api/resource/not_a_number
causes a sqlalchemy error. This PR didn't feel like a scope to fix it and at least now I don't have an idea on how to solve this as it turned to be a dynamic thing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking good. Two small things in comments.
redash/handlers/dashboards.py
Outdated
fn = ( | ||
models.Dashboard.get_by_slug_and_org | ||
if request.args.get("legacy") is not None | ||
else models.Dashboard.get_by_id_and_org | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ternary operators aren't that readable in Python. Might be better to write this as:
if request.args.get("legacy") is not None:
fn = models.Dashboard.get_by_slug_and_org
else:
fn = models.get_by_id_and_org
(fn
will be available in the outer context)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done 👍
redash/serializers/__init__.py
Outdated
@@ -246,6 +246,7 @@ def serialize_dashboard(obj, with_widgets=False, user=None, with_favorite_state= | |||
d = { | |||
"id": obj.id, | |||
"slug": obj.slug, | |||
"name_as_slug": obj.name_as_slug, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now that we migrated all API routes to use an id, we can drop this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good 👍 Just two small notes, but fixing them is up to you.
Co-authored-by: Levko Kravets <levko.ne@gmail.com>
👍 |
…etredash#1009) * on dashboard api calls - take the id from the beginning of the slug, unless there is no number in it - in that case, take the entire slug as id * add dashboard id when showing links to dashboards * change path to include new name when renaming dashboards * move slug generation to backend * redirect to new name after changing (this time with a proper promise) * oh right, we already have a slug function * add spec that makes sure that renamed dashboards are redirected to the url which contains their new name * use id-slug in all Cypress specs * move dashboards from /dashboard/:slug to /dashboards/:id-:name_as_slug * Update dashboard url as its name changes * Update separator to be "/" * Update missing dashboard urls * Update api not to depend on int id * Use '-' instead of '/' as separator and update Dashboard.get calls * slug -> name_as_slug * Keep slug urls on cypress * Update route path * Use legacy attr for GET * Use getter for urlForDashboard * Update dashboard url when loaded by slug * Update Dashboard routes to use id instead of slug * Update Dashboard handler tests * Update Cypress tests * Fix create new dashboard spec * Use axios { params } * Drop Ternary operator * Send updated slug directly in 'slug' attr * Update multiple urls Dashboard test name * Update route names Co-authored-by: Levko Kravets <levko.ne@gmail.com> Co-authored-by: Gabriel Dutra <nesk.frz@gmail.com> Co-authored-by: Levko Kravets <levko.ne@gmail.com>
Before dashboard title change:
After dashboard title change: