Finish wiring up ~owls data
Hey nice it looks like it's working! :3 "Bright Speckled Parasol" is a nice test case, it has a long text string! And when the NC value is not included in the ~owls list, we indeed don't show the badge!
This commit is contained in:
parent
16b86fc65e
commit
240a683e71
6 changed files with 81 additions and 102 deletions
|
@ -79,20 +79,26 @@ async function loadOWLSValuesByIdOrName() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const itemValuesByIdOrName = {};
|
const itemValuesByIdOrName = {};
|
||||||
for (const [itemName, value] of Object.entries(json)) {
|
for (const [itemName, valueText] of Object.entries(json)) {
|
||||||
// OWLS returns an empty string for NC Mall items they don't have a trade
|
// OWLS returns an empty string for NC Mall items they don't have a trade
|
||||||
// value for, to allow the script to distinguish between NP items vs
|
// value for, to allow the script to distinguish between NP items vs
|
||||||
// no-data NC items. We omit it from our data instead, because our UI is
|
// no-data NC items. We omit it from our data instead, because our UI is
|
||||||
// already aware of whether the item is NP or NC.
|
// already aware of whether the item is NP or NC.
|
||||||
if (value.trim() === "") {
|
if (valueText.trim() === "") {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: OWLS doesn't currently provide item IDs ever. Add support for it
|
// TODO: OWLS doesn't currently provide item IDs ever. Add support for it
|
||||||
// if it does! (I'm keeping the rest of the code the same because I
|
// if it does! (I'm keeping the rest of the code the same because I
|
||||||
// think that might happen for disambiguation, like Waka did.)
|
// think that might happen for disambiguation, like Waka did.) Until
|
||||||
|
// then, we just always key by name.
|
||||||
const normalizedItemName = normalizeItemName(itemName);
|
const normalizedItemName = normalizeItemName(itemName);
|
||||||
itemValuesByIdOrName[normalizedItemName] = value;
|
|
||||||
|
// We wrap it in an object with the key `valueText`, just to not break
|
||||||
|
// potential external consumers of this endpoint if we add more fields.
|
||||||
|
// (This is kinda silly and unnecessary, but it should get gzipped out and
|
||||||
|
// shouldn't add substantial time to building or parsing, so like w/e!)
|
||||||
|
itemValuesByIdOrName[normalizedItemName] = { valueText };
|
||||||
}
|
}
|
||||||
|
|
||||||
return itemValuesByIdOrName;
|
return itemValuesByIdOrName;
|
||||||
|
|
|
@ -36,7 +36,13 @@ import { useQuery, useMutation } from "@apollo/client";
|
||||||
import { Link, useParams } from "react-router-dom";
|
import { Link, useParams } from "react-router-dom";
|
||||||
|
|
||||||
import ItemPageLayout, { SubtleSkeleton } from "./ItemPageLayout";
|
import ItemPageLayout, { SubtleSkeleton } from "./ItemPageLayout";
|
||||||
import { Delay, logAndCapture, useLocalStorage, usePageTitle } from "./util";
|
import {
|
||||||
|
Delay,
|
||||||
|
logAndCapture,
|
||||||
|
MajorErrorMessage,
|
||||||
|
useLocalStorage,
|
||||||
|
usePageTitle,
|
||||||
|
} from "./util";
|
||||||
import HTML5Badge, { layerUsesHTML5 } from "./components/HTML5Badge";
|
import HTML5Badge, { layerUsesHTML5 } from "./components/HTML5Badge";
|
||||||
import {
|
import {
|
||||||
itemAppearanceFragment,
|
itemAppearanceFragment,
|
||||||
|
@ -77,7 +83,7 @@ export function ItemPageContent({ itemId, isEmbedded }) {
|
||||||
thumbnailUrl
|
thumbnailUrl
|
||||||
description
|
description
|
||||||
createdAt
|
createdAt
|
||||||
wakaValueText
|
ncTradeValueText
|
||||||
|
|
||||||
# For Support users.
|
# For Support users.
|
||||||
rarityIndex
|
rarityIndex
|
||||||
|
@ -91,7 +97,7 @@ export function ItemPageContent({ itemId, isEmbedded }) {
|
||||||
usePageTitle(data?.item?.name, { skip: isEmbedded });
|
usePageTitle(data?.item?.name, { skip: isEmbedded });
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return <Box color="red.400">{error.message}</Box>;
|
return <MajorErrorMessage error={error} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const item = data?.item;
|
const item = data?.item;
|
||||||
|
|
|
@ -5,7 +5,6 @@ import {
|
||||||
Flex,
|
Flex,
|
||||||
Popover,
|
Popover,
|
||||||
PopoverArrow,
|
PopoverArrow,
|
||||||
PopoverBody,
|
|
||||||
PopoverContent,
|
PopoverContent,
|
||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
Portal,
|
Portal,
|
||||||
|
@ -16,12 +15,7 @@ import {
|
||||||
useToast,
|
useToast,
|
||||||
VStack,
|
VStack,
|
||||||
} from "@chakra-ui/react";
|
} from "@chakra-ui/react";
|
||||||
import {
|
import { ExternalLinkIcon, ChevronRightIcon } from "@chakra-ui/icons";
|
||||||
ExternalLinkIcon,
|
|
||||||
ChevronRightIcon,
|
|
||||||
QuestionIcon,
|
|
||||||
WarningTwoIcon,
|
|
||||||
} from "@chakra-ui/icons";
|
|
||||||
import { gql, useMutation } from "@apollo/client";
|
import { gql, useMutation } from "@apollo/client";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -159,44 +153,15 @@ function ItemPageBadges({ item, isEmbedded }) {
|
||||||
{item.isNc && (
|
{item.isNc && (
|
||||||
<SubtleSkeleton
|
<SubtleSkeleton
|
||||||
isLoaded={
|
isLoaded={
|
||||||
// Distinguish between undefined (still loading) and null (loaded and
|
// Distinguish between undefined (still loading) and null (loaded
|
||||||
// empty).
|
// and empty).
|
||||||
item.wakaValueText !== undefined
|
item.ncTradeValueText !== undefined
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{shouldShowWaka() && item.wakaValueText && (
|
{item.ncTradeValueText && (
|
||||||
<>
|
<LinkBadge href="http://www.neopets.com/~owls">
|
||||||
{/* For hover-y devices, use a hover popover over the badge. */}
|
OWLS: {item.ncTradeValueText}
|
||||||
<Box sx={{ "@media (hover: none)": { display: "none" } }}>
|
</LinkBadge>
|
||||||
<WakaPopover trigger="hover">
|
|
||||||
<LinkBadge
|
|
||||||
href="http://www.neopets.com/~waka"
|
|
||||||
colorScheme="yellow"
|
|
||||||
>
|
|
||||||
<WarningTwoIcon marginRight="1" />
|
|
||||||
Waka: {item.wakaValueText}
|
|
||||||
</LinkBadge>
|
|
||||||
</WakaPopover>
|
|
||||||
</Box>
|
|
||||||
{/* For touch-y devices, use a tappable help icon. */}
|
|
||||||
<Flex
|
|
||||||
sx={{ "@media (hover: hover)": { display: "none" } }}
|
|
||||||
align="center"
|
|
||||||
>
|
|
||||||
<LinkBadge
|
|
||||||
href="http://www.neopets.com/~waka"
|
|
||||||
colorScheme="yellow"
|
|
||||||
>
|
|
||||||
<WarningTwoIcon marginRight="1" />
|
|
||||||
Waka: {item.wakaValueText}
|
|
||||||
</LinkBadge>
|
|
||||||
<WakaPopover>
|
|
||||||
<Flex align="center" fontSize="sm" paddingX="2" tabIndex="0">
|
|
||||||
<QuestionIcon />
|
|
||||||
</Flex>
|
|
||||||
</WakaPopover>
|
|
||||||
</Flex>
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
</SubtleSkeleton>
|
</SubtleSkeleton>
|
||||||
)}
|
)}
|
||||||
|
@ -439,55 +404,4 @@ function ShortTimestamp({ when }) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function WakaPopover({ children, ...props }) {
|
|
||||||
return (
|
|
||||||
<Popover placement="bottom" {...props}>
|
|
||||||
<PopoverTrigger>{children}</PopoverTrigger>
|
|
||||||
<Portal>
|
|
||||||
<PopoverContent>
|
|
||||||
<PopoverArrow />
|
|
||||||
<PopoverBody fontSize="sm">
|
|
||||||
<p>
|
|
||||||
<strong>
|
|
||||||
The Waka Guide for NC trade values is closing down!
|
|
||||||
</strong>{" "}
|
|
||||||
We're sad to see them go, but excited that the team gets to move
|
|
||||||
onto the next phase in their life 💖
|
|
||||||
</p>
|
|
||||||
<Box height="1em" />
|
|
||||||
<p>
|
|
||||||
The Waka guide was last updated August 7, 2021, so this value
|
|
||||||
might not be accurate anymore. Consider checking in with the
|
|
||||||
Neoboards!
|
|
||||||
</p>
|
|
||||||
<Box height="1em" />
|
|
||||||
<p>
|
|
||||||
Thanks again to the Waka team for letting us experiment with
|
|
||||||
sharing their trade values here. Best wishes for everything to
|
|
||||||
come! 💜
|
|
||||||
</p>
|
|
||||||
</PopoverBody>
|
|
||||||
</PopoverContent>
|
|
||||||
</Portal>
|
|
||||||
</Popover>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// August 21, 2021. (The month uses 0-indexing, but nothing else does! 🙃)
|
|
||||||
const STOP_SHOWING_WAKA_AFTER = new Date(2021, 7, 21, 0, 0, 0, 0);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* shouldShowWaka returns true if, according to the browser, it's not yet
|
|
||||||
* August 21, 2021. It starts returning false at midnight on Aug 21.
|
|
||||||
*
|
|
||||||
* That way, our Waka deprecation message is on an auto-timer. After Aug 21,
|
|
||||||
* it's safe to remove all Waka UI code, and the Waka API endpoint and GraphQL
|
|
||||||
* fields. (It might be kind to return a placeholder string for the GraphQL
|
|
||||||
* case!)
|
|
||||||
*/
|
|
||||||
function shouldShowWaka() {
|
|
||||||
const now = new Date();
|
|
||||||
return now < STOP_SHOWING_WAKA_AFTER;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ItemPageLayout;
|
export default ItemPageLayout;
|
||||||
|
|
|
@ -102,7 +102,7 @@ function ItemTradesPage({
|
||||||
thumbnailUrl
|
thumbnailUrl
|
||||||
description
|
description
|
||||||
createdAt
|
createdAt
|
||||||
wakaValueText
|
ncTradeValueText
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import DataLoader from "dataloader";
|
import DataLoader from "dataloader";
|
||||||
|
import fetch from "node-fetch";
|
||||||
import { normalizeRow } from "./util";
|
import { normalizeRow } from "./util";
|
||||||
|
|
||||||
const buildClosetListLoader = (db) =>
|
const buildClosetListLoader = (db) =>
|
||||||
|
@ -825,6 +826,31 @@ const buildItemTradesLoader = (db, loaders) =>
|
||||||
{ cacheKeyFn: ({ itemId, isOwned }) => `${itemId}-${isOwned}` }
|
{ cacheKeyFn: ({ itemId, isOwned }) => `${itemId}-${isOwned}` }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const buildItemNCTradeValueLoader = () =>
|
||||||
|
new DataLoader(async (itemIds) => {
|
||||||
|
// This loader calls our /api/allNCTradeValues endpoint, to take advantage
|
||||||
|
// of the CDN caching. This helps us respond a bit faster than calling the
|
||||||
|
// API directly would, and avoids putting network pressure or caching
|
||||||
|
// complexity on our ~owls friends! (It would also be pretty reasonable to
|
||||||
|
// do this as a process-level cache or something instead, but I'm reusing
|
||||||
|
// Waka code from when we were on a more distributed system where that
|
||||||
|
// wouldn't have worked out, and I don't think the effort to refactor this
|
||||||
|
// just for the potential perf win is worthy!)
|
||||||
|
const url = process.env.NODE_ENV === "production"
|
||||||
|
? "https://impress-2020.openneo.net/api/allNCTradeValues"
|
||||||
|
: "http://localhost:3000/api/allNCTradeValues";
|
||||||
|
const res = await fetch(url);
|
||||||
|
if (!res.ok) {
|
||||||
|
throw new Error(
|
||||||
|
`Error loading /api/allNCTradeValues: ${res.status} ${res.statusText}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const allNCTradeValues = await res.json();
|
||||||
|
|
||||||
|
return itemIds.map((itemId) => allNCTradeValues[itemId]);
|
||||||
|
});
|
||||||
|
|
||||||
const buildPetTypeLoader = (db, loaders) =>
|
const buildPetTypeLoader = (db, loaders) =>
|
||||||
new DataLoader(async (petTypeIds) => {
|
new DataLoader(async (petTypeIds) => {
|
||||||
const qs = petTypeIds.map((_) => "?").join(",");
|
const qs = petTypeIds.map((_) => "?").join(",");
|
||||||
|
@ -1495,6 +1521,7 @@ function buildLoaders(db) {
|
||||||
db
|
db
|
||||||
);
|
);
|
||||||
loaders.itemTradesLoader = buildItemTradesLoader(db, loaders);
|
loaders.itemTradesLoader = buildItemTradesLoader(db, loaders);
|
||||||
|
loaders.itemNCTradeValueLoader = buildItemNCTradeValueLoader();
|
||||||
loaders.petTypeLoader = buildPetTypeLoader(db, loaders);
|
loaders.petTypeLoader = buildPetTypeLoader(db, loaders);
|
||||||
loaders.petTypeBySpeciesAndColorLoader = buildPetTypeBySpeciesAndColorLoader(
|
loaders.petTypeBySpeciesAndColorLoader = buildPetTypeBySpeciesAndColorLoader(
|
||||||
db,
|
db,
|
||||||
|
|
|
@ -33,6 +33,18 @@ const typeDefs = gql`
|
||||||
"""
|
"""
|
||||||
wakaValueText: String @cacheControl(maxAge: ${oneHour})
|
wakaValueText: String @cacheControl(maxAge: ${oneHour})
|
||||||
|
|
||||||
|
"""
|
||||||
|
This item's NC trade value as a human-readable string. Returns null if the
|
||||||
|
value is not known.
|
||||||
|
|
||||||
|
Note that the format of this string is not well-specified—it's fully
|
||||||
|
human-curated and may include surprising words or extra notes! We recommend
|
||||||
|
presenting the text exactly as-is, rather than trying to parse and math it.
|
||||||
|
|
||||||
|
This data is currently curated by neopets.com/~owls, thank you!! <3
|
||||||
|
"""
|
||||||
|
ncTradeValueText: String @cacheControl(maxAge: ${oneHour})
|
||||||
|
|
||||||
currentUserOwnsThis: Boolean! @cacheControl(maxAge: 0, scope: PRIVATE)
|
currentUserOwnsThis: Boolean! @cacheControl(maxAge: 0, scope: PRIVATE)
|
||||||
currentUserWantsThis: Boolean! @cacheControl(maxAge: 0, scope: PRIVATE)
|
currentUserWantsThis: Boolean! @cacheControl(maxAge: 0, scope: PRIVATE)
|
||||||
|
|
||||||
|
@ -344,6 +356,20 @@ const resolvers = {
|
||||||
// This feature is deprecated, so now we just always return unknown value.
|
// This feature is deprecated, so now we just always return unknown value.
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
|
ncTradeValueText: async ({ id }, _, { itemNCTradeValueLoader }) => {
|
||||||
|
let ncTradeValue;
|
||||||
|
try {
|
||||||
|
ncTradeValue = await itemNCTradeValueLoader.load(id);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(
|
||||||
|
`Error loading ncTradeValueText for item ${id}, skipping:`
|
||||||
|
);
|
||||||
|
console.error(e);
|
||||||
|
ncTradeValue = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ncTradeValue ? ncTradeValue.valueText : null;
|
||||||
|
},
|
||||||
|
|
||||||
currentUserOwnsThis: async (
|
currentUserOwnsThis: async (
|
||||||
{ id },
|
{ id },
|
||||||
|
|
Loading…
Reference in a new issue