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

feat: add permalink to dashboard and explore #19078

Merged
merged 40 commits into from
Mar 16, 2022

Conversation

villebro
Copy link
Member

@villebro villebro commented Mar 9, 2022

SUMMARY

This PR adds new permanent link functionality that makes it possible to persist Dashboard and Explore state in the metadata database. The state is persisted in a new metadata table called key_value, which is able to store pickleable binary objects (can be used in the future for other resources, too). The new permalinks look as follows:

  • Dashboard: http://host/superset/dashboard/p/<key>/
  • Explore: http://host/superset/explore/p/<key>/

The dashboard state is a dict with the following fields:

  • filterState: native filter state
  • hash (optional): the anchor (used for direct links to charts/tabs)
  • urlParams (optional): any URL params that are active, excluding the reserved URL params like native_filter_key etc. This is also where filter box state is persisted. Note that these are stored as a list of key-value tuples to support having multiple same keys (not currently supported by the chart data API but implemented like this for future proofing)

The Explore state is also a dict with the following fields:

  • formData the chart form data
  • urlParams (optional): any URL params that are active, excluding the reserved URL params like form_data_key, slice_id etc. (same format as in the dashboard state)

The benefits compared to the deprecated shorturl functionality:

  • Leverages existing resource security mechanism: if you don't have access to a chart/dashboard, you are also not able to view permalink state for those charts/dashboards
  • Uses UUID-based keys by default to make permalinks unguessable. However, where needed, numeric serial keys (similar to the legacy shorturl keys) can be used by flipping a config flag. Do note, however, that changing this setting will cause old links to stop working, i.e. it's not possible to retrieve permalink state based by its UUID key if the feature has been configured to work with the serial id and vice versa.
  • Is not restricted by the URL length limitations that affects shorturls (however, Filter Box state is still subject to URL overflows).

Notice that old shorturls will still work like before - but links created after this will be based on the new permalink feature.

Detailed list of changes:

  • Rename current key_value package to temporary_cache to better reflect the ephemeral nature of the state (not guaranteed to be persisted)
  • Add new key_value table to metadata database along with relevant commands for adding, updating, getting and deleting key-value pairs
  • Add permalink endpoints (POST and GET) to both Explore and Dashboard that leverage the new key_value store
  • Add PERMALINK_KEY_TYPE to config.py which makes it possible to switch between serial and UUID based keys (UUID = default)
  • fully support persisting filter box state and url parameters in permalink state (this was currently mostly broken)
  • remove old shorturl creation functionality from frontend and backend
  • Migrate all permalink buttons to new feature:
    • Explore action buttons (above chart): "Copy permalink to clipboard" and "Share permalink by email"
    • Explore embed iframe button (above chart): link now references a permalink
    • Dashboard context menu: "Copy permalink to clipboard" and "Share permalink by email"
    • Dashboard chart context menu: "Copy permalink to clipboard" and "Share permalink by email"
    • Dashboard tab anchor link

BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF

Screen.Recording.2022-03-11.at.9.35.44.AM.mov

TESTING INSTRUCTIONS

  1. Explore a dataset, add a metric/columns to a few controls and share a permalink both via clipboard and email. Change control values and verify that changes don't change the state in the previously shared permalink. Bonus: create a chart with Jinja that uses url_param and make sure URL params are correctly persisted in permalinks.
  2. Click the Embed code button and make sure the link works
  3. Save the chart and ensure sharing works similar to above
  4. Go to a dashboard with native filters and tabs, select a few filters and share the dashboard from the context menu.
  5. Click on the anchor button next to a tab and make sure both the link and email message works
  6. Click on a chart context menu and make sure both link and email message works
  7. Create a dashboard with a filter box and a chart, apply a filter on the filter box and make sure shared permalinks open with the predefined filters
  8. Bonus: add a chart with Jinja that references a url_param and make sure the chart renders correctly 1) in the dashboard after creating a permalink 2) in the explore view after creating a permalink to the chart.

ADDITIONAL INFORMATION

  • Has associated issue:
  • Required feature flags:
  • Changes UI
  • Includes DB Migration (follow approval process in SIP-59)
    • Migration is atomic, supports rollback & is backwards-compatible
    • Confirm DB migration upgrade and downgrade tested
    • Runtime estimates and downtime expectations provided
  • Introduces new feature or API
  • Removes existing feature or API

@codecov
Copy link

codecov bot commented Mar 10, 2022

Codecov Report

Merging #19078 (c0fb6d7) into master (8d53db1) will increase coverage by 0.02%.
The diff coverage is 86.77%.

