Commit graph

1274 commits

Author SHA1 Message Date
a5c3108a90 Whoops, fix outfitImage caching bug
I was accidentally serving images _without_ `updatedAt` with the long-term cache headers, but only in prod, not in dev

I think this is probably because I made a mistake in my Vercel route config, by including an `$updatedAt` parameter that doesn't actually exist in this case.

I'm guessing the Vercel dev server has different behavior for my mistake than the prod server. I'm guessing the dev server makes it an empty string, and the prod server makes it the literal string `$updatedAt`. Oops!

I hope this fixes it!
2021-05-27 17:29:06 -07:00
e608844171 Add better 404 message for outfit image 2021-05-27 17:01:20 -07:00
27437c33de Merge branch 'graphql-docs' into main 2021-05-27 16:52:15 -07:00
8525ac393f Fix GraphQL docstrings
Oops, right, I forgot for a while that GraphQL fields have a special syntax for docstrings, and it's not just comments! This will help stuff show up in our GraphQL Playground API docs correctly 🥰
2021-05-27 16:51:31 -07:00
5f0089f990 Access-Control-Allow-Origin: * for GraphQL
Someone asked to use the DTI API for a small client-side project, so I'm making this change to support it!

As explained in the comment, I think this should be safe regarding CSRF attacks. But it _does_ increase the risk that someday we change something elsewhere that creates a problem, like using cookies to authorize something. So, let's remember to be careful! (as I would hope we would be when adding another auth mechanism!)
2021-05-27 16:41:52 -07:00
abc322c24d Remove imageUrl from Outfit GQL
I'm not sure which image url is better to return from stuff like this, and I don't actually have a use case for it anymore, so let's just clear it out until we need something like it!
2021-05-26 20:01:03 -07:00
684bf5778f Use live outfit URLs in bulk converter 2021-05-26 20:00:01 -07:00
929e6c57a4 Use live outfit image URLs in single image converter 2021-05-26 19:57:06 -07:00
2543f89255 Add ~live outfit image URLs
Gonna have the /outfit-urls page start returning these instead, for feature parity with before

