Skip to content
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

Create External Plugin that reads from disk or url #3114

Merged
merged 4 commits into from
Aug 19, 2021
Merged

Conversation

blueridger
Copy link
Member

@blueridger blueridger commented Aug 11, 2021

Description

This creates a new special plugin: the External plugin. This is a dynamic plugin that allows 3rd parties to rapidly pipe data into an instance.

The External plugin can be used multiple times, because it simply uses the PluginId pattern "external/X" where X can be any name (but preferably an agreed upon name between the 3rd-party software and the instance maintainer).

The External plugin loads its graph and optionally its declaration and identityProposals from either:

  1. the plugin config folder on disk
    • To use this method, simply place the files into the config/plugins/external/X folder.
  2. a base url that statically serves the files
    • To use this method, simply serve the files statically with cross-origin enabled in the same directory, and add a config.json file in the instance's config/plugins/external/X folder with form: { "graphUrl": "https://www.myhost.com/path/to/file/graph.json" }

Supported files for either method are:

  1. graph.json/graph.json.gzip (required) - works whether or not it is compressed using our library
  2. declaration.json (optional) - if omitted, a default declaration with minimal node/edge types is used, but also graphs don't have to adhere to the declaration if they don't desire to be configured using our Weight Configuration UI.
  3. identityProposals.json (optional) - if omitted, no identities are proposed

Test Plan

local config graph

  1. checkout gh-pages branch of our cred repo
  2. copy output/graphs/sourcecred/github/graph.json.gzip to config/plugins/external/github2/graph.json.gzip
  3. In sourcecred.json, remove "sourcecred/initiatives" and replace "sourcecred/github" with "external/github2"
  4. scdev go --no-load
  5. verify output/graphs/sourcecred/github2/graph.json.gzip exists

also ended up testing with a non-zipped graph.json

remote url graph

  1. add config/plugins/external/github2/config.json containing {"baseUrl": "https://raw.githubusercontent.com/sourcecred/cred/gh-pages/output/graphs/sourcecred/github/"}
  2. delete existing github2 graphs from config and output directories.
  3. scdev graph
  4. verify output/graphs/sourcecred/github2/graph.json.gzip exists

@META-DREAMER
Copy link
Collaborator

Excited to see this moving forward! Initial thoughts:

Are users of external plugins expected to generate their own graphs outside of the sourcecred runtime when the other plugin graphs are generated? I was imagining the external plugin wouldn't be a static JSON file but instead a JS file that default exports a class representing the plugin which then gets executed at runtime (e.g. by the load and graph commands). That way external plugin authors can implement arbitrary logic to generate the plugin declaration / graph object and don't need to manage a separate process to update the graph.json file.

@blueridger
Copy link
Member Author

blueridger commented Aug 11, 2021

So really, there is nothing stopping those types of external plugins from being developed right now, they would just have to be registered in our bundledPlugins/bundledDeclarations files. The lift to allow an instance maintainer to import unregistered plugins is likely pretty simple, and I can take a look at knocking that out too this week.

This PR introduces some interesting possibilities:

  1. The graph is the easiest plugin requirement to build using our library, and allowing the declaration and identityProposals to be wholly ignored can bump dev speed, especially with just a small amount of documentation / examples added.
  2. One-and-done static graphs generated using an observable notebook or the like, which could be powerful for the airdrop use-case.
  3. Language agnostic capability. Since the only real requirement is JSON, it opens the door for a determined Python etc dev to forego using our library and generate graphs in their native language, even potentially for 3rd party development of helper libraries in other languages.
  4. Support for remote endpoints allows server devs to expose less data by doing graph calculation on-server and allows compatibility with distributed storage of graphs like on IPFS.
  5. This enables "graph sharing" across instances without needing other people's private keys, especially if we add identityProposals and declarations to the graph CLI output. You just point to their statically hosted raw files on github.

@META-DREAMER
Copy link
Collaborator

@blueridger Makes sense, yea I think this is def a worthwhile primitive to have. I didn't think about the support for remote endpoints, that's actually very powerful and helps a ton for inter-org value graphs.

I think the key then becomes to make graph construction as easy as possible via our NPM package. I would say improving the dev experience around that / documenting an example implementation would be more useful than the runtime plugins.

META-DREAMER
META-DREAMER previously approved these changes Aug 12, 2021
@blueridger
Copy link
Member Author

Hadn't manual tested the remote case yet. Found and fixed a bug and updated test plan. Seeking reapproval @HammadJ

@blueridger blueridger dismissed META-DREAMER’s stale review August 13, 2021 18:10

significant changes made

@blueridger
Copy link
Member Author

Realized, this plugin would be significantly more useful in the library if it could be constructed with a configuration. And also that the 3 files shouldn't need to have the same url root in the remote case. Made these changes. Seeking reapproval @HammadJ

Copy link
Member

@topocount topocount left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm so excited you're exploring this! I've found some nits, but otherwise I'm feeling quite good about this.

One question I had: What are the implications of moving plugin declarations to async land? I knew it would have to happen eventually, but do we have to consider adding extra prompts to accommodate longer wait times here?

Comment on lines +17 to +21
export async function getPluginDeclaration(
pluginId: PluginId,
storage: DataStorage
): Promise<PluginDeclaration> {
const mapping = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I appreciate this refactoring

pluginId,
storage: new DiskStorage(process.cwd()),
});
return null;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just curious: why not an implicit undefined return here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will change, probably just a years-old habit from the 1 year in college I spent writing java lol.

fromJSON as declarationFromJSON,
} from "../../analysis/pluginDeclaration";
import {join as pathJoin} from "path";
import {type TaskReporter} from "../../util/taskReporter";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

type should outside the curly brackets here, if only types are in the import.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

accepted all

import {loadJson, loadJsonWithDefault} from "../../util/storage";
import {merge as mergeWeights} from "../../core/weights";
import {weightsForDeclaration} from "../../analysis/pluginDeclaration";
import {type PluginId} from "../../api/pluginId";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

identityProposalsParser,
} from "../../core/ledger/identityProposal";
import * as Combo from "../../util/combo";
import {type DataStorage} from "../../core/storage";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

Comment on lines +42 to +46
type ExternalPluginConfig = {|
+graphUrl: string,
+declarationUrl?: string,
+identityProposalsUrl?: string,
|};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to see a URL parser created for these, but that can probably be a separate issue, or even a good first issue. I thought we had one, but I can't find it. It'd be really good to "fail fast" on something like this before any network requests are made.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so true. create an issue?

@blueridger blueridger linked an issue Aug 18, 2021 that may be closed by this pull request
@blueridger
Copy link
Member Author

What are the implications of moving plugin declarations to async land? I knew it would have to happen eventually, but do we have to consider adding extra prompts to accommodate longer wait times here?

Fair question. I don't anticipate it being a problem, since we actually don't parallelize promises very much in our code, and even over network, it should be a very short fetch time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Create External Plugin that reads from Disk or URL
3 participants