-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Flatten ?filters=(...)&labels=(...) to ?f=...&f=...&l=...&l=... #4810
Conversation
|
👋 Sorry for a drive-by comment on a draft PR, but if you set the attribute key to More info: https://hexdocs.pm/plug/Plug.Conn.Query.html |
0f16e70
to
caf00fe
Compare
Thanks for bringing it up @ruslandoga ! At the moment, this format must be recognizable only to the FE and ideally humans, though. I think of it as URL-stored dashboard state. Currently, the BE receives applied dashboard filters in URL-encoded stringified JSON, and there's an extra transform taking place https://github.com/plausible/analytics/blob/master/assets/js/dashboard/util/filters.js#L231 before that, which we're not planning to get rid of (in the interest of readable URLs). |
6f83914
to
cd6b053
Compare
The idea with the new filters format was to also support nesting in case we need it in the future. Stuff like:
This format seems to make that a bit harder. Did you consider that as well @apata ? |
Good question, @ukutaht! I expect there to be a bunch of ways to do this because we know the key words: and, or, not, all operators and all operator options. [
[
"or",
[
[
"and",
[
[
"is",
"visit:source",
["Google", "Bing"],
{ "case_sensitive": false }
],
["is", "visit:utm_medium", "is", ["cpc"]]
]
],
[
"and",
[
["is", "visit:source", ["Facebook"], { "case_sensitive": false }],
["is", "visit:utm_medium", ["paid_social"]]
]
]
]
],
["not", ["is", "visit:country", ["Estonia"]]],
["contains", "event:goal", ["trial"], { "case_sensitive": false }]
] For this edgy filter tree, an URL in the new style might be
This would be reconstructable thanks to fixed order of f= in the URL and known group lengths. // url.split("&").map(i => i.split("=")).filter(i => i[0] === "f").map(i => i[1]) -->
[
"or/2",
"and/2",
"is/i,source,Google,Bing",
"is,utm_medium,cpc",
"and/2",
"is/i,source,Facebook",
"is,utm_medium,paid_social",
"not/1",
"is,country,Estonia",
"contains/i,goal,trial",
]
// reconstruct recursively |
Cool - the format makes sense to me. While it does diverge a bit form what we initially designed, and comes with the annoyance of managing yet another backwards compatibility redirect, I think it's probably worth it in order to have URLs that can be copy-pasted easily. |
const serializedFilter = [ | ||
encodeURIComponentPermissive(operator, ':/'), | ||
encodeURIComponentPermissive(dimension, ':/'), | ||
...clauses.map((clause) => |
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 recently added support for case insensitivity in APIv2 via modifiers (plausible/docs#567).
While we haven't exposed this in the frontend, it's something we should do once we figure out the UX. I'm not sure whether this function is extendable to allow for that structure though.
Note that in general modifiers are not always flags and may contain arbitrary data structures within.
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.
I believe it is: #4810 (comment)
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.
Note that in general modifiers are not always flags and may contain arbitrary data structures within.
The filter modifiers and structures we support is up to us, so I'm not clear on what you mean by arbitrary.
That said, I can imagine something like the following working:
-> `is\non_flag_modifier_name:${encodeURIComponent(JSON.stringify(complex_structure))},<dimension>,<clause1>,<clause2>`
That's stretching the limits of readability though.
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.
I'm not clear on what you mean by arbitrary.
@macobo and I were just bouncing around some ideas about how to extend filtering capabilities in the future which would utilize more modifiers. Note that these new modifiers will not necessarily be built in the short term but having the ability to extend filters with this kind of stuff in the future would be really useful.
See this discussion: https://3.basecamp.com/5308029/buckets/26383192/card_tables/cards/7793417773
As we were discussing it did cross my mind that this may not work well with the proposed URL structure here.,,
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.
Thanks for the extra examples! What makes these modifiers arbitrary?
We know what keys to look for, and even what the possible values can be.
As I see it, filter clauses are the only arbitrary value currently: their values are unpredictable to us, and may overlap with any of our option names or option configurations.
That's why in this solution, they are expanded to be the last members of the comma separated array: f=<known word at known position>/<optional known words and values>,<known word at known position>,...unpredictable words...
However, from my side, I'm not married to this solution. There's various others that are totally unexplored at the moment, like enriching jsonurl etc. It's just that "working well" will be hard to solve for. Short, easy to implement, linkable, readable are some of the things that I optimized for. I personally think expandable is also one of the qualities, but I see that doubt remains.
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.
I think something analogous to what you proposed in #4810 (comment) should work for the original question (how to handle more complex data structures in filter modifiers and should unblock us for now.
c4e5e89
to
7df16a1
Compare
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.
Behaviorally this is great and I appreciate the extensive tests!
I didn't do extensive testing on the backwards redirect.
I also had some minor suggestions around code structure to avoid the backwards redirects causing issues in the future. Let me know if you'd prefer me to push to the branch to implement the suggestions.
assets/js/dashboard/query.ts
Outdated
) { | ||
const searchRecord = parseSearch(windowLocation.search) | ||
const getValue = (k: string) => searchRecord[k] | ||
export function maybeGetRedirectTargetFromLegacySearchParams( |
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.
Let's add a comment detailing the history of this above it - this will help future readers of the code.
export function maybeGetRedirectTargetFromLegacySearchParams( | |
/* | |
Calculates whether to redirect from legacy url format(s) to new formats. | |
We serialize dashboard filters in the URL and maintain old URLs for backwards compatibility reasons to not break old links. | |
There have been two previous formats: | |
1. legacy-searchparams - a custom encoding schema was used for filters. This was deemed not flexible enough and diverged from how we represented filters in the code. | |
2. legacy-jsonurl - jsonurl was used to encode the more complex filter structure via json url. However the links generated via this didn't always work across all platforms (e.g. Twitter). | |
*/ | |
export function maybeGetRedirectTargetFromLegacySearchParams( |
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.
Good idea. Added with slight modifications.
assets/js/dashboard/query.ts
Outdated
const getValue = (k: string) => searchRecord[k] | ||
export function maybeGetRedirectTargetFromLegacySearchParams( | ||
windowLocation: Location | ||
): null | string { |
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.
I was struggling reviewing this function. I think it's because the original 'legacy' format and the new 'legacy' format are both called legacy in this, and the code is structured differently, causing friction.
I suggest:
- naming the formats:
legacy-jsonurl
andlegacy-searchparams
- moving
parseLegacyFilter
andparseLegacyPropsFilter
andparseLegacyFilter
tolegacy-searchparams.ts
- Splitting this function into two parts, function
function redirectTarget()
and putting them inlegacy-searchparams.ts
andlegacy-jsonurl-...
.
I think this would increase consistency and simplify keeping this working if/as we iterate this further.
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.
I agree that what you propose is better, but since this the first redirect wasn't properly tested, I opted against it originally: in review, any slight breaking changes could have slipped past. I've now split it out though.
@@ -0,0 +1,80 @@ | |||
/* @format */ |
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.
I am generally not a fan of utils - they tend to become grab bags of misc code. Given the behavior becomes also more integrated with the rest of the app code, I think some thought is warranted.
How would you feel about moving `url-search-params and the legacy files under assets/js/dashboard/navigation/ or creating a new folder for them?
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.
You're right that utils can get messy, but I think I'd prefer it solved in a separate refactor that tackles code organization only.
There's relations between url, api, url search params, query, filters that our current code organization isn't fully capturing. Just now I had to solve some circular import dependency issue.
a85f8e6
to
33dc1fd
Compare
33dc1fd
to
c20fbd3
Compare
Changes
Tests
Changelog
Documentation
Dark mode