diff --git a/src/app/WardrobePage/SearchPanel.js b/src/app/WardrobePage/SearchPanel.js index 942e725..a6a86e9 100644 --- a/src/app/WardrobePage/SearchPanel.js +++ b/src/app/WardrobePage/SearchPanel.js @@ -204,6 +204,21 @@ function useSearchResults(query, outfitState) { setIsEndOfResults(false); }, [query]); + // NOTE: This query should always load ~instantly, from the client cache. + const { data: zoneData } = useQuery(gql` + query SearchPanelZones { + allZones { + id + label + } + } + `); + const allZones = zoneData?.allZones || []; + const filterToZones = query.filterToZoneLabel + ? allZones.filter((z) => z.label === query.filterToZoneLabel) + : []; + const filterToZoneIds = filterToZones.map((z) => z.id); + // Here's the actual GQL query! At the bottom we have more config than usual! const { loading: loadingGQL, @@ -215,11 +230,13 @@ function useSearchResults(query, outfitState) { query SearchPanel( $query: String! $speciesId: ID! + $zoneIds: [ID!]! $colorId: ID! $offset: Int! ) { itemSearchToFit( query: $query + zoneIds: $zoneIds speciesId: $speciesId colorId: $colorId offset: $offset @@ -257,7 +274,13 @@ function useSearchResults(query, outfitState) { ${itemAppearanceFragment} `, { - variables: { query: debouncedQuery.value, speciesId, colorId, offset: 0 }, + variables: { + query: debouncedQuery.value, + zoneIds: filterToZoneIds, + speciesId, + colorId, + offset: 0, + }, skip: !debouncedQuery.value && !debouncedQuery.filterToZoneLabel, notifyOnNetworkStatusChange: true, onCompleted: (d) => { diff --git a/src/server/index.js b/src/server/index.js index e859408..6f06ea5 100644 --- a/src/server/index.js +++ b/src/server/index.js @@ -233,6 +233,7 @@ const typeDefs = gql` query: String! speciesId: ID! colorId: ID! + zoneIds: [ID!] offset: Int limit: Int ): ItemSearchResult! @@ -634,7 +635,7 @@ const resolvers = { }, itemSearchToFit: async ( _, - { query, speciesId, colorId, offset, limit }, + { query, speciesId, colorId, zoneIds, offset, limit }, { petTypeBySpeciesAndColorLoader, itemSearchToFitLoader } ) => { const petType = await petTypeBySpeciesAndColorLoader.load({ @@ -645,6 +646,7 @@ const resolvers = { const items = await itemSearchToFitLoader.load({ query: query.trim(), bodyId, + zoneIds, offset, limit, }); diff --git a/src/server/loaders.js b/src/server/loaders.js index 40a18f0..2075fcb 100644 --- a/src/server/loaders.js +++ b/src/server/loaders.js @@ -189,7 +189,7 @@ const buildItemSearchToFitLoader = (db, loaders) => // This isn't actually optimized as a batch query, we're just using a // DataLoader API consistency with our other loaders! const queryPromises = queryAndBodyIdPairs.map( - async ({ query, bodyId, offset, limit }) => { + async ({ query, bodyId, zoneIds = [], offset, limit }) => { const actualOffset = offset || 0; const actualLimit = Math.min(limit || 30, 30); @@ -200,6 +200,10 @@ const buildItemSearchToFitLoader = (db, loaders) => const matcherPlaceholders = words .map((_) => "t.name LIKE ?") .join(" AND "); + const zoneIdsPlaceholder = + zoneIds.length > 0 + ? `swf_assets.zone_id IN (${zoneIds.map((_) => "?").join(", ")})` + : "1"; const [rows, _] = await db.execute( `SELECT DISTINCT items.*, t.name FROM items INNER JOIN item_translations t ON t.item_id = items.id @@ -207,10 +211,17 @@ const buildItemSearchToFitLoader = (db, loaders) => ON rel.parent_type = "Item" AND rel.parent_id = items.id INNER JOIN swf_assets ON rel.swf_asset_id = swf_assets.id WHERE ${matcherPlaceholders} AND t.locale="en" AND - (swf_assets.body_id = ? OR swf_assets.body_id = 0) + (swf_assets.body_id = ? OR swf_assets.body_id = 0) AND + ${zoneIdsPlaceholder} ORDER BY t.name LIMIT ? OFFSET ?`, - [...wordMatchersForMysql, bodyId, actualLimit, actualOffset] + [ + ...wordMatchersForMysql, + bodyId, + ...zoneIds, + actualLimit, + actualOffset, + ] ); const entities = rows.map(normalizeRow); diff --git a/src/server/query-tests/ItemSearch.test.js b/src/server/query-tests/ItemSearch.test.js index f54c43d..4c7b1ce 100644 --- a/src/server/query-tests/ItemSearch.test.js +++ b/src/server/query-tests/ItemSearch.test.js @@ -119,7 +119,8 @@ describe("ItemSearch", () => { ON rel.parent_type = \\"Item\\" AND rel.parent_id = items.id INNER JOIN swf_assets ON rel.swf_asset_id = swf_assets.id WHERE t.name LIKE ? AND t.name LIKE ? AND t.locale=\\"en\\" AND - (swf_assets.body_id = ? OR swf_assets.body_id = 0) + (swf_assets.body_id = ? OR swf_assets.body_id = 0) AND + 1 ORDER BY t.name LIMIT ? OFFSET ?", Array [ @@ -134,6 +135,73 @@ describe("ItemSearch", () => { `); }); + it("loads Neopian Times items that fit the Starry Zafara as a Background", async () => { + const res = await query({ + query: gql` + query { + itemSearchToFit( + query: "Neopian Times" + speciesId: "54" + colorId: "75" + zoneIds: ["3"] + ) { + query + items { + id + name + } + } + } + `, + }); + + expect(res).toHaveNoErrors(); + expect(res.data).toMatchInlineSnapshot(` + Object { + "itemSearchToFit": Object { + "items": Array [ + Object { + "id": "40431", + "name": "Neopian Times Background", + }, + ], + "query": "Neopian Times", + }, + } + `); + expect(getDbCalls()).toMatchInlineSnapshot(` + Array [ + Array [ + "SELECT * FROM pet_types WHERE (species_id = ? AND color_id = ?)", + Array [ + "54", + "75", + ], + ], + Array [ + "SELECT DISTINCT items.*, t.name FROM items + INNER JOIN item_translations t ON t.item_id = items.id + INNER JOIN parents_swf_assets rel + ON rel.parent_type = \\"Item\\" AND rel.parent_id = items.id + INNER JOIN swf_assets ON rel.swf_asset_id = swf_assets.id + WHERE t.name LIKE ? AND t.name LIKE ? AND t.locale=\\"en\\" AND + (swf_assets.body_id = ? OR swf_assets.body_id = 0) AND + swf_assets.zone_id IN (?) + ORDER BY t.name + LIMIT ? OFFSET ?", + Array [ + "%Neopian%", + "%Times%", + "180", + "3", + 30, + 0, + ], + ], + ] + `); + }); + it("searches for each word separately (fit mode)", async () => { const res = await query({ query: gql` @@ -183,7 +251,8 @@ describe("ItemSearch", () => { ON rel.parent_type = \\"Item\\" AND rel.parent_id = items.id INNER JOIN swf_assets ON rel.swf_asset_id = swf_assets.id WHERE t.name LIKE ? AND t.name LIKE ? AND t.locale=\\"en\\" AND - (swf_assets.body_id = ? OR swf_assets.body_id = 0) + (swf_assets.body_id = ? OR swf_assets.body_id = 0) AND + 1 ORDER BY t.name LIMIT ? OFFSET ?", Array [ @@ -242,7 +311,8 @@ describe("ItemSearch", () => { ON rel.parent_type = \\"Item\\" AND rel.parent_id = items.id INNER JOIN swf_assets ON rel.swf_asset_id = swf_assets.id WHERE t.name LIKE ? AND t.locale=\\"en\\" AND - (swf_assets.body_id = ? OR swf_assets.body_id = 0) + (swf_assets.body_id = ? OR swf_assets.body_id = 0) AND + 1 ORDER BY t.name LIMIT ? OFFSET ?", Array [ @@ -324,7 +394,8 @@ describe("ItemSearch", () => { ON rel.parent_type = \\"Item\\" AND rel.parent_id = items.id INNER JOIN swf_assets ON rel.swf_asset_id = swf_assets.id WHERE t.name LIKE ? AND t.locale=\\"en\\" AND - (swf_assets.body_id = ? OR swf_assets.body_id = 0) + (swf_assets.body_id = ? OR swf_assets.body_id = 0) AND + 1 ORDER BY t.name LIMIT ? OFFSET ?", Array [