❗ Current head c0fb6d7 differs from pull request most recent head 4e9b423. Consider uploading reports for the commit 4e9b423 to get more accurate results

@@            Coverage Diff             @@
##           master   #19078      +/-   ##
==========================================
+ Coverage   66.54%   66.56%   +0.02%     
==========================================
  Files        1646     1668      +22     
  Lines       63630    64190     +560     
  Branches     6475     6476       +1     
==========================================
+ Hits        42343    42729     +386     
- Misses      19607    19779     +172     
- Partials     1680     1682       +2     
Flag Coverage Δ
hive ?
mysql 81.96% <88.20%> (+0.08%) ⬆️
postgres 82.00% <88.20%> (+0.08%) ⬆️
presto ?
python 82.09% <88.20%> (-0.23%) ⬇️
sqlite 81.76% <88.20%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
...erset-frontend/src/components/AnchorLink/index.jsx 80.00% <ø> (ø)
.../components/Header/HeaderActionsDropdown/index.jsx 71.42% <ø> (ø)
...dashboard/components/SliceHeaderControls/index.tsx 66.66% <ø> (ø)
...nd/src/dashboard/components/gridComponents/Tab.jsx 80.48% <ø> (ø)
...rd/components/nativeFilters/FilterBar/keyValue.tsx 18.18% <ø> (ø)
...nd/src/explore/components/ExploreActionButtons.tsx 57.14% <0.00%> (ø)
.../explore/components/ExploreViewContainer/index.jsx 56.99% <0.00%> (ø)
superset/explore/utils.py 100.00% <ø> (ø)
superset/temporary_cache/commands/entry.py 100.00% <ø> (ø)
superset/temporary_cache/commands/parameters.py 100.00% <ø> (ø)
... and 84 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 8d53db1...4e9b423. Read the comment docs.

@michael-s-molina
Copy link
Member

michael-s-molina commented Mar 10, 2022

I think we also need to use a permanent link for "Embed Code" in Explore to reflect the latest changes made by the user.

Screen Shot 2022-03-10 at 9 05 27 AM

@michael-s-molina
Copy link
Member

michael-s-molina commented Mar 10, 2022

I created a permanent link for a dashboard as an admin and tried to access it using a user that doesn't have permission. I get an "Access Denied" tooltip as expected.

I tried the same thing but this time for an Explore chart. I get this:

Screen Shot 2022-03-10 at 9 11 37 AM

@michael-s-molina
Copy link
Member

michael-s-molina commented Mar 10, 2022

Do you think we need the changed_on and changed_by_fk columns in the database? They'll always be null.

@michael-s-molina
Copy link
Member

I found an existing problem but we can try to fix it here. When the network is not available (you can simulate using the Network tab of Chrome Dev Tools), and I try to copy a permalink, the message states "Sorry, your browser does not support copying." I'm fine with either handling the different types of errors or use the same generic message "Sorry, something went wrong. Try again later." that is used when sharing by email. If we do decide to handle by type, then we should also make the change to email sharing.

@michael-s-molina
Copy link
Member

When trying to access a permanent link for a deleted chart I get the following message:

Screen Shot 2022-03-10 at 9 51 31 AM

When trying to access a permanent link for a deleted dashboard I get the following message:

Screen Shot 2022-03-10 at 9 52 52 AM

@@ -309,8 +310,8 @@ class SliceHeaderControls extends React.PureComponent<

