-
Notifications
You must be signed in to change notification settings - Fork 2
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
feat: Metabase module controller and service #4072
base: main
Are you sure you want to change the base?
Conversation
import { getTeamIdAndMetabaseId } from "./getTeamIdAndMetabaseId.js"; | ||
import { createCollection } from "./createCollection.js"; | ||
|
||
const client = createMetabaseClient(); |
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 this line is needed, the client
doesn't appear to be used anywhere
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.
Might be worth following this kind of pattern in all these places where client is used - see the getClient
pattern in this file:
const $client = getClient(); |
We're also getting an API test failure here in a test suite unrelated to metabase: https://github.com/theopensystemslab/planx-new/blob/47aba73d1eefdf8782b12493264a925e1a58969c/api.planx.uk/modules/send/s3/index.test.ts I think this is because there we are globally mocking axios and not accounting for the new use case we have in metabase/shared/client.ts: |
Thanks @jamdelion, I sought Claude's advice on restricting the mock in this file and tried a few things out but to no avail. It looked like the axios spy in Maybe we can flag at dev call on Thursday? |
Happy to pick this one up and take a look once rebased and the merge conflicts are resolved 👍 |
778b6d4
to
6f251b1
Compare
Cheers @DafyddLlyr I've just resolved those--up for talking things through tomorrow if you've got time then but no rush :) |
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've had a quick pass through with some initial comments - always happy to catch up and talk through if you'd like.
I'll checkout the branch and see if I can get to the bottom of the issue with tests and point you in the right direction.
In my mind, we're aiming for something like this - does this match your mental model?
sequenceDiagram
participant User
participant Hasura
participant API
participant MetabaseClient
Note over User, Hasura: User Integration
User->>Hasura: Create Team
Hasura->>Hasura: Add record to teams table
Hasura-->>User: Success
Note over Hasura, MetabaseClient: Async action
Hasura->>Hasura: Event: New team created
Hasura->>API: Create collection request
API->>MetabaseClient: Create collection
MetabaseClient-->>API: Success
API->>Hasura: Update Team Table
Hasura-->>API: Success
Once we have an API endpoint that we can call to create a collection for a team, the next step will be to call it automatically when a team is created, and to populate the current production db with the existing collection ids.
router.post( | ||
"/collection", | ||
validate(createCollectionIfDoesNotExistSchema), | ||
MetabaseCollectionsController, |
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.
MetabaseCollectionsController, | |
metabaseCollectionsController, |
Functions should have camelCase names, only types, interfaces and classes should be in PascalCase.
I'll see if I can find an ESLint rule for this.
@@ -18,5 +20,10 @@ router.post( | |||
validate(logAnalyticsSchema), | |||
logUserResumeController, | |||
); | |||
router.post( | |||
"/collection", |
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.
API docs (docs.yaml
) need to be updated each time we create a new route. There's (currently!) no automated step for this.
description: z.string().optional(), | ||
parentId: z.number().optional(), //.default(COUNCILS_COLLECTION_ID), | ||
}) | ||
.transform((data) => ({ |
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 is probably on me sorry for not being clear on the whole camelCase / snake_case question!
We are no correctly accepting all requests in camelCase, but our API should also only handle camelCase variables. We do need to transform it for Metabase, but we can just do that when we pass it along - it looks like this is already happening in createCollection.ts
?
camelCase in from consumer → API → Some logic → Pass to metabase in snake_case → Parse response back to camelCase → Some logic → camelCase back to consumer
Happy to talk this one through if not clear 🙂
|
||
export async function createCollection( | ||
params: NewCollectionParams, | ||
): Promise<any> { |
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.
nit: We want to avoid using any
wherever we can
api.planx.uk/modules/analytics/metabase/collection/createCollection.ts
Outdated
Show resolved
Hide resolved
return metabaseId; | ||
} | ||
|
||
// Create new Metabase collection if !metabaseId |
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.
nit: Redundant
import { createMetabaseClient } from "../shared/client.js"; | ||
import type { GetCollectionResponse } from "./types.js"; | ||
|
||
const client = createMetabaseClient(); |
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.
Rather than instantiating multiple clients, we could follow a singleton pattern and only have one.
This pattern is used for the planx-core clients (e.g. $public
or $api
).
@@ -18,5 +20,10 @@ router.post( | |||
validate(logAnalyticsSchema), | |||
logUserResumeController, | |||
); | |||
router.post( | |||
"/collection", |
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 should also contextualise this a little more - currently the route is just api.editors.planx.dev/collection
.
It would be useful I think to prefix all of the Metabase specific paths, e.g. api.editors.planx.dev/metabase/collection
.
I actually think that api.editors.planx.dev/metabase/:teamSlug/collection
(instead of using name
in the request body) might be a good naming convention - it's clear we're creating a collection for a team.
Later routes could be api.editors.planx.dev/metabase/:teamSlug/collection/:collectionId/dashboard
etc
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 think I have sorted this (f4dea96), though I went with collection/:teamSlug
--does that order seem important?
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.
Nice! Order here is totally fine 👍
export const createCollectionIfDoesNotExistSchema = z.object({ | ||
body: z | ||
.object({ | ||
name: z.string(), |
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.
name
is a little vague - this is actually the teamSlug right? Took me a minute to catch that - we should update this.
/** Metbase collection ID for the the "Council" collection **/ | ||
// const COUNCILS_COLLECTION_ID = 58; |
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.
Are we hitting issues here with 58 just being the ID on production and not locally and on staging?
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, that's why it's commented out. Is there a better way to handle this in the meantime?
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 think there's three main approaches -
- We set up
COUNCILS_COLLECTION_ID
as an env var and pass in a valid ID for staging/pizza/localhost - this will differ per dev so not ideal but perfectly workable as an initial step - We commit to Metabase being prod only, and add a middleware to all
/metabase
routes on our API which just exits early if we're not on production - makes testing tricky! - We set up Metabase on staging and sync data down
We might end up on the third option, but let's work our way there via 1 and 2 first!
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.
In the meantime no issues keeping this in place and commented out.
Made this back into a draft because I realised that I've just made team slugs the name of the Metabase collections too--will fix that (and look into |
a7c471e
to
e1a5032
Compare
e1a5032
to
f4dea96
Compare
Okay I think we're back up and running (aside from the failing tests 😅). No rush at all for feedback. To respond to @DafyddLlyr's previous point:
Largely yes, asides from the script being triggered by the User - Create Team event (but as mentioned above, this is outside the scope of this PR). Originally, since not all services will have analytics and not all teams will have Metabase collections, I thought the |
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.
Code looking great!
Just looking a bit deeper at tests now to sort out CI. One initial issue is that we don't have the right env vars set up on AWS (again 🤦♂️).
The good news is that I think I worked out the cause of our headaches here. There's been a few PRs like this recently which have update the env file - #4094. I think that these have just pushed a new .env file without first pulling, thus overwriting your new variables. The timing seems to line up. Could you please update the .env
files on S3 again please (pull then push)?
|
||
tags: | ||
- name: analytics | ||
- name: metabase | ||
|
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.
Despite the plural "tags", Swagger only allows a route to be in one location -
We generally want these to match the structure of out API modules - we can either being metabase out to it's own module, or keep it in analytics.
I'd argue it's it's own module (is there any shared types/code/logic?) but this isn't a blocker and we can make this decision later - it's just a case of moving file around.
For now, let's chose the easy path and just apply the analytics tag.
// metabaseClient = createMetabaseClient(); | ||
// return metabaseClient; | ||
// }; | ||
export const $metabase = createMetabaseClient(); |
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.
Nice!
const metabaseParams = { | ||
name, | ||
...rest, | ||
} as const; |
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 sure this as const
is needed here.
return metabaseId; | ||
} | ||
|
||
const { slug, ...rest } = params; |
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.
Please see linting comment on PR about unused var.
* @returns `response.data`, so use dot notation to access `id` or `parent_id`. | ||
*/ | ||
export async function createTeamCollection( | ||
params: NewCollectionParams, |
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.
params: NewCollectionParams, | |
{ slug, parentId, description }: NewCollectionParams, |
We could use destructing here to save a big of complexity and mapping below.
Thanks for the review once again @DafyddLlyr! Sadly things aren't quite working yet🫠 I've done the tidying up tasks, hopefully those are now resolved. I also re-pulled / pushed the vars ( API tests are also failing despite 55d089b. I don't quite understand the issue, since it looks like there aren't explicit error messages, just warnings. I also thought the 'code style' issues should be addressed through pre-commit scripts, but maybe I've misunderstood? |
What does this PR do
createCollectionIfDoesNotExist
servicecreateCollection
Metabase API callWhy
This is the second PR that breaks up #3971 (follows #4071)