From 4406c96c956ad9c575b1b80cf55ebf7a23c042e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dandelion=20Man=C3=A9?= Date: Fri, 27 Jul 2018 21:30:50 -0700 Subject: [PATCH] Create a Repo type and use throughout the project (#555) Our data model orients on getting repos from GitHub, which are alternatively represented as strings like "sourcecred/sourcecred", or pairs of variables representing the owner and name, or objects with owner and name properties. We also have a few different implementations of repo validation, which are not applied consistently. This commit changes all that. We now have a consistent Repo type which is an object containing a string owner and string name. Thanks to a clever suggestion by @wchargin, it is implemented as an opaque subtype of an object containing those properties, so that the only valid way to construct a Repo typed object is to use one of the functions that consistently validates the repo. As a fly-by fix, I noticed that there were some functions in the GitHub query generation that didn't properly mark arguments as readOnly. I've fixed these. Test plan: No externally-observable behavior changes (except insofar as there is a slight change in variable names in the GitHub graphql query, which has also resulted in a snapshot diff). `yarn travis --full` passes. `git grep repoOwner` presents no hits. --- src/app/credExplorer/App.js | 14 ++--- src/app/credExplorer/PagerankTable.test.js | 8 +-- src/app/credExplorer/RepositorySelect.js | 32 ++--------- src/app/credExplorer/RepositorySelect.test.js | 57 ++++++++----------- src/app/credExplorer/repoRegistry.js | 2 +- src/app/credExplorer/repoRegistry.test.js | 29 +++++----- src/app/pluginAdapter.js | 3 +- src/cli/commands/load.js | 38 ++++++------- src/core/repo.js | 29 ++++++++++ src/core/repo.test.js | 51 +++++++++++++++++ .../__snapshots__/queries.test.js.snap | 14 ++--- src/graphql/queries.test.js | 4 +- src/plugins/git/cloneAndLoadRepository.js | 14 ++--- src/plugins/git/loadGitData.js | 9 +-- src/plugins/git/pluginAdapter.js | 8 +-- .../github/__snapshots__/graphql.test.js.snap | 4 +- .../github/bin/fetchAndPrintGithubRepo.js | 8 ++- src/plugins/github/fetchGithubRepo.js | 21 ++----- src/plugins/github/graphql.js | 15 ++--- src/plugins/github/graphql.test.js | 3 +- src/plugins/github/loadGithubData.js | 10 +--- src/plugins/github/pluginAdapter.js | 8 +-- 22 files changed, 201 insertions(+), 180 deletions(-) create mode 100644 src/core/repo.js create mode 100644 src/core/repo.test.js diff --git a/src/app/credExplorer/App.js b/src/app/credExplorer/App.js index c5e20b9..7d31759 100644 --- a/src/app/credExplorer/App.js +++ b/src/app/credExplorer/App.js @@ -17,7 +17,7 @@ import {type EdgeEvaluator} from "../../core/attribution/pagerank"; import {WeightConfig} from "./WeightConfig"; import type {PagerankNodeDecomposition} from "../../core/attribution/pagerankNodeDecomposition"; import RepositorySelect from "./RepositorySelect"; -import type {Repo} from "./repoRegistry"; +import type {Repo} from "../../core/repo"; import * as NullUtil from "../../util/null"; @@ -135,18 +135,16 @@ export class App extends React.Component { } const githubPromise = new GithubAdapter() - .load(selectedRepo.owner, selectedRepo.name) + .load(selectedRepo) .then((adapter) => { const graph = adapter.graph(); return {graph, adapter}; }); - const gitPromise = new GitAdapter() - .load(selectedRepo.owner, selectedRepo.name) - .then((adapter) => { - const graph = adapter.graph(); - return {graph, adapter}; - }); + const gitPromise = new GitAdapter().load(selectedRepo).then((adapter) => { + const graph = adapter.graph(); + return {graph, adapter}; + }); Promise.all([gitPromise, githubPromise]).then((graphsAndAdapters) => { const graph = Graph.merge(graphsAndAdapters.map((x) => x.graph)); diff --git a/src/app/credExplorer/PagerankTable.test.js b/src/app/credExplorer/PagerankTable.test.js index a936007..1b8091f 100644 --- a/src/app/credExplorer/PagerankTable.test.js +++ b/src/app/credExplorer/PagerankTable.test.js @@ -81,7 +81,7 @@ async function example() { backwardName: "is fooed by", }, ], - load: (_unused_repoOwner, _unused_repoName) => { + load: (_unused_repo) => { throw new Error("unused"); }, }), @@ -109,7 +109,7 @@ async function example() { backwardName: "is barred by", }, ], - load: (_unused_repoOwner, _unused_repoName) => { + load: (_unused_repo) => { throw new Error("unused"); }, }), @@ -125,7 +125,7 @@ async function example() { edgePrefix: () => EdgeAddress.fromParts(["xox"]), nodeTypes: () => [], edgeTypes: () => [], - load: (_unused_repoOwner, _unused_repoName) => { + load: (_unused_repo) => { throw new Error("unused"); }, }), @@ -141,7 +141,7 @@ async function example() { nodeTypes: () => [], edgeTypes: () => [], name: () => "unused", - load: (_unused_repoOwner, _unused_repoName) => { + load: (_unused_repo) => { throw new Error("unused"); }, }), diff --git a/src/app/credExplorer/RepositorySelect.js b/src/app/credExplorer/RepositorySelect.js index 1f1ed92..91ec623 100644 --- a/src/app/credExplorer/RepositorySelect.js +++ b/src/app/credExplorer/RepositorySelect.js @@ -7,7 +7,8 @@ import deepEqual from "lodash.isequal"; import * as NullUtil from "../../util/null"; import type {LocalStore} from "../localStore"; -import {type Repo, fromJSON, REPO_REGISTRY_API} from "./repoRegistry"; +import {fromJSON, REPO_REGISTRY_API} from "./repoRegistry"; +import {type Repo, stringToRepo, repoToString} from "../../core/repo"; export const REPO_KEY = "selectedRepository"; export type Status = @@ -66,26 +67,6 @@ export default class RepositorySelect extends React.Component { } } -function validateRepo(repo: Repo) { - const validRe = /^[A-Za-z0-9_-]+$/; - if (!repo.owner.match(validRe)) { - throw new Error(`Invalid repository owner: ${JSON.stringify(repo.owner)}`); - } - if (!repo.name.match(validRe)) { - throw new Error(`Invalid repository name: ${JSON.stringify(repo.name)}`); - } -} - -function repoStringToRepo(x: string): Repo { - const pieces = x.split("/"); - if (pieces.length !== 2) { - throw new Error(`Invalid repo string: ${x}`); - } - const repo = {owner: pieces[0], name: pieces[1]}; - validateRepo(repo); - return repo; -} - export async function loadStatus(localStore: LocalStore): Promise { try { const response = await fetch(REPO_REGISTRY_API); @@ -147,15 +128,14 @@ export class PureRepositorySelect extends React.PureComponent< Please choose a repository to inspect:{" "} {selectedRepo != null && (