{supersetCanShare && (
<ShareMenuItems
copyMenuItemTitle={t('Copy chart URL')}
emailMenuItemTitle={t('Share chart by email')}
copyMenuItemTitle={t('Copy permalink to clipboard')}
Copy link
Member

@kgabryje kgabryje Mar 10, 2022

Choose a reason for hiding this comment

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

I'm not sure if using a word "permalink" brings value to the end user. I feel like it might be confusing, and simply saying "link" or "URL" makes the intention clearer. I'd suggest asking a product manager for an opinion on that.
(applies to changes in ExploreActionButtons too)

Copy link
Member

@michael-s-molina michael-s-molina Mar 10, 2022

Choose a reason for hiding this comment

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

The inspiration here is coming from Github and Shortcut but we could validate the name as you suggested.

Screen Shot 2022-03-08 at 10 02 21 AM

Screen Shot 2022-03-08 at 10 03 35 AM

Copy link
Member Author

Choose a reason for hiding this comment

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

Agreed - I know @michael-s-molina did some investigative work and found many products use the term. Here's GitHub:
image

One other thing we may want to consider is removing "to clipboard", as it's pretty much self evident.

Copy link
Member

@michael-s-molina michael-s-molina left a comment

Choose a reason for hiding this comment

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

Awesome work @villebro! The new security checkings for permanent links are a BIG improvement to the previous version. I also really liked the reorganization of key_value and temporary_cache. I tested and reviewed the whole feature and left some comments. I’ll continue with the review as we add tests and fix some bugs.

@villebro
Copy link
Member Author

I think we also need to use a permanent link for "Embed Code" in Explore to reflect the latest changes made by the user.

Good catch - fixed

@villebro
Copy link
Member Author

Do you think we need the changed_on and changed_by_fk columns in the database? They'll always be null.

While this is true for permalinks, it is not necessarily true for other features that use the key-value table (note that we have commands for deleting and updating key value entries)

@villebro villebro force-pushed the villebro/key-value-permalink branch from aee4236 to 7b0f4b2 Compare March 11, 2022 11:42
@villebro
Copy link
Member Author

Added redirects to the list view for both Explore and Dashboard with toasts

It's working for dashboards but it's failing for Explore.

Screen.Recording.2022-03-16.at.10.15.54.AM.mov

@michael-s-molina this is an interesting edge case - in this case the chart has been removed, hence the chart doesn't have a title, but since the form data is available in the permalink state, all the relevant state is available to render the chart, and is comparable to a permalink for a chart that hasn't been saved yet. What would be the expected behavior here?

@michael-s-molina
Copy link
Member

@michael-s-molina this is an interesting edge case - in this case the chart has been removed, hence the chart doesn't have a title, but since the form data is available in the permalink state, all the relevant state is available to render the chart, and is comparable to a permalink for a chart that hasn't been saved yet. What would be the expected behavior here?

I think it's a different use case. We don't know why a chart has been deleted, it may be because the information is no longer valid or the underlying dataset has been changed/removed, etc. We should consider cached data for removed charts as no longer valid and redirect the user to the charts list.

@villebro
Copy link
Member Author

@michael-s-molina this is an interesting edge case - in this case the chart has been removed, hence the chart doesn't have a title, but since the form data is available in the permalink state, all the relevant state is available to render the chart, and is comparable to a permalink for a chart that hasn't been saved yet. What would be the expected behavior here?

I think it's a different use case. We don't know why a chart has been deleted, it may be because the information is no longer valid or the underlying dataset has been changed/removed, etc. We should consider cached data for removed charts as no longer valid and redirect the user to the charts list.

Makes sense - I'll change as follows: if chart permalink metadata has a chart id, and said chart id is no longer available, we redirect to the list view and issue a toast for missing chart. Btw, now I'm confused why there wasn't an exception when it tried to validate the perms for the missing chart, but I guess I'll soon find out.

@michael-s-molina
Copy link
Member

Btw, now I'm confused why there wasn't an exception when it tried to validate the perms for the missing chart, but I guess I'll soon find out.

😮 Yes, it's important to check that.

Copy link
Member

@michael-s-molina michael-s-molina left a comment

Choose a reason for hiding this comment

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

LGTM. Thank you so much for all the hard work @villebro! After all the challenges the application is in a much better state now with the endpoints for cached and permanent states and all the security improvements.

Copy link
Member

@jinghua-qa jinghua-qa left a comment

Choose a reason for hiding this comment

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

LGTM

@villebro
Copy link
Member Author

Thanks @michael-s-molina and @jinghua-qa for all the help with this! ❤️

@villebro villebro merged commit b7a0559 into apache:master Mar 16, 2022
@github-actions
Copy link
Contributor

Ephemeral environment shutdown and build artifacts deleted.

@villebro villebro deleted the villebro/key-value-permalink branch March 16, 2022 23:17
Copy link
Member

@ktmud ktmud left a comment

Choose a reason for hiding this comment

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

Thanks for working on this, @villebro ! Sorry for being late to the party, but I was half way through the reviewing then got distracted by something else.

Sharing the comments nonetheless in case they are useful for future iterations.

const [shortUrl, setShortUrl] = useState('');

async function getShortUrl(urlOverride?: string) {
if (update) {
Copy link
Member

Choose a reason for hiding this comment

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

One of the benefits of this hook is that the generated URL will not update if dashboard states didn't update. I.e., when you click on "Copy URL" twice, it will only generate one short-url record in the database, as opposed to now, each click will generated a new and different short-url. Do you think it's worth implementing a similar deduping effort either in the frontend or backend?

url: PropTypes.string,
addDangerToast: PropTypes.func.isRequired,
anchorLinkId: PropTypes.string,
dashboardId: PropTypes.number,
Copy link
Member

Choose a reason for hiding this comment

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

Since this is just a 100L component, it may be worth just converting this to TypeScript instead of adding new proptypes

.catch(this.props.addDangerToast);
const nativeFiltersKey = getUrlParam(URL_PARAMS.nativeFiltersKey);
if (this.props.dashboardId) {
getFilterValue(this.props.dashboardId, nativeFiltersKey)
Copy link
Member

Choose a reason for hiding this comment

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

Maybe getFilterValue should be renamed to getSavedFilterState for clarify.

We should probably also just pass the full state to URLShortLinkButton itself so we can avoid a round trip to the server for the saved states---because the client states should always be in sync with the URL. If the states already exist somewhere on the client, then we can pass it around for generating the short URL.

By passing full states (an arbitrary JSON object + a base URL template), it also decouples URLShortLinkButton with either dashboard or chart API endpoint, so it can be used in even other places (e.g. SQL queries, etc).

@@ -17,6 +17,7 @@
* under the License.
*/
import { SupersetClient, logging } from '@superset-ui/core';
Copy link
Member

Choose a reason for hiding this comment

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

This file is still named keyValue.tsx, should we rename it for consistency?

500:
$ref: '#/components/responses/500'
"""
key_type = current_app.config["PERMALINK_KEY_TYPE"]
Copy link
Member

@ktmud ktmud Mar 17, 2022

Choose a reason for hiding this comment

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

I don't think we should provide this option. Adding one more config options means more effort to support it in the future. We should not have to provide more flexibility in the config file unless users request it. The numeric ids were deprecated based on arguments of security risks---if security risk with numeric ids is real, then it's real for everyone. If not, then we should just not worry about it and keep using numeric ids that users are used to.

If URL readability was the concern for UUID, we should probably just change the keys from uuid (which only uses 16 hex characters) to case-sensitive hashids used in real short-url services (e.g., bit.ly and t.co).

Personally I think hashids might be a better end state.

Copy link
Member Author

Choose a reason for hiding this comment

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

I added this option to per a request from @graceguo-supercat, see discussion here: #18181 (comment)

Copy link
Member Author

@villebro villebro Mar 19, 2022

Choose a reason for hiding this comment

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

Having said that, I really like the readability of hashids, so I'm happy to make that the default and only supported permalink key type, thus eliminating this config flag. @graceguo-supercat is that ok for you? I can open a follow-up PR to address this (should be fairly simple).

Copy link
Member

@michael-s-molina michael-s-molina Mar 21, 2022

Choose a reason for hiding this comment

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

The cache endpoint is using token_url. We could use it with a smaller length (with collisions in mind) instead of introducing another dependency. I'm fine with either solution since hashids also has its merits.

Copy link
Member

@ktmud ktmud Mar 21, 2022

Choose a reason for hiding this comment

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

Hashids are deterministic based on input (numeric ids) whilst token.url is completely random. I think we may want to be deterministic here.

@sadpandajoe
Copy link
Member

🏷️ preset:2022.11

villebro added a commit that referenced this pull request Apr 3, 2022
* rename key_value to temporary_cache

* add migration

* create new key_value package

* add commands

* lots of new stuff

* fix schema reference

* remove redundant filter state from bootstrap data

* add missing license headers

* fix pylint

* fix dashboard permalink access

* use valid json mocks for filter state tests

* fix temporary cache tests

* add anchors to dashboard state

* lint

* fix util test

* fix url shortlink button tests

* remove legacy shortner

* remove unused imports

* fix js tests

* fix test

* add native filter state to anchor link

* add UPDATING.md section

* address comments

* address comments

* lint

* fix test

* add utils tests + other test stubs

* add key_value integration tests

* add filter box state to permalink state

* fully support persisting url parameters

* lint, add redirects and a few integration tests

* fix test + clean up trailing comma

* fix anchor bug

* change value to LargeBinary to support persisting binary values

* fix urlParams type and simplify urlencode

* lint

* add optional entry expiration

* fix incorrect chart id + add test

(cherry picked from commit b7a0559)
@john-bodley
Copy link
Member

john-bodley commented Apr 14, 2022

@michael-s-molina or @villebro do you know whether this change may have introduced a regression in how inline dashboard permalink works? Previously you could create an anchor to the chart within the dashboard though now it seems like the permalink links to the chart in explorer.

The feature was loved/used by a number of people at Airbnb.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🏷️ bot A label used by `supersetbot` to keep track of which PR where auto-tagged with release labels lts-v1 preset:2022.11 preset-io size/XXL 🍒 1.5.0 🍒 1.5.1 🍒 1.5.2 🍒 1.5.3 🚢 2.0.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants