Load repo registry even when hosted at non-root (#678)

Summary:
This commit is the next step in #643. It makes the `RepositorySelect`
robust to being hosted at arbitrary gateways by accepting `Assets` and
resolving the repository registry API route appropriately.

Test Plan:
Unit tests modified, but it would be good to also manually test this.
Run `./scripts/build_static_site.sh` to build the site to, say,
`/tmp/gateway/`. Then spin up a static HTTP server serving `/tmp/` and
navigate to `/gateway/` in the browser. Note that you can navigate
around the application and load the repository registry on the prototype
without any console warnings or errors, although you cannot yet load
actual graph data.

wchargin-branch: use-assets-in-RepositorySelect
This commit is contained in:
William Chargin 2018-08-15 17:12:24 -07:00 committed by GitHub
parent 19af47a664
commit b252a6b5de
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 64 additions and 14 deletions

View File

@ -84,6 +84,7 @@ export function createApp(
<div style={{maxWidth: 900, margin: "0 auto", padding: "0 10px"}}>
<div style={{marginBottom: 10}}>
<RepositorySelect
assets={this.props.assets}
localStore={localStore}
onChange={(repo) => this.stateTransitionMachine.setRepo(repo)}
/>

View File

@ -6,6 +6,7 @@ import deepEqual from "lodash.isequal";
import * as NullUtil from "../../util/null";
import type {LocalStore} from "../localStore";
import type {Assets} from "../assets";
import {fromJSON, REPO_REGISTRY_API} from "./repoRegistry";
import {type Repo, stringToRepo, repoToString} from "../../core/repo";
@ -22,6 +23,7 @@ export type Status =
| {|+type: "FAILURE"|};
type Props = {|
+assets: Assets,
+onChange: (x: Repo) => void,
+localStore: LocalStore,
|};
@ -35,7 +37,8 @@ export default class RepositorySelect extends React.Component<Props, State> {
}
componentDidMount() {
loadStatus(this.props.localStore).then((status) => {
const {assets, localStore} = this.props;
loadStatus(assets, localStore).then((status) => {
this.setState({status});
if (status.type === "VALID") {
this.props.onChange(status.selectedRepo);
@ -67,9 +70,12 @@ export default class RepositorySelect extends React.Component<Props, State> {
}
}
export async function loadStatus(localStore: LocalStore): Promise<Status> {
export async function loadStatus(
assets: Assets,
localStore: LocalStore
): Promise<Status> {
try {
const response = await fetch(REPO_REGISTRY_API);
const response = await fetch(assets.resolve(REPO_REGISTRY_API));
if (response.status === 404) {
return {type: "NO_REPOS"};
}

View File

@ -11,6 +11,7 @@ import RepositorySelect, {
type Status,
REPO_KEY,
} from "./RepositorySelect";
import {Assets} from "../assets";
import {toJSON, type RepoRegistry, REPO_REGISTRY_API} from "./repoRegistry";
import {makeRepo} from "../../core/repo";
@ -106,14 +107,15 @@ describe("app/credExplorer/RepositorySelect", () => {
});
describe("loadStatus", () => {
const assets = new Assets("/my/gateway/");
function expectLoadValidStatus(
localStore,
expectedAvailableRepos,
expectedSelectedRepo
) {
const result = loadStatus(localStore);
const result = loadStatus(assets, localStore);
expect(fetch).toHaveBeenCalledTimes(1);
expect(fetch).toHaveBeenCalledWith(REPO_REGISTRY_API);
expect(fetch).toHaveBeenCalledWith("/my/gateway" + REPO_REGISTRY_API);
expect.assertions(7);
return result.then((status) => {
expect(status.type).toBe("VALID");
@ -142,7 +144,7 @@ describe("app/credExplorer/RepositorySelect", () => {
it("returns FAILURE on invalid fetch response", () => {
fetch.mockResponseOnce(JSON.stringify(["hello"]));
expect.assertions(4);
return loadStatus(testLocalStore()).then((status) => {
return loadStatus(assets, testLocalStore()).then((status) => {
expect(status).toEqual({type: "FAILURE"});
expect(console.error).toHaveBeenCalledTimes(1);
// $ExpectFlowError
@ -152,7 +154,7 @@ describe("app/credExplorer/RepositorySelect", () => {
it("returns FAILURE on fetch failure", () => {
fetch.mockReject(new Error("some failure"));
expect.assertions(4);
return loadStatus(testLocalStore()).then((status) => {
return loadStatus(assets, testLocalStore()).then((status) => {
expect(status).toEqual({type: "FAILURE"});
expect(console.error).toHaveBeenCalledTimes(1);
// $ExpectFlowError
@ -162,7 +164,7 @@ describe("app/credExplorer/RepositorySelect", () => {
it("returns NO_REPOS on fetch 404", () => {
fetch.mockResponseOnce("irrelevant", {status: 404});
expect.assertions(3);
return loadStatus(testLocalStore()).then((status) => {
return loadStatus(assets, testLocalStore()).then((status) => {
expect(status).toEqual({type: "NO_REPOS"});
});
});
@ -264,10 +266,31 @@ describe("app/credExplorer/RepositorySelect", () => {
});
describe("RepositorySelect", () => {
const assets = new Assets("/my/gateway/");
it("calls `loadStatus` with the proper assets", () => {
mockRegistry([makeRepo("irrelevant", "unused")]);
shallow(
<RepositorySelect
assets={assets}
onChange={jest.fn()}
localStore={testLocalStore()}
/>
);
// A bit of overlap with tests for `loadStatus` directly---it'd be
// nicer to spy on `loadStatus`, but that's at module top level,
// so `RepositorySelect` closes over it directly.
expect(fetch).toHaveBeenCalledWith("/my/gateway" + REPO_REGISTRY_API);
});
it("initially renders a LocalStoreRepositorySelect with status LOADING", () => {
mockRegistry([makeRepo("irrelevant", "unused")]);
const e = shallow(
<RepositorySelect onChange={jest.fn()} localStore={testLocalStore()} />
<RepositorySelect
assets={assets}
onChange={jest.fn()}
localStore={testLocalStore()}
/>
);
const child = e.find(LocalStoreRepositorySelect);
const status = child.props().status;
@ -291,7 +314,11 @@ describe("app/credExplorer/RepositorySelect", () => {
const selectedRepo = makeRepo("foo", "bar");
mockRegistry([selectedRepo]);
const e = shallow(
<RepositorySelect onChange={onChange} localStore={testLocalStore()} />
<RepositorySelect
assets={assets}
onChange={onChange}
localStore={testLocalStore()}
/>
);
await waitForUpdate(e);
const childStatus = e.props().status;
@ -308,7 +335,11 @@ describe("app/credExplorer/RepositorySelect", () => {
const repo = makeRepo("foo", "bar");
mockRegistry([repo]);
const e = shallow(
<RepositorySelect onChange={onChange} localStore={testLocalStore()} />
<RepositorySelect
assets={assets}
onChange={onChange}
localStore={testLocalStore()}
/>
);
await waitForUpdate(e);
expect(onChange).toHaveBeenCalledTimes(1);
@ -320,7 +351,11 @@ describe("app/credExplorer/RepositorySelect", () => {
fetch.mockReject(new Error("something bad"));
const e = shallow(
<RepositorySelect onChange={onChange} localStore={testLocalStore()} />
<RepositorySelect
assets={assets}
onChange={onChange}
localStore={testLocalStore()}
/>
);
await waitForUpdate(e);
expect(onChange).toHaveBeenCalledTimes(0);
@ -334,7 +369,11 @@ describe("app/credExplorer/RepositorySelect", () => {
const repo = makeRepo("foo", "bar");
mockRegistry([repo]);
const e = mount(
<RepositorySelect onChange={onChange} localStore={testLocalStore()} />
<RepositorySelect
assets={assets}
onChange={onChange}
localStore={testLocalStore()}
/>
);
const child = e.find(PureRepositorySelect);
child.props().onChange(repo);
@ -347,7 +386,11 @@ describe("app/credExplorer/RepositorySelect", () => {
const repos = [makeRepo("foo", "bar"), makeRepo("z", "a")];
mockRegistry(repos);
const e = mount(
<RepositorySelect onChange={onChange} localStore={testLocalStore()} />
<RepositorySelect
assets={assets}
onChange={onChange}
localStore={testLocalStore()}
/>
);
await waitForUpdate(e);
const child = e.find(PureRepositorySelect);