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

Dashboard URL does not show new name when dashboard name is updated #1009

Merged
merged 34 commits into from
Jul 16, 2020

Conversation

rauchy
Copy link
Contributor

@rauchy rauchy commented Aug 15, 2019

Before dashboard title change:
before_title_change

After dashboard title change:
after_title_change

@arikfr
Copy link
Member

arikfr commented Apr 23, 2016

This is sort of intentional to make sure the URL doesn't break. I see two possible solutions here:

  1. Stop using slugs for dashboard names and switch to ids as we do with queries, alerts, etc.
  2. If we want to keep the slugs, we should add the id of the dashboard to the name and do the lookup by the id and not the slug. I.e. if the dashboard id is 7, then the url will be /dashboard/7-cohort-analysis. Because the slug is meaningless for lookup we can keep it at sync with the actual title. This has other benefits such as removing the need to make sure the slug is unique (the id will guarantee this).

@arikfr arikfr added the Backend label Apr 23, 2016
@ChiragKParmar
Copy link
Contributor Author

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).

@arikfr
Copy link
Member

arikfr commented May 4, 2016

I agree. And because we have the "technology" it's not a big cost to keep slugs, while improving their implementation.

@arikfr arikfr added this to the Next milestone Mar 18, 2018
@susodapop
Copy link
Contributor

Related to this: the CSS title needs to update when the dashboard name is changed. I think fixing the title is independent of the URL slug. It should be fixed regardless what is done about the slugs.

Notice in the attached screenshot that the tab title doesn't match the dashboard title.

screen shot 2019-01-17 at 07 19 54

@arikfr
Copy link
Member

arikfr commented Jan 17, 2019

@susodapop worth opening a separate issue for this then. Probably an easy fix.

Copy link
Member

@arikfr arikfr left a 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 😖

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
Copy link
Member

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 if get_by_id_and_org fails.
  • Test if the slug matches the dashboards slug.

Copy link
Contributor Author

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.

Copy link
Member

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.

Copy link
Contributor Author

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.

Copy link
Contributor Author

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

Copy link
Member

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 a851402 😈

.replace(/[^\w-]+/g, '')
.replace(/--+/g, '-')
.replace(/^-+/, '')
.replace(/-+$/, '')}`;
Copy link
Member

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.

Copy link
Contributor Author

@rauchy rauchy Aug 15, 2019

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.

Copy link
Member

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:

  1. We will keep the current slug value in the database as is, so we can do lookups for existing dashboards.
  2. We will no longer update it, but compute it when returning the API response based on the current name.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

name_as_slug 😬😬😬

@rauchy
Copy link
Contributor

rauchy commented Aug 19, 2019

  1. You should probably move this into redash.utils.slugify.
  2. What does it do differently?
  3. How does it handle unicode?
  4. Tests?

I realized this after a few minutes 🤦‍♂

image

Copy link
Member

@gabrieldutra gabrieldutra left a 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 the location.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 tests
  • Update Cypress tests

@@ -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,
Copy link
Member

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".

Comment on lines 1178 to 1181
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()
Copy link
Member

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.

Comment on lines 75 to 77
if (has(data, "name")) {
navigateTo(urlForDashboard(updatedDashboard), true);
}
Copy link
Member

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")
Copy link
Member

@gabrieldutra gabrieldutra Jul 3, 2020

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.

Copy link
Member

@arikfr arikfr left a 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.

Comment on lines 152 to 156
fn = (
models.Dashboard.get_by_slug_and_org
if request.args.get("legacy") is not None
else models.Dashboard.get_by_id_and_org
)
Copy link
Member

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)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done 👍

@@ -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,
Copy link
Member

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?

Copy link
Collaborator

@kravets-levko kravets-levko left a 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.

client/app/pages/dashboards/DashboardPage.jsx Outdated Show resolved Hide resolved
Co-authored-by: Levko Kravets <levko.ne@gmail.com>
@arikfr arikfr merged commit cb97364 into master Jul 16, 2020
@arikfr
Copy link
Member

arikfr commented Jul 16, 2020

👍

@arikfr arikfr deleted the id-and-slug-for-dashboards branch July 16, 2020 20:04
andrewdever pushed a commit to andrewdever/redash that referenced this pull request Oct 5, 2020
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants