Skip to content
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

Creation Quote Metric #55

Merged
merged 25 commits into from
Aug 10, 2023
Merged

Creation Quote Metric #55

merged 25 commits into from
Aug 10, 2023

Conversation

mtakeda
Copy link
Contributor

@mtakeda mtakeda commented Jul 14, 2023

What problem is this solving?

Sending quotation created metrics to analytics in order to have more business data to take decisions and analyze user's behavior.

How to test it?

Create a quote in https://takeda--b2bstore005.myvtex.com/deals
After one hour (maximum), data should appear in Analytics Redshift, into table "vtex"."schemaless"."b2b_suite_buyerorg_data_raw"

Workspace

Screenshots or example usage:

No changes in layout. Current quotation create workflow kept as is.
image

Redshit query and result example:

SELECT
    payload.kind,
    payload.account,
    payload.fields.quote_id,
    payload.fields.quote_reference_name,
    payload.fields.creation_date,
    payload.fields.send_to_sales_rep,
    payload.fields.org_id,
    payload.fields.cost_center_id,
    payload.fields.cost_center_name,
    payload.fields.buy_org_id,
    payload.fields.buy_org_name,
    payload.fields.member_id,
    payload.fields.member_email,
    payload.fields.role
FROM
    "vtex"."schemaless"."b2b_suite_buyerorg_data_raw"
WHERE payload.name = 'b2b-suite-buyerorg-data'
order by ingestion_time desc

image

type:

type Metric = {
  name: 'b2b-suite-buyerorg-data'
  kind: 'create-quote-ui-event'
  description: 'Create Quotation Action - UI'
  account: string
}

type QuoteFieldsMetric = {
  org_id: string
  cost_center_id: string
  cost_center_name: string
  buy_org_id: string
  buy_org_name: string
  member_id: string
  member_email: string
  role: string
  creation_date: string
  quote_id: string
  quote_reference_name: string
  send_to_sales_rep: boolean
}

type QuoteMetric = Metric & { fields: QuoteFieldsMetric }

Describe alternatives you've considered, if any.

NA

Related to / Depends on

NA

How does this PR make you feel?

@mtakeda mtakeda requested a review from a team July 14, 2023 13:53
@vtex-io-ci-cd
Copy link

vtex-io-ci-cd bot commented Jul 14, 2023

Hi! I'm VTEX IO CI/CD Bot and I'll be helping you to publish your app! 🤖

Please select which version do you want to release:

  • Patch (backwards-compatible bug fixes)

  • Minor (backwards-compatible functionality)

  • Major (incompatible API changes)

And then you just need to merge your PR when you are ready! There is no need to create a release commit/tag.

  • No thanks, I would rather do it manually 😞

@vtex-io-docs-bot
Copy link

vtex-io-docs-bot bot commented Jul 14, 2023

Beep boop 🤖

I noticed you didn't make any changes at the docs/ folder

  • There's nothing new to document 🤔
  • I'll do it later 😞

In order to keep track, I'll create an issue if you decide now is not a good time

  • I just updated 🎉🎉

@github-actions
Copy link

github-actions bot commented Jul 14, 2023

Messages
📖 ❤️ Thanks!
📖

🎉 PR additions = 299, PR deletions = 5

Generated by 🚫 dangerJS against 4bff5c2

docs/README.md Outdated Show resolved Hide resolved
docs/README.md Outdated
Comment on lines 254 to 255
---

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you forgot these lines

Comment on lines 120 to 133
const userEmail = sessionResponse?.namespaces?.profile?.email?.value
const { data: userData } = useQuery(CHECK_PERMISSIONS, {
variables: { email: userEmail },
})

const costCenterId: string = userData?.getUserByEmail?.[0]?.costId
const { data: costCenterData } = useQuery(GET_COSTCENTER_BY_ID, {
variables: { id: costCenterId },
})

const orgId: string = userData?.getUserByEmail?.[0]?.orgId
const { data: organizationData } = useQuery(GET_ORGANIZATION_BY_ID, {
variables: { id: orgId },
})

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will make us fetch all that data every time this page is rendered, even if we end up not needing it.

It seems like we only ever need this when sending the metric, right? So it's best if we only do this data fetching at that time, within the sendMetric function.

In that case you won't be doing the fetching using hooks (hooks are these functions with names starting with use). React hooks have logic behind the scenes that cause the screen to be rerendered when the data is loading, and then again when it arrives. For your use case we should do everything behind the scenes and without causing rerenders. If these data fetches were not graphql calls you could just use axios for that, for example. In graphql's case you can use a simple library like graphql-request instead.

Comment on lines 120 to 132
const userEmail = sessionResponse?.namespaces?.profile?.email?.value
const { data: userData } = useQuery(CHECK_PERMISSIONS, {
variables: { email: userEmail },
})

const costCenterId: string = userData?.getUserByEmail?.[0]?.costId
const { data: costCenterData } = useQuery(GET_COSTCENTER_BY_ID, {
variables: { id: costCenterId },
})

const orgId: string = userData?.getUserByEmail?.[0]?.orgId
const { data: organizationData } = useQuery(GET_ORGANIZATION_BY_ID, {
variables: { id: orgId },

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One of the advantages of graphql is that you don't need to do separate fetches to get information. Since you need all three of these to send the metric, you can build a single graphql query joining them all together in a single request. Also, it's probably better to build a new query for this use case anyway, since you probably don't need the exact same data that those existing queries were asking for. You can drop the unused fields and join the queries together, resulting in a better API request.

Comment on lines 224 to 226
userData: userData?.getUserByEmail?.[0],
costCenterName: costCenterData?.getCostCenterById?.name,
buyOrgName: organizationData?.getOrganizationById?.name,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not 100% sure, but it's possible that all this data is already present in the session or segment cookie, so it would be good to double check before making the changes I suggested to the data fetching.

try {
const metric = buildQuoteMetric(sendToSalesRep, metricsParam)

axios.post(ANALYTICS_URL, metric)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's better to have an await call here, otherwise you won't catch errors correctly

Suggested change
axios.post(ANALYTICS_URL, metric)
await axios.post(ANALYTICS_URL, metric)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case, I'm not sure I need to catch errors. The idea of not having an await is not having to wait for the response from analytics API and not delay a bit more the response for users.

Copy link

@mairatma mairatma Jul 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that it's not very important for you to catch errors here, but in that case you should remove the try/catch in this function. Without the await it won't work well if the request actually fails.

If you want to keep catching the errors here you can add the await. That won't really change the behavior of the call, because you don't await the sendMetric call itself, so it will still run in the background without affecting the rendering thread in any way.


axios.post(ANALYTICS_URL, metric)
} catch (error) {
console.error('Unable to log metrics', error)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's best to make this a warn, since its a background call that doesn't (and shouldn't) break the page.

Copy link

@mairatma mairatma left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Mauro! Just smaller suggestions now.

Comment on lines 73 to 75
getUserByEmail(email: $email) @context(provider: "vtex.storefront-permissions") {
costId
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a small indentation fix:

Suggested change
getUserByEmail(email: $email) @context(provider: "vtex.storefront-permissions") {
costId
}
getUserByEmail(email: $email) @context(provider: "vtex.storefront-permissions") {
costId
}

buy_org_name: metricsData.organizationName,
cost_center_id: metricsData.costId,
cost_center_name: metricsData.costCenterName,
member_id: namespaces?.profile?.id?.value,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In line 98 the code is assuming that namespaces.profile.email.value is always set, but here you're handling the case where namespaces and profile might not be set. If that's a possibility we need to also add this to lines 97 and 98, skipping the whole metric process in case the values are not set (since those are required).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of skipping the while metric process, I'll check the namespace, profile, and email, and let it save null values. I believe it's better to save the metric with the data we have than not save it at all

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, if the metric is still useful, let's save it!

Comment on lines 80 to 83
const { data } = (
await axios.post(GRAPHQL_URL(accountName, workspace), query)
).data

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When a GraphQL request fails, the response status is often 200 anyway, depending on the type of the error (mainly because there can be partial errors). So there would be no exception thrown here, but data might be undefined, or it might only have part of the information you requested. In those cases the response will contain a field called errors, so you can check for that here.

Suggested change
const { data } = (
await axios.post(GRAPHQL_URL(accountName, workspace), query)
).data
const { data, errors } = (
await axios.post(GRAPHQL_URL(accountName, workspace), query)
).data

More information on graphql errors here.

).data

const quoteResult = data.getQuote as Omit<QuoteMetricsData, 'costId'>
const costId = data.getUserByEmail?.[0].costId as string

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're using an optional operator here, so you're assuming that data.getUserByEmail might not be set, right? If that's a possibility then you shouldn't cast to string here, as it'll make costId's type not consider the use case where it will be undefined. Is it ok if costId isn't set? If it isn't it's better to bail early here, and if it's ok then you should let it be string | undefined.

@mtakeda mtakeda requested a review from a team July 24, 2023 11:52
mairatma
mairatma previously approved these changes Jul 24, 2023
Copy link

@mairatma mairatma left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lgtm, thanks! 👏

@mairatma
Copy link

@mtakeda don't forget to select the version bump within vtex-io-ci-cd bot's message at the further above in this PR, or that check will keep failing.

manifest.json Outdated
@@ -1,7 +1,7 @@
{
"vendor": "vtex",
"name": "b2b-quotes",
"version": "1.5.5",
"version": "1.5.6",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This shouldn't be changed here in the PR. It will be changed automatically by the vtex io bot once this PR is merged. You should revert this change, and just select the right type of version bump in the question post from vtex io bot.

@@ -1,6 +1,6 @@
{
"name": "vtex.b2b-quotes",
"version": "1.5.5",
"version": "1.5.6",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, please revert this line.

Comment on lines 98 to 99
buy_org_id: metricsData.organization,
buy_org_name: metricsData.organizationName,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've just noticed that this PR also has the typos:

Suggested change
buy_org_id: metricsData.organization,
buy_org_name: metricsData.organizationName,
buyer_org_id: metricsData.organization,
buyer_org_name: metricsData.organizationName,

Can you check if there are others and fix them? Same for the metrics for using quotes.

@sonarcloud
Copy link

sonarcloud bot commented Aug 3, 2023

Kudos, SonarCloud Quality Gate passed!    Quality Gate passed

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 0 Code Smells

0.0% 0.0% Coverage
0.0% 0.0% Duplication

warning The version of Java (11.0.17) you have used to run this analysis is deprecated and we will stop accepting it soon. Please update to at least Java 17.
Read more here

@mtakeda mtakeda merged commit 6112479 into master Aug 10, 2023
10 checks passed
@mtakeda mtakeda deleted the B2BTEAM-1256 branch August 10, 2023 18:39
@vtex-io-ci-cd
Copy link

vtex-io-ci-cd bot commented Aug 10, 2023

Your PR has been merged! App is being published. 🚀
Version 1.5.5 → 1.6.0

After the publishing process has been completed (check #vtex-io-releases) and doing A/B tests with the new version, you can deploy your release by running:

vtex deploy [email protected]

After that your app will be updated on all accounts.

For more information on the deployment process check the docs. 📖

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants