-
-
Notifications
You must be signed in to change notification settings - Fork 229
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add admin for multidimensional data pages #4505
base: master
Are you sure you want to change the base?
Conversation
Quick links (staging server):
Login:
SVG tester:Number of differences (default views): 2153 (ca33c1) ❌ Edited: 2025-02-12 13:55:41 UTC |
3b776df
to
b74eab7
Compare
slug={match.params.slug} | ||
gitCmsBranchName={gitCmsBranchName} | ||
manager={admin} | ||
<QueryClientProvider client={queryClient}> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The changes below are mostly whitespace, except the new route and the new provider.
handleMultiDimDataPageRequest | ||
) | ||
getRouteWithROTransaction(apiRouter, "/multi-dims.json", handleGetMultiDims) | ||
putRouteWithRWTransaction(apiRouter, "/multi-dim/:slug", handlePutMultiDim) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We will change the API in a follow-up PR (including making it plural).
adminSiteServer/validation.ts
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Most of the code moved from adminSiteServer/apiRoutes/charts.ts
.
row: DbRawChartConfig | ||
): DbEnrichedChartConfig { | ||
row: Pick<DbRawChartConfig, "patch" | "full"> | ||
): Pick<DbEnrichedChartConfig, "patch" | "full"> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To make it work for cases when we don't pass the whole config around.
constructor(message: string, status?: number) { | ||
super(message) | ||
constructor(message: string, status?: number, options?: ErrorOptions) { | ||
super(message, options) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To make the cause work.
|
||
import { AdminLayout } from "./AdminLayout.js" | ||
import { AdminAppContext } from "./AdminAppContext.js" | ||
import { Timeago } from "./Forms.js" | ||
import { ColumnsType } from "antd/es/table/InternalTable.js" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can use the public type instead of the internal one.
9c3aa88
to
a73c680
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nicely done!
!(await multiDimDataPageExists(knex, { | ||
slug: link.target, | ||
published: true, | ||
})) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why this change? And, if we change it, why not get rid of isMultiDimDataPagePublished
then (which is now unused)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because I wanted to get rid of isMultiDimDataPagePublished
after I needed the existence check based on other stuff than published
and then I forgot to remove it. Removing!
adminSiteServer/apiRoutes/charts.ts
Outdated
if (newConfig.slug) { | ||
await validateGrapherSlug(knex, newConfig.slug, existingConfig?.id) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need a slug to be present if the chart is published, and should make sure to fail if it's not present.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, of course! Good catch, fixing.
multiDim.config.views.map((view) => view.fullConfigId) | ||
) | ||
|
||
await Promise.all( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm unsure about the limitless concurrency this can run in, if the mdim has many views.
Maybe using p-map
with {concurrency: 10}
or something is better suited here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm happy to change it, and I'd be curious to hear if this was an issue in practice before.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not quite sure honestly!
I think undici (Node's fetch implementation) limits the number of open connections by itself, and I believe we have run into some kind of error related to that in the past?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With {concurrency: 10}
it's a lot slower. Do you think 32 would be ok? Or even higher?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, go for it! It's all just vibes-based anyway 😁
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, in that case, I'd probably leave it as Promise.all()
and fix it if we ever hit a problem. The S3 client doesn't seem to use undici.
if ( | ||
FEATURE_FLAGS.has(FeatureFlagFeature.MultiDimDataPage) && | ||
(await isMultiDimDataPagePublished(trx, slug)) | ||
(await multiDimDataPageExists(trx, { slug, published: true })) | ||
) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code path here should also call validateGrapherSlug
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, true. I didn't realize we are checking only for published graphers in the validation. That's even more complex.
multiDim = await setMultiDimPublished(trx, multiDim, published) | ||
action = published ? "publish" : "unpublish" | ||
} | ||
if (slug !== undefined && slug !== multiDim.slug) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few things here:
- I think we should first handle slug, then publish state.
- In theory, we only need to call
validateGrapherSlug
if the mdim is published. - And we should probably also call it at the point when a mdim is published (i.e. publish state changes to true).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Why do you think we should handle the slug first? I don't see it.
- I'd prefer if we kept the mdim slug valid even before publishing. I'd change the logic when we'll have a use case for it.
- I added the additional validation — I think it's fine to validate the slug twice in when the mdim is published instead of making the logic more complex.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- (1) otherwise we could, in theory, end up in a state where we mark the mdim as published (under the old slug), and then the slug rename fails, i.e. we have it published under the old slug.
I realize that this cannot currently happen, because we either change the slug or the published state, not both at once - but just to be a tiny bit on the safer side, I still think switching these around makes sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, because of the R2! If it was just the DB it wouldn't matter, because the transaction gets rolled back. Changing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I switched the order and also moved the validation logic earlier, so it can also fail before we update stuff to R2.
render: (slug, { id }) => ( | ||
<SlugField id={id} slug={slug} slugMutation={slugMutation} /> | ||
), | ||
sorter: (a, b) => a.title.localeCompare(b.title), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sorter: (a, b) => a.title.localeCompare(b.title), | |
sorter: (a, b) => a.slug.localeCompare(b.slug), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch!
|
||
return ( | ||
<AdminLayout title="Multidimensional Data Pages"> | ||
<NotificationContext.Provider value={null}> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we need this extra context, since it's always null and not antd-specific anyhow?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, you are right. I based my code on the images admin, it was a total cargo culting. 🙂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The combination of react-query and antd makes this page feel really nice! 🎉
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All good now! Thanks for addressing these.
Thanks for the review! Can you check again the last commit, pls? |
if (!(await multiDimDataPageExists(trx, { slug }))) { | ||
await validateNewGrapherSlug(trx, slug) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hm, do we need that if here?
If a mdim already exists under this slug, and we're trying to create a new one, then this will fail either way: at the very least, it'll fail at the UNIQUE
index on the DB table.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need the if, because this handler does an upsert. It won't fail on the unique constraint, it will update the existing row(s) if the mdim already exists.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see, but then we need to actually fail if an mdim exists. Which we currently don't do!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I renamed the function we call to clear up the confusion about create vs upsert.
action = "publish" | ||
} | ||
} | ||
// Note: Keep this change last, since we don't want to update the configs |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know precisely when a DB rollback will happen, honestly.
Could it happen that we change the slug (and propagate that to R2), and then roll that back in the DB afterwards?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It can still happen, it can roll back any time we throw an error before the helper knex function somewhere in our Express handler returns. But the later we do the R2 upload, the better.
We have this problem in many places, and it's fine. The proper way to do this would be with https://en.wikipedia.org/wiki/Change_data_capture, if we really needed the correctness, i.e. first commit the changes to the DB and then propagate the changes to external systems.
This stack of pull requests is managed by Graphite. Learn more about stacking. |
Resolves #4458
ETL path and a new/changed admin API will be in a follow-up PR.