docs: graphql, schema comments
This commit is contained in:
parent
db85d80ddb
commit
9a3b7c9d94
45
docs/graphql.md
Normal file
45
docs/graphql.md
Normal file
|
@ -0,0 +1,45 @@
|
|||
Metaforecast website is implemented on top of GraphQL API.
|
||||
|
||||
Tech stack:
|
||||
|
||||
- [Pothos](https://pothos-graphql.dev/) on the backend for implementing our graphql server
|
||||
- [urql](https://formidable.com/open-source/urql/) on the frontend
|
||||
- [GraphQL Code Generator](https://www.graphql-code-generator.com/) for schema generation, queries generation and schema introspection
|
||||
|
||||
Rationale for this stack can be found on [#32](https://github.com/quantified-uncertainty/metaforecast/issues/32) and [#21](https://github.com/quantified-uncertainty/metaforecast/issues/32) in comments.
|
||||
|
||||
# Code layout
|
||||
|
||||
List of all files used for graphql:
|
||||
|
||||
- [schema.graphql](../schema.graphql), GraphQL schema generated by graphql-code-generator.
|
||||
- [codegen.yml](../codegen.yml), graphql-code-generator [config](https://www.graphql-code-generator.com/docs/config-reference/codegen-config)
|
||||
- [graphql.config.yaml](../graphql.config.yaml), [GraphQL Config](https://www.graphql-config.com/) for better VS Code integration ([VS Code GraphQL extension](https://marketplace.visualstudio.com/items?itemName=GraphQL.vscode-graphql) type-checks graphql files thanks to it)
|
||||
- [src/pages/api/graphql.ts](../src/pages/api/graphql.ts) implements the GraphQL HTTP endpoint and GraphQL playground
|
||||
- [src/web/urql.ts](../src/web/urql.ts) contains some helper functions.
|
||||
|
||||
[src/graphql/](../src/graphql) dir contains GraphQL server code.
|
||||
|
||||
`*.graphql` files in `src/web/**` contain GraphQL fragments, queries and mutations which are used on the frontend.
|
||||
|
||||
`graphql-code-generator` converts those into `*.generated.ts` files which can be imported from the React components.
|
||||
|
||||
# Recipes
|
||||
|
||||
**I want to check out what Metaforecast's GraphQL API is capable of**
|
||||
|
||||
Go to [/api/graphql](https://metaforecast.org/api/graphql) and do some queries by hand. Note the "Docs" link in the top right corner.
|
||||
|
||||
**I want to add a new query/mutation to our schema**
|
||||
|
||||
Read the [Pothos](https://pothos-graphql.dev/) docs to learn how to implement new objects and fields. Add the new code somewhere in `src/graphql/schema/`.
|
||||
|
||||
**I want to display a new piece of data on the metaforecast website**
|
||||
|
||||
- add a query in a nearest `queries.graphql` file (if there isn't one, create it)
|
||||
- run `gql-gen` (or keep it running with `gql-gen -w`) to get a `graphql.generated.ts` file
|
||||
- use [useQuery](https://formidable.com/open-source/urql/docs/basics/react-preact/#queries) with your new `MyQueryDocument` which you can import from the `graphql.generated.ts` file
|
||||
|
||||
If you need SSR, you'll also need to do **the same** query with **the same variables** from your page's `getServerSideProps` function. Check out any existing page which calls `ssrUrql`.
|
||||
|
||||
(You might not need to use `useQuery` in your components if you're doing SSR and the page content is not dynamic, just pass the data from `getServerSideProps` in `props` and avoid GraphQL on the client side).
|
|
@ -8,11 +8,18 @@ import { QuestionObj } from "./questions";
|
|||
const DashboardObj = builder.objectRef<Dashboard>("Dashboard").implement({
|
||||
fields: (t) => ({
|
||||
id: t.exposeID("id"),
|
||||
title: t.exposeString("title"),
|
||||
description: t.exposeString("description"),
|
||||
creator: t.exposeString("creator"),
|
||||
title: t.exposeString("title", {
|
||||
description: "The title of the dashboard",
|
||||
}),
|
||||
description: t.exposeString("description", {
|
||||
description: "The longer description of the dashboard",
|
||||
}),
|
||||
creator: t.exposeString("creator", {
|
||||
description: 'The creator of the dashboard, e.g. "Peter Parker"',
|
||||
}),
|
||||
questions: t.field({
|
||||
type: [QuestionObj],
|
||||
description: "The list of questions on the dashboard",
|
||||
resolve: async (parent) => {
|
||||
return await prisma.question.findMany({
|
||||
where: {
|
||||
|
@ -29,6 +36,7 @@ const DashboardObj = builder.objectRef<Dashboard>("Dashboard").implement({
|
|||
builder.queryField("dashboard", (t) =>
|
||||
t.field({
|
||||
type: DashboardObj,
|
||||
description: "Look up a single dashboard by its id",
|
||||
args: {
|
||||
id: t.arg({ type: "ID", required: true }),
|
||||
},
|
||||
|
@ -55,16 +63,25 @@ const CreateDashboardResult = builder
|
|||
|
||||
const CreateDashboardInput = builder.inputType("CreateDashboardInput", {
|
||||
fields: (t) => ({
|
||||
title: t.string({ required: true }),
|
||||
description: t.string(),
|
||||
creator: t.string(),
|
||||
ids: t.idList({ required: true }),
|
||||
title: t.string({
|
||||
required: true,
|
||||
description: "The title of the dashboard",
|
||||
}),
|
||||
description: t.string({
|
||||
description: "The longer description of the dashboard",
|
||||
}),
|
||||
creator: t.string({
|
||||
description: 'The creator of the dashboard, e.g. "Peter Parker"',
|
||||
}),
|
||||
ids: t.idList({ required: true, description: "List of question ids" }),
|
||||
}),
|
||||
});
|
||||
|
||||
builder.mutationField("createDashboard", (t) =>
|
||||
t.field({
|
||||
type: CreateDashboardResult,
|
||||
description:
|
||||
"Create a new dashboard; if the dashboard with given ids already exists then it will be returned instead.",
|
||||
args: {
|
||||
input: t.arg({ type: CreateDashboardInput, required: true }),
|
||||
},
|
||||
|
|
|
@ -8,6 +8,7 @@ import { QuestionObj } from "./questions";
|
|||
builder.queryField("frontpage", (t) =>
|
||||
t.field({
|
||||
type: [QuestionObj],
|
||||
description: "Get a list of questions that are currently on the frontpage",
|
||||
resolve: async () => {
|
||||
const legacyQuestions = await getFrontpage();
|
||||
const ids = legacyQuestions.map((q) => q.id);
|
||||
|
|
|
@ -3,9 +3,15 @@ import { platforms, QualityIndicators } from "../../backend/platforms";
|
|||
import { builder } from "../builder";
|
||||
|
||||
const PlatformObj = builder.objectRef<string>("Platform").implement({
|
||||
description: "Platform supported by metaforecast",
|
||||
description: "Forecasting platform supported by Metaforecast",
|
||||
fields: (t) => ({
|
||||
id: t.id({
|
||||
description: 'Short unique platform name, e.g. "xrisk"',
|
||||
resolve: (x) => x,
|
||||
}),
|
||||
label: t.string({
|
||||
description:
|
||||
'Platform name for displaying on frontend etc., e.g. "X-risk estimates"',
|
||||
resolve: (platformName) => {
|
||||
if (platformName === "metaforecast") {
|
||||
return "Metaforecast";
|
||||
|
@ -21,9 +27,6 @@ const PlatformObj = builder.objectRef<string>("Platform").implement({
|
|||
return platform.label;
|
||||
},
|
||||
}),
|
||||
id: t.id({
|
||||
resolve: (x) => x,
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
|
@ -32,7 +35,9 @@ export const QualityIndicatorsObj = builder
|
|||
.implement({
|
||||
description: "Various indicators of the question's quality",
|
||||
fields: (t) => ({
|
||||
stars: t.exposeInt("stars"),
|
||||
stars: t.exposeInt("stars", {
|
||||
description: "0 to 5",
|
||||
}),
|
||||
numForecasts: t.int({
|
||||
nullable: true,
|
||||
resolve: (parent) =>
|
||||
|
@ -48,19 +53,28 @@ export const ProbabilityOptionObj = builder
|
|||
.implement({
|
||||
fields: (t) => ({
|
||||
name: t.exposeString("name", { nullable: true }),
|
||||
probability: t.exposeFloat("probability", { nullable: true }), // number, 0 to 1
|
||||
probability: t.exposeFloat("probability", {
|
||||
description: "0 to 1",
|
||||
nullable: true,
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
export const QuestionObj = builder.prismaObject("Question", {
|
||||
findUnique: (question) => ({ id: question.id }),
|
||||
fields: (t) => ({
|
||||
id: t.exposeID("id"),
|
||||
id: t.exposeID("id", {
|
||||
description: "Unique string which identifies the question",
|
||||
}),
|
||||
title: t.exposeString("title"),
|
||||
description: t.exposeString("description"),
|
||||
url: t.exposeString("url"),
|
||||
url: t.exposeString("url", {
|
||||
description:
|
||||
"Non-unique, a very small number of platforms have a page for more than one prediction",
|
||||
}),
|
||||
timestamp: t.field({
|
||||
type: "Date",
|
||||
description: "Timestamp at which metaforecast fetched the question",
|
||||
resolve: (parent) => parent.timestamp,
|
||||
}),
|
||||
platform: t.field({
|
||||
|
@ -81,7 +95,7 @@ export const QuestionObj = builder.prismaObject("Question", {
|
|||
},
|
||||
}),
|
||||
visualization: t.string({
|
||||
resolve: (parent) => (parent.extra as any)?.visualization,
|
||||
resolve: (parent) => (parent.extra as any)?.visualization, // used for guesstimate only, see searchGuesstimate.ts
|
||||
nullable: true,
|
||||
}),
|
||||
}),
|
||||
|
|
|
@ -7,9 +7,15 @@ import { QuestionObj } from "./questions";
|
|||
const SearchInput = builder.inputType("SearchInput", {
|
||||
fields: (t) => ({
|
||||
query: t.string({ required: true }),
|
||||
starsThreshold: t.int(),
|
||||
forecastsThreshold: t.int(),
|
||||
forecastingPlatforms: t.stringList(),
|
||||
starsThreshold: t.int({
|
||||
description: "Minimum number of stars on a question",
|
||||
}),
|
||||
forecastsThreshold: t.int({
|
||||
description: "Minimum number of forecasts on a question",
|
||||
}),
|
||||
forecastingPlatforms: t.stringList({
|
||||
description: "List of platform ids to filter by",
|
||||
}),
|
||||
limit: t.int(),
|
||||
}),
|
||||
});
|
||||
|
@ -17,13 +23,15 @@ const SearchInput = builder.inputType("SearchInput", {
|
|||
builder.queryField("searchQuestions", (t) =>
|
||||
t.field({
|
||||
type: [QuestionObj],
|
||||
description:
|
||||
"Search for questions; uses Algolia instead of the primary metaforecast database",
|
||||
args: {
|
||||
input: t.arg({ type: SearchInput, required: true }),
|
||||
},
|
||||
resolve: async (parent, { input }) => {
|
||||
// defs
|
||||
const query = input.query === undefined ? "" : input.query;
|
||||
if (query == "") return [];
|
||||
if (query === "") return [];
|
||||
const forecastsThreshold = input.forecastsThreshold;
|
||||
const starsThreshold = input.starsThreshold;
|
||||
const platformsIncludeGuesstimate =
|
||||
|
|
Loading…
Reference in New Issue
Block a user