diff --git a/scripts/build-cached-data.js b/scripts/build-cached-data.js index e5cab08..3955faa 100644 --- a/scripts/build-cached-data.js +++ b/scripts/build-cached-data.js @@ -1,41 +1,62 @@ // We run this on build to cache some stable database tables into the JS // bundle! +require("honeycomb-beeline")({ + writeKey: process.env["HONEYCOMB_WRITE_KEY"], + dataset: + process.env["NODE_ENV"] === "production" + ? "Dress to Impress (2020)" + : "Dress to Impress (2020, dev)", + serviceName: "impress-2020-build-process", +}); const fs = require("fs").promises; const path = require("path"); +const { ApolloServer } = require("apollo-server"); +const { createTestClient } = require("apollo-server-testing"); +const gql = require("graphql-tag"); + const connectToDb = require("../src/server/db"); -const { normalizeRow } = require("../src/server/util"); +const { config } = require("../src/server"); const cachedDataPath = path.join(__dirname, "..", "src", "app", "cached-data"); -async function buildZonesCache(db) { - const [rows] = await db.query( - `SELECT z.id, z.depth, zt.label FROM zones z ` + - `INNER JOIN zone_translations zt ON z.id = zt.zone_id ` + - `WHERE locale = "en" ORDER BY z.id;` - ); - const entities = rows.map(normalizeRow); +async function main() { + await fs.mkdir(cachedDataPath, { recursive: true }); + + // Check out this scrappy way of making a query against server code ^_^` + const { query } = createTestClient(new ApolloServer(config)); + const res = await query({ + query: gql` + query BuildCachedData { + allZones { + id + label + depth + isCommonlyUsedByItems + } + } + `, + }); + if (res.errors) { + for (const error of res.errors) { + console.error(error); + } + throw new Error(`GraphQL request failed`); + } const filePath = path.join(cachedDataPath, "zones.json"); - fs.writeFile(filePath, JSON.stringify(entities, null, 4), "utf8"); + await fs.writeFile( + filePath, + JSON.stringify(res.data.allZones, null, 4), + "utf8" + ); console.log(`📚 Wrote zones to ${path.relative(process.cwd(), filePath)}`); } -async function main() { - const db = await connectToDb(); - await fs.mkdir(cachedDataPath, { recursive: true }); - - try { - await buildZonesCache(db); - } catch (e) { - db.close(); - throw e; - } - db.close(); -} - -main().catch((e) => { - console.error(e); - process.exitCode = 1; -}); +main() + .catch((e) => { + console.error(e); + process.exit(1); + }) + .then(() => process.exit()); diff --git a/src/app/apolloClient.js b/src/app/apolloClient.js index 299f62f..1380b00 100644 --- a/src/app/apolloClient.js +++ b/src/app/apolloClient.js @@ -105,6 +105,15 @@ const typePolicies = { const id = readField("id"); return label || cachedZonesById.get(id)?.label || `Zone #${id}`; }, + + isCommonlyUsedByItems: (isCommonlyUsedByItems, { readField }) => { + const id = readField("id"); + return ( + isCommonlyUsedByItems || + cachedZonesById.get(id)?.isCommonlyUsedByItems || + false + ); + }, }, }, }; diff --git a/src/server/index.js b/src/server/index.js index c4a716b..acfb4a3 100644 --- a/src/server/index.js +++ b/src/server/index.js @@ -1,5 +1,5 @@ const { gql, makeExecutableSchema } = require("apollo-server"); -import { addBeelineToSchema, beelinePlugin } from "./lib/beeline-graphql"; +const { addBeelineToSchema, beelinePlugin } = require("./lib/beeline-graphql"); const connectToDb = require("./db"); const buildLoaders = require("./loaders"); @@ -177,6 +177,7 @@ const typeDefs = gql` id: ID! depth: Int! label: String! + isCommonlyUsedByItems: Boolean! } type ItemSearchResult { @@ -224,6 +225,7 @@ const typeDefs = gql` allColors: [Color!]! @cacheControl(maxAge: 10800) # Cache for 3 hours (we might add more!) allSpecies: [Species!]! @cacheControl(maxAge: 10800) # Cache for 3 hours (we might add more!) allValidSpeciesColorPairs: [SpeciesColorPair!]! # deprecated + allZones: [Zone!]! item(id: ID!): Item items(ids: [ID!]!): [Item!]! itemSearch(query: String!): ItemSearchResult! @@ -536,6 +538,15 @@ const resolvers = { const zoneTranslation = await zoneTranslationLoader.load(id); return zoneTranslation.label; }, + isCommonlyUsedByItems: async ({ id }, _, { zoneLoader }) => { + // Zone metadata marks item zones with types 2, 3, and 4. But also, in + // practice, the Biology Effects zone (type 1) has been used for a few + // items too. So, that's what we return true for! + const zone = await zoneLoader.load(id); + const isMarkedForItems = ["2", "3", "4"].includes(zone.typeId); + const isBiologyEffects = zone.id === "4"; + return isMarkedForItems || isBiologyEffects; + }, }, Color: { name: async ({ id }, _, { colorTranslationLoader }) => { @@ -599,6 +610,10 @@ const resolvers = { })); return allPairs; }, + allZones: async (_, __, { zoneLoader }) => { + const zones = await zoneLoader.loadAll(); + return zones.map(({ id }) => ({ id })); + }, item: (_, { id }) => ({ id }), items: (_, { ids }) => { return ids.map((id) => ({ id })); diff --git a/src/server/lib/beeline-graphql.js b/src/server/lib/beeline-graphql.js index 6f3bb86..7b8aea1 100644 --- a/src/server/lib/beeline-graphql.js +++ b/src/server/lib/beeline-graphql.js @@ -3,7 +3,7 @@ const beeline = require("honeycomb-beeline"); const gql = require("graphql"); -export function addBeelineToSchema(schema) { +function addBeelineToSchema(schema) { if (!beeline) return; forEachField(schema, (field) => { if (!field.resolve) return; @@ -68,7 +68,7 @@ const fieldsFor = (name, path) => ({ "graphql.key": path.split(".").pop(), }); -export const beelinePlugin = { +const beelinePlugin = { requestDidStart() { const trace = beeline.startTrace(); return { @@ -84,3 +84,8 @@ export const beelinePlugin = { }; }, }; + +module.exports = { + addBeelineToSchema, + beelinePlugin, +}; diff --git a/src/server/loaders.js b/src/server/loaders.js index 0b08f0c..977a8ce 100644 --- a/src/server/loaders.js +++ b/src/server/loaders.js @@ -397,8 +397,8 @@ const buildPetStatesForPetTypeLoader = (db, loaders) => ); }); -const buildZoneLoader = (db) => - new DataLoader(async (ids) => { +const buildZoneLoader = (db) => { + const zoneLoader = new DataLoader(async (ids) => { const qs = ids.map((_) => "?").join(","); const [rows, _] = await db.execute( `SELECT * FROM zones WHERE id IN (${qs})`, @@ -415,6 +415,20 @@ const buildZoneLoader = (db) => ); }); + zoneLoader.loadAll = async () => { + const [rows, _] = await db.execute(`SELECT * FROM zones`); + const entities = rows.map(normalizeRow); + + for (const zone of entities) { + zoneLoader.prime(zone.id, zone); + } + + return entities; + }; + + return zoneLoader; +}; + const buildZoneTranslationLoader = (db) => new DataLoader(async (zoneIds) => { const qs = zoneIds.map((_) => "?").join(",");