diff --git a/src/backend/loadContext.js b/src/backend/loadContext.js index 9ed461d..1bae658 100644 --- a/src/backend/loadContext.js +++ b/src/backend/loadContext.js @@ -68,6 +68,7 @@ export class LoadContext { +_declarations = PluginLoaders.declarations; +_updateMirror = PluginLoaders.updateMirror; +_createPluginGraphs = PluginLoaders.createPluginGraphs; + +_createReferenceDetector = PluginLoaders.createReferenceDetector; +_contractPluginGraphs = PluginLoaders.contractPluginGraphs; +_overrideWeights = WeightedGraph.overrideWeights; +_computeTask = ComputeFunction.computeTask; @@ -98,11 +99,17 @@ export class LoadContext { this._options, project ); - const pluginGraphs = await this._createPluginGraphs( + const referenceDetector = await this._createReferenceDetector( this._pluginLoaders, this._options, cachedProject ); + const pluginGraphs = await this._createPluginGraphs( + this._pluginLoaders, + this._options, + cachedProject, + referenceDetector + ); const contractedGraph = await this._contractPluginGraphs( this._pluginLoaders, pluginGraphs diff --git a/src/backend/loadContext.test.js b/src/backend/loadContext.test.js index 45f707b..63b46db 100644 --- a/src/backend/loadContext.test.js +++ b/src/backend/loadContext.test.js @@ -9,6 +9,7 @@ import {LoadContext} from "./loadContext"; const fakes = { declarations: ({fake: "declarations"}: any), + referenceDetector: ({fake: "referenceDetector"}: any), pluginGraphs: ({fake: "pluginGraphs"}: any), contractedGraph: ({fake: "contractedGraph"}: any), weightedGraph: ({fake: "weightedGraph"}: any), @@ -44,6 +45,10 @@ const mockProxyMethods = ( .proxyMethod("updateMirror") .mockResolvedValueOnce({project, cache}), + createReferenceDetector: spyBuilder + .proxyMethod("createReferenceDetector") + .mockResolvedValueOnce(fakes.referenceDetector), + createPluginGraphs: spyBuilder .proxyMethod("createPluginGraphs") .mockResolvedValueOnce(fakes.pluginGraphs), @@ -98,6 +103,7 @@ describe("src/backend/loadContext", () => { // Methods _declarations: expect.anything(), _updateMirror: expect.anything(), + _createReferenceDetector: expect.anything(), _createPluginGraphs: expect.anything(), _contractPluginGraphs: expect.anything(), _overrideWeights: expect.anything(), @@ -140,11 +146,17 @@ describe("src/backend/loadContext", () => { expectedEnv, project ); - expect(spies.createPluginGraphs).toBeCalledWith( + expect(spies.createReferenceDetector).toBeCalledWith( loadContext._pluginLoaders, expectedEnv, cachedProject ); + expect(spies.createPluginGraphs).toBeCalledWith( + loadContext._pluginLoaders, + expectedEnv, + cachedProject, + fakes.referenceDetector + ); expect(spies.contractPluginGraphs).toBeCalledWith( loadContext._pluginLoaders, fakes.pluginGraphs diff --git a/src/backend/pluginLoaders.js b/src/backend/pluginLoaders.js index c1ba8cb..f66b70c 100644 --- a/src/backend/pluginLoaders.js +++ b/src/backend/pluginLoaders.js @@ -10,6 +10,10 @@ import {type GithubToken} from "../plugins/github/token"; import {type Loader as GithubLoader} from "../plugins/github/loader"; import {type Loader as IdentityLoader} from "../plugins/identity/loader"; import {type Loader as DiscourseLoader} from "../plugins/discourse/loader"; +import { + type ReferenceDetector, + CascadingReferenceDetector, +} from "../core/references"; /** * A type combining all known plugin Loader interfaces. @@ -97,13 +101,41 @@ export async function updateMirror( return {project, cache}; } +/** + * Creates a ReferenceDetector composing all plugin reference detectors + * requested by the project. + */ +export async function createReferenceDetector( + {github, discourse}: $Shape, + {githubToken}: GraphEnv, + {cache, project}: CachedProject +): Promise { + const refs: ReferenceDetector[] = []; + if (project.repoIds.length) { + // TODO: similar to create graph, rather not depend on the token (#1580). + if (!githubToken) { + throw new Error("Tried to load GitHub, but no GitHub token set"); + } + refs.push( + await github.referenceDetector(project.repoIds, githubToken, cache) + ); + } + if (project.discourseServer) { + refs.push( + await discourse.referenceDetector(project.discourseServer, cache) + ); + } + return new CascadingReferenceDetector(refs); +} + /** * Creates PluginGraphs containing all plugins requested by the Project. */ export async function createPluginGraphs( {github, discourse}: PluginLoaders, {githubToken}: GraphEnv, - {cache, project}: CachedProject + {cache, project}: CachedProject, + _unused_referenceDetector: ReferenceDetector ): Promise { const tasks: Promise[] = []; if (project.discourseServer) { diff --git a/src/backend/pluginLoaders.test.js b/src/backend/pluginLoaders.test.js index 4d5598e..0caf330 100644 --- a/src/backend/pluginLoaders.test.js +++ b/src/backend/pluginLoaders.test.js @@ -1,6 +1,10 @@ // @flow import {type CacheProvider} from "./cache"; +import { + type ReferenceDetector, + CascadingReferenceDetector, +} from "../core/references"; import * as WeightedGraph from "../core/weightedGraph"; import {node as graphNode} from "../core/graphTestUtil"; import {createProject} from "../core/project"; @@ -23,7 +27,9 @@ const mockGraphs = { const fakes = { githubDeclaration: ({fake: "githubDeclaration"}: any), + githubReferences: ({fake: "githubReferences"}: any), discourseDeclaration: ({fake: "discourseDeclaration"}: any), + discourseReferences: ({fake: "discourseReferences"}: any), identityDeclaration: ({fake: "identityDeclaration"}: any), }; @@ -31,15 +37,21 @@ const mockCacheProvider = (): CacheProvider => ({ database: jest.fn(), }); +const mockReferenceDetector = (): ReferenceDetector => ({ + addressFromUrl: jest.fn(), +}); + const mockPluginLoaders = () => ({ github: { declaration: jest.fn().mockReturnValue(fakes.githubDeclaration), updateMirror: jest.fn(), + referenceDetector: jest.fn().mockResolvedValue(fakes.githubReferences), createGraph: jest.fn().mockResolvedValue(mockGraphs.github), }, discourse: { declaration: jest.fn().mockReturnValue(fakes.discourseDeclaration), updateMirror: jest.fn(), + referenceDetector: jest.fn().mockResolvedValue(fakes.discourseReferences), createGraph: jest.fn().mockResolvedValue(mockGraphs.discourse), }, identity: { @@ -185,6 +197,7 @@ describe("src/backend/pluginLoaders", () => { describe("createPluginGraphs", () => { it("should create discourse graph", async () => { // Given + const references = mockReferenceDetector(); const loaders = mockPluginLoaders(); const cache = mockCacheProvider(); const githubToken = null; @@ -198,7 +211,8 @@ describe("src/backend/pluginLoaders", () => { const pluginGraphs = await PluginLoaders.createPluginGraphs( loaders, {githubToken}, - cachedProject + cachedProject, + references ); // Then @@ -216,6 +230,7 @@ describe("src/backend/pluginLoaders", () => { it("fail when missing GithubToken", async () => { // Given + const references = mockReferenceDetector(); const loaders = mockPluginLoaders(); const cache = mockCacheProvider(); const githubToken = null; @@ -229,7 +244,8 @@ describe("src/backend/pluginLoaders", () => { const p = PluginLoaders.createPluginGraphs( loaders, {githubToken}, - cachedProject + cachedProject, + references ); // Then @@ -240,6 +256,7 @@ describe("src/backend/pluginLoaders", () => { it("should create github graph", async () => { // Given + const references = mockReferenceDetector(); const loaders = mockPluginLoaders(); const cache = mockCacheProvider(); const githubToken = exampleGithubToken; @@ -253,7 +270,8 @@ describe("src/backend/pluginLoaders", () => { const pluginGraphs = await PluginLoaders.createPluginGraphs( loaders, {githubToken}, - cachedProject + cachedProject, + references ); // Then @@ -271,6 +289,35 @@ describe("src/backend/pluginLoaders", () => { }); }); + describe("createReferenceDetector", () => { + it("should create a CascadingReferenceDetector", async () => { + // Given + const loaders = mockPluginLoaders(); + const cache = mockCacheProvider(); + const githubToken = exampleGithubToken; + const project = createProject({ + id: "has-github-and-discourse", + discourseServer: {serverUrl: "http://foo.bar"}, + repoIds: [exampleRepoId], + }); + const cachedProject = ({project, cache}: any); + + // When + const references = await PluginLoaders.createReferenceDetector( + loaders, + {githubToken}, + cachedProject + ); + + // Then + expect(references).toBeInstanceOf(CascadingReferenceDetector); + expect(((references: any): CascadingReferenceDetector).refs).toEqual([ + fakes.githubReferences, + fakes.discourseReferences, + ]); + }); + }); + describe("contractPluginGraphs", () => { it("should only merge graphs when no identities are defined", async () => { // Given diff --git a/src/plugins/discourse/loader.js b/src/plugins/discourse/loader.js index cb5fa33..13a3f11 100644 --- a/src/plugins/discourse/loader.js +++ b/src/plugins/discourse/loader.js @@ -4,11 +4,13 @@ import base64url from "base64url"; import {TaskReporter} from "../../util/taskReporter"; import {type CacheProvider} from "../../backend/cache"; import {type WeightedGraph} from "../../core/weightedGraph"; +import {type ReferenceDetector} from "../../core/references"; import {type PluginDeclaration} from "../../analysis/pluginDeclaration"; import {type DiscourseServer} from "./server"; import {Mirror} from "./mirror"; import {SqliteMirrorRepository} from "./mirrorRepository"; import {weightsForDeclaration} from "../../analysis/pluginDeclaration"; +import {DiscourseReferenceDetector} from "./referenceDetector"; import {createGraph as _createGraph} from "./createGraph"; import {declaration} from "./declaration"; import {Fetcher} from "./fetch"; @@ -20,6 +22,10 @@ export interface Loader { cache: CacheProvider, reporter: TaskReporter ): Promise; + referenceDetector( + server: DiscourseServer, + cache: CacheProvider + ): Promise; createGraph( server: DiscourseServer, cache: CacheProvider @@ -29,6 +35,7 @@ export interface Loader { export default ({ declaration: () => declaration, updateMirror, + referenceDetector, createGraph, }: Loader); @@ -44,6 +51,14 @@ export async function updateMirror( await mirror.update(reporter); } +export async function referenceDetector( + {serverUrl}: DiscourseServer, + cache: CacheProvider +): Promise { + const repo = await repository(cache, serverUrl); + return new DiscourseReferenceDetector(repo); +} + export async function createGraph( {serverUrl}: DiscourseServer, cache: CacheProvider diff --git a/src/plugins/github/loader.js b/src/plugins/github/loader.js index 382931a..c858569 100644 --- a/src/plugins/github/loader.js +++ b/src/plugins/github/loader.js @@ -3,6 +3,7 @@ import {TaskReporter} from "../../util/taskReporter"; import {type CacheProvider} from "../../backend/cache"; import {type WeightedGraph} from "../../core/weightedGraph"; +import {type ReferenceDetector} from "../../core/references"; import {type PluginDeclaration} from "../../analysis/pluginDeclaration"; import {type GithubToken} from "./token"; import {Graph} from "../../core/graph"; @@ -15,6 +16,7 @@ import { default as fetchGithubRepo, fetchGithubRepoFromCache, } from "./fetchGithubRepo"; +import {fromRelationalViews as referenceDetectorFromRelationalViews} from "./referenceDetector"; export interface Loader { declaration(): PluginDeclaration; @@ -24,6 +26,11 @@ export interface Loader { cache: CacheProvider, reporter: TaskReporter ): Promise; + referenceDetector( + repoIds: $ReadOnlyArray, + token: GithubToken, + cache: CacheProvider + ): Promise; createGraph( repoIds: $ReadOnlyArray, token: GithubToken, @@ -33,6 +40,7 @@ export interface Loader { export default ({ declaration: () => declaration, + referenceDetector, updateMirror, createGraph, }: Loader); @@ -54,6 +62,21 @@ export async function updateMirror( } } +export async function referenceDetector( + repoIds: $ReadOnlyArray, + token: GithubToken, + cache: CacheProvider +): Promise { + const rvs = []; + for (const repoId of repoIds) { + const repo = await fetchGithubRepoFromCache(repoId, {token, cache}); + const rv = new RelationalView(); + rv.addRepository(repo); + rvs.push(rv); + } + return referenceDetectorFromRelationalViews(rvs); +} + export async function createGraph( repoIds: $ReadOnlyArray, token: GithubToken,