-
Notifications
You must be signed in to change notification settings - Fork 1.7k
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
Fixes path_prefix flag #1623
Fixes path_prefix flag #1623
Conversation
@@ -62,7 +62,13 @@ export function createRouter(pathPrefix = 'data', demoMode = false): Router { | |||
}; | |||
}; | |||
|
|||
let _router: Router = createRouter(); | |||
export function getDefaultRouter(): Router { | |||
const sep = window.location.pathname.endsWith('/') ? '' : '/'; |
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’s not great that this introduces a side-effect at module load time.
We now need to reason about the first time that this module is imported;
it becomes harder to reason about in isolation.
Before #1512, we issued requests to relative paths. Can we just go back
to doing that instead of switching to absolute paths and then trying to
relativize them? We wouldn’t need to worry about window.location
at
all, right?
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 contemplated and decided to keep the logic that uses new URL
for its sanitization/normalization but unfortunately forces us to reason with absolute path. Not sure if I understand what you mean by side-effect at the bootstrap time.
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.
Noted. I haven’t carefully looked at the ways in which we use URL
;
I’ll do so after the holiday.
* @param demoMode {boolean=} Whether to modify urls for filesystem demo usage. | ||
*/ | ||
export function createRouter(pathPrefix = 'data', demoMode = false): Router { | ||
export function createRouter(pathPrefix, demoMode = false): Router { |
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 is the default prefix no longer "data"
? This is a breaking
change—does it just not happen to break any clients?
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 vetted all the callsites manually
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 vetted all the callsites manually
I mean, this is not possible in principle, right? createRouter
is a
publicly exported function defined in a module whose Bazel target has
visibility //visibility:public
in a workspace that we explicitly
encourage people to import, both inside Google and beyond. By all
accounts it is a public API. Are we in the habit of breaking our public
contracts?
I also still don’t understand why you want to make this change.
@@ -81,6 +81,23 @@ describe('backend tests', () => { | |||
assert.equal(r.runs(), '/foo/runs'); | |||
}); | |||
|
|||
describe('default router', () => { | |||
beforeEach(function() { | |||
this.router = getDefaultRouter(); |
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 assign to this.router
and introduce this state instead of just
using
assert.equal(
getDefaultRouter().runs(),
`${window.location.pathname}/data/runs`
);
in the one test case that actually uses this?
PR tensorflow#1512 introduced a bug where it formed a complete path without using existing pathname. Previously without forming the complete path, it was treated as a relative path from current window.location.pathname.
Argh, I just read the proper API doc for |
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 is definitely better: thanks. It’s unfortunate that we depend on
window.location
at all, but I see that existing code does this, too,
so at least this isn’t a regression.
}); | ||
|
||
it('ignores custom extension', function() { | ||
assert.equal( | ||
assertRelativePath( | ||
this.router.pluginRoute('scalars', '/a', undefined, 'meow'), |
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 does this typecheck? The Router
interface declares pluginRoute
as a function taking two parameters, not four. Do we have implicit any
propagation somehow?
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 catching the error!
Fixed it.
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.
No, that’s not what I’m concerned about. That’s perfectly fine.
At the callsite in this comment, you are invoking a function with more
arguments than the number of declared parameters. TypeScript disallows
this (which it must—this is the only sound thing to do). But this
type error was not caught. Why?
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.
From a brief experiment, it looks like attaching router onto Mocha's scope makes us lose all the typing info. Will refactor to avoid that.
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, that’s what I suspected. Thanks: please do refactor thus.
// Use URL to normalize pathPrefix with leading slash and without. | ||
url.pathname = pathPrefix + path; | ||
const maybeRelativePath = pathPrefix + path; | ||
const url = new URL(maybeRelativePath, window.location.href); |
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.
Do we need to use the actual window.location here? Could we just pass "http://localhost" or even "http://ignored" instead? That would make it clearer that we're only dealing with relative URL paths.
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.
The point of using window.location.href
is that if you’ve navigated to
http://localhost:6006/some-prefix/ then that prefix will be included
in the eventual URL. Doesn’t this break if you use "http://localhost"
instead of window.location.href
? Am I missing something?
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.
But maybeRelativePath already includes the configured pathPrefix. Unless the idea here was to make TensorBoard automatically detect the pathPrefix from the href, I don't think it's necessary to get it from window.location.href.
And actually, I don't think any path information is retained from the second parameter to URL at all, from testing it just now - it only extracts the origin anyway:
> new URL('prefix/foo', 'http://xyz')
URL {href: "http://xyz/prefix/foo", origin: "http://xyz", protocol: "http:", username: "", password: "", …}
> new URL('prefix/foo', 'http://xyz/')
URL {href: "http://xyz/prefix/foo", origin: "http://xyz", protocol: "http:", username: "", password: "", …}
> new URL('prefix/foo', 'http://xyz/foo')
URL {href: "http://xyz/prefix/foo", origin: "http://xyz", protocol: "http:", username: "", password: "", …}
> new URL('/prefix/foo', 'http://xyz/foo')
URL {href: "http://xyz/prefix/foo", origin: "http://xyz", protocol: "http:", username: "", password: "", …}
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
> new URL('prefix/foo', 'http://xyz/foo/bar')
< URL {href: "http://xyz/foo/prefix/foo", origin: "http://xyz", protocol: "http:", username: "", password: "", …}
> new URL('prefix/foo', 'http://xyz/foo/')
< URL {href: "http://xyz/foo/prefix/foo", origin: "http://xyz", protocol: "http:", username: "", password: "", …}
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.
Your tests are correct but nonexhaustive. If you’re on http://xyz/foo
and navigate to prefix/bar
, you will visit http://xyz/prefix/bar
.
You need to include a trailing slash to force an additional path
component:
> String(new URL("y/z", "http://foo/"))
"http://foo/y/z"
> String(new URL("y/z", "http://foo/bar/"))
"http://foo/bar/y/z"
> String(new URL("y/z", "http://foo/bar/baz/"))
"http://foo/bar/baz/y/z"
But maybeRelativePath already includes the configured pathPrefix
Yes, but the pathPrefix
is always "data"
, right? (Admittedly, this
naming is very misleading.)
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.
Ugh, you're right. It also turns out the pathPrefix naming situation was exacerbated when I suggested renaming dataDir to pathPrefix because I had managed to conflate dataDir and the --path_prefix flag:
#1563 (comment)
So really we should probably change everything here back to dataDir instead of pathPrefix. The router used to entirely ignore the path prefix (because it only operated on relative paths). With this change, the use of window.location.href will bake it into the final pathname as an absolute URL path (starting with /), but it's still not explicitly referenced.
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.
The router used to entirely ignore the path prefix (because it only
operated on relative paths). With this change, the use of
window.location.href will bake it into the final pathname as an
absolute URL path (starting with /), but it's still not explicitly
referenced.
Yes, exactly.
So really we should probably change everything here back to dataDir
instead of pathPrefix.
Fine with me. It’s worth noting that the value of this argument is in
fact always "data"
in public code, except for tests of the router
itself.
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.
Code looks better; thanks!
Some questions since I’ve never actually seen these demos working:
- What’s our procedure for testing the demo logic?
- How do we generate the data for these demos, and how do we run them?
- Can we run them with a
--path_prefix
, or are path prefixes and demo mode mutually exclusive? - Is any of this automated?
url.pathname = `${pathPrefix}/${normalizedPath}${ext}`; | ||
return url.pathname + url.search; | ||
url.pathname = `${dataDir}/${normalizedPath}${ext}`; | ||
return url.pathname; |
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.
Can this be safely replaced with
return `${dataDir}/${normalizedPath}${ext}`;
or do we need the special getter-setter semantics of URL.pathname
?
(For instance, the current code will always return a string that starts
with a slash, even if dataDir
does not. Is this intentional?)
If the latter, could you please leave a comment explaining why? It’s
certainly not obvious at a glance.
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.
For instance, the current code will always return a string that starts
with a slash, even if dataDir does not. Is this intentional?
Yup, this is intentional. demos data shims are served at /data/ regardless of --path_prefix so we want to form an absolute path.
About using getter-setter, I was implicitly using URL's ability to sanitize the path and form a legal URL. Because we had undone that part in prodURL, technically, there is no reason to do that for demo url. (moreover, because we do not render output of the router into source of an anchor tag or in dangerous way, I think the sanitization of URL we form is not strictly required so that's that)
If the latter, could you please leave a comment explaining why
Will do
if (params) url.search = params.toString(); | ||
return url.pathname + url.search; | ||
let relativePath = dataDir + route; | ||
if (params) { |
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 (not a big deal): if I read this correctly, the behavior is
different when params
is new URLSearchParams()
vs. undefined
: in
the former case you get URLs like /data/foo?
and /data/foo?bar=1&
,
while in the latter case you get /data/foo
and /data/foo?bar=1
. To
my knowledge, these should be handled identically by the backend, so I’m
fine with the code as is, even though it would IMHO be more sensible to
write as
function createProdDataPath(
dataDir: string,
route: string,
ext: string,
params: URLSearchParams = new URLSearchParams()
): string {
let relativePath = dataDir + route;
if (String(params)) {
const delimiter = route.includes("?") ? "&" : "?";
relativePath += delimiter + String(params);
}
return relativePath;
}
Thanks! I haven’t read the demo-mode code/tests very carefully, as we’re |
Hi @stephanwlee and @wchargin is this bug fix going to be included in a patch release of 1.12 (e.g. 1.12.2)? |
@falaki Sorry, this change did not make it into 1.12.1 and we do not have a plan to release another patch before 1.13. However, 1.13 should come out early next year. |
PR tensorflow#1512 introduced a bug where it formed a complete path without using existing pathname. Previously without forming the complete path, it was treated as a relative path from current window.location.pathname. We decided to revert back to the old behavior without using `new URL` which was not providing a clear benefit.
PR #1512 introduced a bug where it formed a complete path without using existing pathname. Previously without forming the complete path, it was treated as a relative path from current window.location.pathname. We decided to revert back to the old behavior without using `new URL` which was not providing a clear benefit.
@falaki We just released 1.12.2 patch with the fix. Thanks for being patient with the release. |
PR #1512 introduced a bug where it formed a complete path without using
existing pathname. Previously without forming the complete path, it was
treated as a relative path from current window.location.pathname.