I might change the strategy on this at some point, like have it get `updatedAt` and redirect instead of generating the image. But this is simpler for now (and the Vercel cache doesn't seem to be as aggressive as I want anyway), and I can change it later!
2021-05-26 19:44:35 -07:00
3603d6fd85 Explain the error condition 2021-05-26 19:11:04 -07:00
81b434347a Add title to OutfitUrlsPage 2021-05-26 19:03:21 -07:00
accd847a7d Merge branch 'main' of github.com:matchu/impress-2020 into main 2021-05-26 18:50:25 -07:00
7f0a450480 Apply sampling rates to Honeycomb events
Oops, we get a _lot_ of outfit image requests, and it's pushing the limits of our free Honeycomb plan! But I don't really need all that much detail, because there's so many.

So, we here apply sampling! `api/outfitImage` is getting a 1/10 rate, and for GraphQL, `ApiOutfitImage` is getting 1/10, and `SearchPanel` is getting 1/5.

I had to add a `addTraceContext` call, to give all the child events awareness of what operation they're being called in, too!

I haven't actually tested that this is working-working, just that the endpoints still return good data. We'll see how it shakes out in prod!

But I did add `console.log(sampleRate, shouldSample, data);` to the `samplerHook` briefly, to see the data flow through, and I reloaded a `SearchPanel` request a few times and observed a plausibly 20% success rate.
2021-05-26 18:50:19 -07:00
5cab2c87c4 Fix UC outfit images
Oops, I wasn't requesting `bodyId` for item layers, so the check for `layer.bodyId !== "0"` was always true—because it was always `undefined`, even when it should have been `"0"`.

This wasn't an issue on the client, because the client _does_ request `bodyId` for caching item appearances between pets of the same body, and I didn't realize that it needs to be part of this fragment too!
2021-05-25 17:02:29 -07:00
e829cc5525 Add a delay to bulk image conversion loading state
Right, yeah, in prod this is way faster, so the loading indicator is a distraction!
2021-05-25 05:49:47 -07:00
5f1980241f Oops, don't stretch preview when showing error
Right, oops, if you typed an invalid image url, the preview square would try to stretch to match! Now, it has a max-height to match its max-width.
2021-05-25 05:47:58 -07:00
11703140c4 Add more explainer copy to /outfit-urls 2021-05-25 05:46:10 -07:00
a08f4e3bd1 Fix controlled component warning
Mm right, when we first render the output, `imageUrl` is `undefined`, so the output textbox renders with `value={undefined}`, which is an "uncontrolled" component that the DOM is free to change.

In practice, this isn't an issue because the textbox has `isReadOnly`, so the user can't _actually_ change it. But it's still a good idea for consistency and clarity to use an empty string instead of `undefined`, and it removes warning spam from my console!
2021-05-25 05:30:57 -07:00
f9f8cdc553 Wire up the bulk outfit image converter
Heck yeah, it's looking great!! Good error handling too :) you can test the partial error case by changing some image URLs to have invalid IDs.
2021-05-25 05:28:02 -07:00
08eee30743 Lookup/petpage converter layout upgrades
Added placeholders, and improved copy styles

I tried a two-column view for desktop, but it's too busy with the long label text. This is clearer!
2021-05-25 04:17:09 -07:00
8b3cfe7118 Basic lookup/petpage design for /outfit-urls
Not converting yet, and only mobile design, but hitting save on it anyway!
2021-05-25 04:04:35 -07:00
01711fe0c2 Don't shrink error icon for long error text
Oops, previously the MajorErrorMessage was willing to shrink the width of the cute Grundo Programmer icon, to allow error messages with long words to avoid word breaks.

Here, we switch `1fr` for `minmax(0, 1fr)`, which allows the text zone to get smaller. (`1fr` is short for `minmax(auto, 1fr)`, which isn't capable of shrinking smaller than the natural value.)

Now, the error text is more willing to shrink by word-wrapping, than the image is by shrinking the image. Success!
2021-05-25 03:54:14 -07:00
390c21b53e Add image converter to /outfit-urls
Adding the tool that will help people convert one image at a time!
2021-05-25 03:35:32 -07:00
42b8c3833c Create /outfit-urls page
Gonna put an upgrade tool here for outfit images, and provide the URL in the replacement images on S3!
2021-05-24 21:07:30 -07:00
e5551a6847 Fix performance regression in SpeciesColorPicker
Oops, my cute API idea for `speciesPickerProps` breaks `React.memo`, of course!

We could fix this by having the caller memoize the `speciesPickerProps` object, but that's too unusual and error-prone. We could also fix this by writing a custom function for `React.memo` to determine whether props match, but that seems like overkill when there's only one actual prop we're using here in practice.

So yeah, I've updated `SpeciesColorPicker` to just accept `speciesTestId` and `colorTestId` props instead!

Note that actually this component _is_ still re-rendering too often, because of a Chakra bug I just discovered and reported! So this change won't immediately improve performance, but it should stop re-rendering too often once we _also_ upgrade Chakra after this bug is fixed. https://github.com/chakra-ui/chakra-ui/issues/4080
2021-05-24 17:50:31 -07:00
e3ae5e7116 extract PaginationToolbar from ItemSearchPage
wanna reuse in like outfits page
2021-05-21 00:50:55 -07:00
a676be0f0f Use impress-outfit-images.openneo.net outfit URLs
Oops, right, I meant to use the new `impress-outfit-images.openneo.net` host for this! It works just fine from `impress-2020.openneo.net` as the backing source right now, but I want these semi-permanent URLs to be a bit more decoupled.
2021-05-20 20:57:06 -07:00
0eec503b91 Create pretty route for outfit images
This is mostly for decoupling's sake: I want pretty outfit URLs that aren't dependent on the `/api/outfitImage` path structure, so that we can serve them as ~permanent pretty URLs that don't actually indicate where or how the image is stored.
2021-05-20 20:35:41 -07:00
ae82a6236f Revert "Use current host for outfit share tags"
This reverts commit a0121f09ea.

I didn't realize that VERCEL_URL wouldn't use the canonical URL. And thinking further about the Open Graph semantics, I think having just one canonical host makes more sense, even if it makes testing a bit more annoying in some cases.
2021-05-14 21:09:09 -07:00
a0121f09ea Use current host for outfit share tags
This will help test preview pages and such!
2021-05-14 20:47:15 -07:00
2bc2fc5bfe Don't handle /outfits/new with SSR
Right, the routes are a bit confusing, `/outfits/new` matches `/outfits/:id`, but shouldn't.

I fix this at the routing level, by creating a more specific `/outfits/new` route that matches first, and just serves `index.html` right from the CDN.

To check that it's exact, I check for either a `?` or the end-of-string after `new`. That way, `/outfits/newish` correctly redirects to the SSR. (This doesn't actually matter a lot, because outfit IDs are only ever numbers and therefore can't start with "new"? But I figure, better to have an unambiguous semantic, then leave random things relying on outfit ID format assumptions.)
2021-05-14 20:35:24 -07:00
d31809cc62 Use VERCEL_URL in /api/outfitImage
Ah right, this is how we get the deployed API code to know its own hostname! This way, when we do a preview deploy, /api/outfitImage will use the preview version of the GraphQL endpoint, too.
2021-05-14 20:28:33 -07:00
5e657e7547 Fix prod outfit SSR by just using URLs, not build
Hmm, the built copy of the HTML isn't deployed with the API function on Vercel. Ok! Let's just do the same trick as we did for the dev server, and make an HTTP request.

This is fine enough for perf because we're caching this result locally to the function, plus it should be a fast CDN-cached response anyway.
2021-05-14 20:27:45 -07:00
08347ad88f Add a note explaining our outfit SSR decision 2021-05-14 20:04:46 -07:00
0fafeb92e1 Oops, fix production outfit SSR
That's what I get for not testing lol!
2021-05-14 20:02:32 -07:00
34ada18beb Use 600x600 outfit images in shares, not 300x300
Size upgrade, wowie! (I figure sharing services probably prefer a higher-resolution image, they're not very big, and to then scale them down for the application-specific use case & device.)
2021-05-14 19:55:12 -07:00
f48e6ba03d Add support for 600x600 outfit images
Gonna use this in the sharing, come next commit!
2021-05-14 19:54:18 -07:00
8f83ac412c Add sharing meta tags to outfit pages
Meta tags are a bit tricky in apps built with `create-react-app`! While some bots like Google are able to render the full page when crawling, not all bots are. Most will just see the empty-ish index.html that would normally load up the application.

But we want outfit sharing to work! And be cool! And use our new outfit thumbnails!

In this change, we add a new server-side rendering API route to handle `/outfits/:id`.

It's very weak server-side rendering: it just loads index.html, and makes a few small tweaks inside the `<head>` tag. But it should be enough for sharing to work in clients that support the basics of Open Graph, which I think most major providers respect! (I know Twitter has their own tags, but it also respects the basics of OG, so let's see whether there's anything we end up _wanting_ to tweak or not!)
2021-05-14 19:51:48 -07:00
fddeeecbdb More eslint fixes
Oh oops, my no-unused-vars changes affected more than I realized! It caused builds to break.

I've now fixed it to pass on our existing codebase!
2021-05-13 18:11:44 -07:00
9722addd3f Generate outfit images by ID alone
Not using this anywhere in-app yet! But might swap it into the user outfits page, and use it to server-side-render social sharing meta tags!

Also eyeing this as a way to replace our nearly 1TB of outfit image S3 storage, and save $20/mo…
2021-05-13 18:03:56 -07:00
8f495d8302 Move getVisibleLayers to a new shared directory
This folder will include code shared by both the client-side app and the server!

The server isn't using it yet, but it will in a new API endpoint soon! I'm doing this in a separate commit to avoid lumping all the import-change noise into that commit.
2021-05-13 17:35:58 -07:00
3e981d82b4 Extract getVisibleLayers to its own file
I'm doing this in preparation for an API endpoint to build outfit images by ID. It'll need the same logic to decide which layers are visible, and the same GQL fragments to load the relevant data!
2021-05-13 17:33:54 -07:00
eec73d245b Add PB badge to items in search / homepage
Oh right, labeling PB items as NP is confusing! Here, we add a "PB" case to the lil badge on the corner of the item thumbnail, in item search page & homepage Newest Items.
2021-05-13 16:24:24 -07:00
2c043adbe0 Support new Fastly S3 domain in outfit images
Oops, I forgot to grep outside `src` for this, lol! I'll keep the S3 domain support for now, because it's still fine to accept and some clients might be on old code, whatever!
2021-05-13 01:15:17 -07:00
8c8e845dc5 Fix eslint for /api
Oops, I didn't notice this when bulk-fixing eslint stuff in src!
2021-05-13 01:13:21 -07:00
e56c5d9a9c Use stale-while-revalidate for /api/validPetPoses
Idk I was surprised to notice that /api/validPetPoses wasn't cached by the Vercel CDN… but I guess that makes sense, because it's only allowed to be an hour old, and client caches hold onto it, so I suppose it makes sense for the CDN cache to not bother.

But in practice, that means users loading the site are pretty _likely_ to see /api/validPetPoses taking its full load time, which can be up to 500ms.

So, I'm offering this additional cache hint, to be willing to serve /api/validPetPose responses up to a week old while reloading the latest data. My hope is that the cache algorithm is much more excited about holding onto that, and that it makes the 500ms delay very _rare_ in practice!
2021-05-13 01:05:34 -07:00
c8feb9a7e0 Fix infinite loop when searching for Markings
Oops, we did an in-place sort on the search variables we passed to Apollo! This meant that Apollo's first read of the variables wouldn't match later reads, so it would always decide the variables had changed, causing an infinite re-render loop.

Remember to copy existing arrays before sorting! 😅

Incidentally, this only happened for Markings, by coincidence: it's the only (I think) searchable zone label with multiple zone IDs, that don't sort alphabetically the same as they sort numerically. This `.sort()` sorts them alphabetically, whereas they come in numerical order in `allZones`, because that's the order the GQL server returns them in `build-cached-data.js`.
2021-05-13 00:28:06 -07:00
b521f79a13 Don't auto-select the first item search filter
There have been usability problems with this search filter UI, and I think they mostly come down to people accidentally selecting filters when they don't mean to—sometimes pressing Enter to indicate that they're done typing, but accidentally selecting something.

Here, we remove that behavior, and additionally add a new behavior to clear the suggestions on pressing Enter.
2021-05-12 23:08:54 -07:00
9ea8245208 Serve pet name as id too in GQL 2021-05-12 22:52:58 -07:00
92a4fd4808 Use Fastly to cache our PNG assets from S3
We've been serving images directly from `impress-asset-images.s3.amazonaws.com` for a long time. While they serve with long-lasting HTTP cache headers, and the app requests them with the `updated_at` timestamp in the query string; each GET request still executes a full S3 ReadObject operation to get the latest version.

In the past, this was only relevant to users on Image Mode, not Flash Mode. But now that everyone's on Image Mode, this matters a lot more!

Now, we've configured a Fastly host at `impress-asset-images.openneo.net`, to sit in front of our S3 bucket. This should dramatically reduce the GET requests to S3 itself, as our cache warms up and gains copies of the most common asset PNGs.

That said, I'm not sure how much actual cost impact this change will have. Our AWS console isn't configured to differentiate cost by bucket yet—I've started this process, but it might take a few days to propagate. All I know is that our current costs are $35/mo data transfer + $20/mo storage, and that outfit images are responsible for most of the storage cost. I hypothesize that `impress-asset-images` is responsible for most of the reads and data transfers, but I'm not sure!

In the future, I think we'll be able to bring our AWS costs to near-zero, by:
- Obsolete `impress-asset-images`, by using the official Neopets PNGs instead, after the HTML5 conversion completes.
- Obsolete `impress-outfit-images`, by using a Node endpoint to generate the images, fronted by a CDN cache. (Transfer the actual data to a long-term storage backup, and replace the S3 objects with redirects, so that old S3 URLs will still work.)

I hope this will be a big slice of the costs though! 🤞

(Note: I'll be deploying this on a bit of a delay, because I want to see the DNS propagate across the globe before flipping to a new domain!)
2021-05-12 22:49:59 -07:00