feat: harmonize log and metrics

This commit is contained in:
Michael Zetterberg
2025-04-17 07:16:11 +02:00
parent 858a81b16f
commit 5323a8e46e
58 changed files with 2324 additions and 4726 deletions

View File

@@ -2,6 +2,7 @@ import { type NextRequest, NextResponse } from "next/server"
import { env } from "@/env/server"
import { dt } from "@/lib/dt"
import { createCounter } from "@/server/telemetry"
import {
getEntries,
@@ -13,22 +14,16 @@ import {
} from "@/utils/sitemap"
import { contentstackSync } from "./sync"
import {
generateSitemapCounter,
generateSitemapFailCounter,
generateSitemapSuccessCounter,
saveEntriesCounter,
saveSitemapDataCounter,
saveSyncTokenCounter,
} from "./telemetry"
import { mapEntriesToSitemapData, mergeEntries } from "./utils"
export const dynamic = "force-dynamic"
export async function GET(request: NextRequest) {
const generateSitemapCounter = createCounter("sitemap", "generate")
const metricsGenerateSitemap = generateSitemapCounter.init()
try {
generateSitemapCounter.add(1)
console.info("sitemap.generate start")
metricsGenerateSitemap.start()
const headersList = request.headers
const secret = headersList.get("x-sitemap-sync-secret")
@@ -44,49 +39,46 @@ export async function GET(request: NextRequest) {
const responseData = await contentstackSync(syncToken)
const mergedEntries = mergeEntries(currentEntries, responseData.entries)
saveEntriesCounter.add(1, { entriesCount: mergedEntries.length })
console.info(
"sitemap.entries.save",
JSON.stringify({ entriesCount: mergedEntries.length })
)
const entriesSaveCounter = createCounter("sitemap", "entries.save")
const metricsEntriesSave = entriesSaveCounter.init({
entriesCount: mergedEntries.length,
})
metricsEntriesSave.start()
await saveEntries(mergedEntries)
metricsEntriesSave.success()
const sitemapData = mapEntriesToSitemapData(mergedEntries)
const lastUpdated = dt().utc().format()
saveSitemapDataCounter.add(1, {
sitemapEntriesCount: sitemapData.length,
})
console.info(
"sitemap.data.save",
JSON.stringify({
const saveDataCounter = createCounter("sitemap", "data.save")
const metricsDataSave = saveDataCounter.init({
sitemapEntriesCount: sitemapData.length,
lastUpdated,
})
)
metricsDataSave.start()
await saveSitemapData(sitemapData)
await saveLastUpdatedDate(lastUpdated)
metricsDataSave.success()
if (syncToken !== responseData.syncToken) {
saveSyncTokenCounter.add(1, {
const syncTokenSaveCounter = createCounter("sitemap", "syncToken.save")
const metricsSyncTokenSave = syncTokenSaveCounter.init({
syncToken: responseData.syncToken,
})
console.info(
"sitemap.synctoken.save",
JSON.stringify({ syncToken: responseData.syncToken })
)
metricsSyncTokenSave.start()
await saveSyncToken(responseData.syncToken)
metricsSyncTokenSave.success()
}
generateSitemapSuccessCounter.add(1)
metricsGenerateSitemap.success()
return NextResponse.json({
message: "Sitemap data generated and stored successfully!",
now: dt().utc().format(),
})
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : JSON.stringify(error)
generateSitemapFailCounter.add(1, { error: errorMessage })
console.error("sitemap.generate.fail", errorMessage)
metricsGenerateSitemap.fail(error)
return NextResponse.json(
{

View File

@@ -1,13 +1,7 @@
import { Region, Stack } from "contentstack"
import { env } from "@/env/server"
import {
syncEntriesCounter,
syncEntriesFailCounter,
syncEntriesPaginationCounter,
syncEntriesSuccessCounter,
} from "./telemetry"
import { createCounter } from "@/server/telemetry"
import type { SyncResponse } from "@/types/sitemap"
@@ -24,72 +18,50 @@ export async function contentstackSync(syncToken: string | null) {
const entries = []
const syncOptions = syncToken ? { sync_token: syncToken } : { init: true }
syncEntriesCounter.add(1, {
const entriesSyncCounter = createCounter("sitemap", "entries.sync")
const metricsEntriesSync = entriesSyncCounter.init({
environment,
...syncOptions,
})
console.info(
"sitemap.entries.sync start",
JSON.stringify({
environment,
...syncOptions,
})
)
metricsEntriesSync.start()
try {
let syncResponse: SyncResponse = await stack.sync(syncOptions)
entries.push(...syncResponse.items)
const entriesSyncPaginationCounter = createCounter(
"sitemap",
"entries.sync.pagination"
)
const metricsEntriesSyncPagination = entriesSyncPaginationCounter.init({
environment,
...syncOptions,
})
// Check if there is a pagination token, and fetch more data if needed
while (syncResponse.pagination_token && !syncResponse.sync_token) {
syncEntriesPaginationCounter.add(1, {
environment,
metricsEntriesSyncPagination.start({
paginationToken: syncResponse.pagination_token,
})
console.info(
"sitemap.entries.sync.pagination start",
JSON.stringify({
environment,
paginationToken: syncResponse.pagination_token,
})
)
syncResponse = await stack.sync({
pagination_token: syncResponse.pagination_token,
})
entries.push(...syncResponse.items)
syncEntriesPaginationCounter.add(1, {
environment,
metricsEntriesSyncPagination.success({
paginationToken: syncResponse.pagination_token,
entriesCount: syncResponse.items.length,
})
console.info(
"sitemap.entries.sync.pagination success",
JSON.stringify({
environment,
paginationToken: syncResponse.pagination_token,
entriesCount: syncResponse.items.length,
})
)
}
if (syncResponse.sync_token) {
syncEntriesSuccessCounter.add(1, {
environment,
...syncOptions,
metricsEntriesSync.success({
newSyncToken: syncResponse.sync_token,
entriesCount: entries.length,
})
console.info(
"sitemap.entries.sync success",
JSON.stringify({
environment,
...syncOptions,
newSyncToken: syncResponse.sync_token,
entriesCount: entries.length,
})
)
return {
syncToken: syncResponse.sync_token,
entries,
@@ -98,14 +70,7 @@ export async function contentstackSync(syncToken: string | null) {
throw new Error("No sync token received, something went wrong")
}
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : JSON.stringify(error)
syncEntriesFailCounter.add(1, {
environment,
...syncOptions,
error: errorMessage,
})
console.error("sitemap.entries.sync error", errorMessage)
metricsEntriesSync.fail(error)
throw new Error("Failed to sync entries")
}

View File

@@ -1,43 +0,0 @@
import { metrics } from "@opentelemetry/api"
const meter = metrics.getMeter("sitemap")
// OpenTelemetry metrics
export const generateSitemapCounter = meter.createCounter("sitemap.generate")
export const generateSitemapSuccessCounter = meter.createCounter(
"sitemap.generate-success"
)
export const generateSitemapFailCounter = meter.createCounter(
"sitemap.generate-fail"
)
export const syncEntriesCounter = meter.createCounter("sitemap.entries.sync")
export const syncEntriesSuccessCounter = meter.createCounter(
"sitemap.entries.sync-success"
)
export const syncEntriesFailCounter = meter.createCounter(
"sitemap.entries.sync-fail"
)
export const syncEntriesPaginationCounter = meter.createCounter(
"sitemap.entries.sync.pagination"
)
export const syncEntriesPaginationSuccessCounter = meter.createCounter(
"sitemap.entries.sync.pagination-success"
)
export const mergeEntriesCounter = meter.createCounter("sitemap.entries.merge")
export const mergeEntriesSuccessCounter = meter.createCounter(
"sitemap.entries.merge-success"
)
export const saveEntriesCounter = meter.createCounter("sitemap.entries.save")
export const transformEntriesCounter = meter.createCounter(
"sitemap.entries.transform"
)
export const transformEntriesSuccessCounter = meter.createCounter(
"sitemap.entries.transform-success"
)
export const saveSitemapDataCounter = meter.createCounter("sitemap.data.save")
export const saveSyncTokenCounter = meter.createCounter(
"sitemap.synctoken.save"
)

View File

@@ -1,32 +1,23 @@
import { Lang } from "@/constants/languages"
import { env } from "@/env/server"
import { createCounter } from "@/server/telemetry"
import { removeTrailingSlash } from "@/utils/url"
import {
mergeEntriesCounter,
mergeEntriesSuccessCounter,
transformEntriesCounter,
transformEntriesSuccessCounter,
} from "./telemetry"
import type { SitemapEntry, SyncItem } from "@/types/sitemap"
export function mergeEntries(
currentEntries: SyncItem[],
newEntries: SyncItem[]
) {
mergeEntriesCounter.add(1, {
const entriesMergeCounter = createCounter("sitemap", "entries.merge")
const metricsEntriesMerge = entriesMergeCounter.init({
currentEntriesCount: currentEntries.length,
newEntriesCount: newEntries.length,
})
console.info(
"sitemap.entries.merge start",
JSON.stringify({
currentEntriesCount: currentEntries.length,
newEntriesCount: newEntries.length,
})
)
metricsEntriesMerge.start()
const entries = [...currentEntries]
newEntries.forEach((entry) => {
const index = entries.findIndex(
@@ -40,27 +31,20 @@ export function mergeEntries(
}
})
mergeEntriesSuccessCounter.add(1, {
metricsEntriesMerge.success({
entriesCount: entries.length,
})
console.info(
"sitemap.entries.merge success",
JSON.stringify({
entriesCount: entries.length,
})
)
return entries
}
export function mapEntriesToSitemapData(entries: SyncItem[]) {
transformEntriesCounter.add(1, { entriesCount: entries.length })
console.info(
"sitemap.entries.transform start",
JSON.stringify({
const entriesTransformCounter = createCounter("sitemap", "entries.transform")
const metricsEntriesTransform = entriesTransformCounter.init({
entriesCount: entries.length,
})
)
metricsEntriesTransform.start()
const filteredEntries = filterEntriesToSitemapEntries(entries)
@@ -69,17 +53,10 @@ export function mapEntriesToSitemapData(entries: SyncItem[]) {
.map(([_, entries]) => mapEntriesToSitemapEntry(entries))
.filter((entry): entry is SitemapEntry => !!entry)
transformEntriesSuccessCounter.add(1, {
metricsEntriesTransform.success({
entriesCount: entries.length,
sitemapEntriesCount: sitemapEntries.length,
})
console.info(
"sitemap.entries.transform success",
JSON.stringify({
entriesCount: entries.length,
sitemapEntriesCount: sitemapEntries.length,
})
)
return sitemapEntries
}

View File

@@ -39,6 +39,7 @@ export default function HotelPin({
)}
</span>
<Typography variant="Body/Paragraph/mdRegular">
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
<p>{isNotAvailable ? "—" : formatPrice(intl, hotelPrice, currency)}</p>
</Typography>
</div>

View File

@@ -3,15 +3,18 @@
* https://jestjs.io/docs/configuration
*/
import nextJest from "next/jest.js"
import { createJsWithTsEsmPreset } from "ts-jest"
import type { Config } from "jest"
const presetConfig = createJsWithTsEsmPreset()
const createJestConfig = nextJest({
// Provide the path to your Next.js app to load next.config.js and .env files in your test environment
dir: "./",
})
const config: Config = {
const config = {
// All imported modules in your tests should be mocked automatically
// automock: false,
@@ -202,6 +205,8 @@ const config: Config = {
// Whether to use watchman for file crawling
// watchman: true,
}
...presetConfig,
} satisfies Config
export default createJestConfig(config)

View File

@@ -1,6 +1,8 @@
import "@testing-library/jest-dom/jest-globals"
import "@testing-library/jest-dom"
import { jest } from "@jest/globals"
jest.mock("next/navigation", () => ({
useRouter: jest.fn(),
usePathname: jest.fn().mockReturnValue("/"),

View File

@@ -15,8 +15,8 @@
"test:e2e:headless": "start-server-and-test test:setup http://127.0.0.1:3000/en/sponsoring \"cypress run --e2e\"",
"test:setup": "yarn build && yarn start",
"preinstall": "/bin/sh -c \"export $(cat .env.local | grep -v '^#' | xargs)\"",
"test": "jest",
"test:watch": "jest --watch",
"test": "node --experimental-vm-modules $(yarn bin jest)",
"test:watch": "node --experimental-vm-modules $(yarn bin jest) --watch",
"ci:build": "yarn lint && yarn test && yarn build",
"clean": "rm -rf .next",
"i18n:extract": "formatjs extract \"{actions,app,components,constants,hooks,i18n,lib,middlewares,server,stores,utils}/**/*.{ts,tsx}\" --format i18n/tooling/formatter.mjs --out-file i18n/tooling/extracted.json",
@@ -76,6 +76,7 @@
"embla-carousel-react": "^8.5.2",
"fast-deep-equal": "^3.1.3",
"fetch-retry": "^6.0.0",
"flat": "^6.0.1",
"framer-motion": "^11.3.28",
"fuse.js": "^7.1.0",
"graphql": "^16.8.1",
@@ -88,6 +89,7 @@
"ioredis": "^5.5.0",
"json-stable-stringify-without-jsonify": "^1.0.1",
"libphonenumber-js": "^1.10.60",
"lodash-es": "^4.17.21",
"nanoid": "^5.0.9",
"next": "^14.2.25",
"next-auth": "5.0.0-beta.19",
@@ -101,7 +103,6 @@
"react-international-phone": "^4.2.6",
"react-intl": "^6.6.8",
"react-to-print": "^3.0.2",
"secure-json-parse": "^4.0.0",
"server-only": "^0.0.1",
"slugify": "^1.6.6",
"sonner": "^1.7.0",
@@ -124,6 +125,7 @@
"@types/adm-zip": "^0.5.7",
"@types/jest": "^29.5.12",
"@types/json-stable-stringify-without-jsonify": "^1.0.2",
"@types/lodash-es": "^4",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
@@ -148,6 +150,7 @@
"react-material-symbols": "^4.4.0",
"schema-dts": "^1.1.2",
"start-server-and-test": "^2.0.3",
"ts-jest": "^29.3.2",
"ts-morph": "^25.0.1",
"ts-node": "^10.9.2",
"typescript": "5.4.5",

View File

@@ -1,12 +1,8 @@
import { metrics } from "@opentelemetry/api"
import sjson from "secure-json-parse"
import * as api from "@/lib/api"
import { getVerifiedUser } from "@/server/routers/user/query"
import { getMembershipNumber } from "@/server/routers/user/utils"
import { createCounter } from "@/server/telemetry"
import { router, safeProtectedServiceProcedure } from "@/server/trpc"
import { isValidSession } from "@/utils/session"
import {
addPackageInput,
cancelBookingInput,
@@ -18,75 +14,6 @@ import {
} from "./input"
import { bookingConfirmationSchema, createBookingSchema } from "./output"
import type { Session } from "next-auth"
const meter = metrics.getMeter("trpc.bookings")
const createBookingCounter = meter.createCounter("trpc.bookings.create")
const createBookingSuccessCounter = meter.createCounter(
"trpc.bookings.create-success"
)
const createBookingFailCounter = meter.createCounter(
"trpc.bookings.create-fail"
)
const priceChangeCounter = meter.createCounter("trpc.bookings.price-change")
const priceChangeSuccessCounter = meter.createCounter(
"trpc.bookings.price-change-success"
)
const priceChangeFailCounter = meter.createCounter(
"trpc.bookings.price-change-fail"
)
const cancelBookingCounter = meter.createCounter("trpc.bookings.cancel")
const cancelBookingSuccessCounter = meter.createCounter(
"trpc.bookings.cancel-success"
)
const cancelBookingFailCounter = meter.createCounter(
"trpc.bookings.cancel-fail"
)
const addPackageCounter = meter.createCounter("trpc.bookings.add-package")
const addPackageSuccessCounter = meter.createCounter(
"trpc.bookings.add-package-success"
)
const addPackageFailCounter = meter.createCounter(
"trpc.bookings.add-package-fail"
)
const guaranteeBookingCounter = meter.createCounter("trpc.bookings.guarantee")
const guaranteeBookingSuccessCounter = meter.createCounter(
"trpc.bookings.guarantee-success"
)
const guaranteeBookingFailCounter = meter.createCounter(
"trpc.bookings.guarantee-fail"
)
const updateBookingCounter = meter.createCounter("trpc.bookings.update-booking")
const updateBookingSuccessCounter = meter.createCounter(
"trpc.bookings.update-booking-success"
)
const updateBookingFailCounter = meter.createCounter(
"trpc.bookings.update-booking-fail"
)
const removePackageCounter = meter.createCounter("trpc.bookings.remove-package")
const removePackageSuccessCounter = meter.createCounter(
"trpc.bookings.remove-package-success"
)
const removePackageFailCounter = meter.createCounter(
"trpc.bookings.remove-package-fail"
)
async function getMembershipNumber(
session: Session | null
): Promise<string | undefined> {
if (!isValidSession(session)) return undefined
const verifiedUser = await getVerifiedUser({ session })
if (!verifiedUser || "error" in verifiedUser) {
return undefined
}
return verifiedUser.data.membershipNumber
}
export const bookingMutationRouter = router({
create: safeProtectedServiceProcedure
.input(createBookingInput)
@@ -95,22 +22,17 @@ export const bookingMutationRouter = router({
const { language, ...inputWithoutLang } = input
const { hotelId, checkInDate, checkOutDate } = inputWithoutLang
const loggingAttributes = {
const createBookingCounter = createCounter("trpc.booking", "create")
const metricsCreateBooking = createBookingCounter.init({
membershipNumber: await getMembershipNumber(ctx.session),
checkInDate,
checkOutDate,
hotelId,
language,
}
createBookingCounter.add(1, loggingAttributes)
console.info(
"api.booking.create start",
JSON.stringify({
query: loggingAttributes,
})
)
metricsCreateBooking.start()
const headers = {
Authorization: `Bearer ${accessToken}`,
}
@@ -125,29 +47,9 @@ export const bookingMutationRouter = router({
)
if (!apiResponse.ok) {
const text = await apiResponse.text()
createBookingFailCounter.add(1, {
hotelId,
checkInDate,
checkOutDate,
error_type: "http_error",
error: JSON.stringify({
status: apiResponse.status,
}),
})
console.error(
"api.booking.create error",
JSON.stringify({
query: loggingAttributes,
error: {
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
},
})
)
await metricsCreateBooking.httpError(apiResponse)
const apiJson = sjson.safeParse(text)
const apiJson = await apiResponse.json()
if ("errors" in apiJson && apiJson.errors.length) {
const error = apiJson.errors[0]
return { error: true, cause: error.code } as const
@@ -160,45 +62,25 @@ export const bookingMutationRouter = router({
const verifiedData = createBookingSchema.safeParse(apiJson)
if (!verifiedData.success) {
createBookingFailCounter.add(1, {
hotelId,
checkInDate,
checkOutDate,
error_type: "validation_error",
})
console.error(
"api.booking.create validation error",
JSON.stringify({
query: loggingAttributes,
error: verifiedData.error,
})
)
metricsCreateBooking.validationError(verifiedData.error)
return null
}
createBookingSuccessCounter.add(1, {
hotelId,
checkInDate,
checkOutDate,
})
console.info(
"api.booking.create success",
JSON.stringify({
query: loggingAttributes,
})
)
metricsCreateBooking.success()
return verifiedData.data
}),
priceChange: safeProtectedServiceProcedure
.input(priceChangeInput)
.mutation(async function ({ ctx, input }) {
const accessToken = ctx.session?.token.access_token ?? ctx.serviceToken
const { confirmationNumber } = input
priceChangeCounter.add(1, { confirmationNumber })
const priceChangeCounter = createCounter("trpc.booking", "price-change")
const metricsPriceChange = priceChangeCounter.init({ confirmationNumber })
metricsPriceChange.start()
const accessToken = ctx.session?.token.access_token ?? ctx.serviceToken
const headers = {
Authorization: `Bearer ${accessToken}`,
@@ -213,46 +95,18 @@ export const bookingMutationRouter = router({
)
if (!apiResponse.ok) {
const text = await apiResponse.text()
priceChangeFailCounter.add(1, {
confirmationNumber,
error_type: "http_error",
error: JSON.stringify({
status: apiResponse.status,
}),
})
console.error(
"api.booking.priceChange error",
JSON.stringify({
query: { confirmationNumber },
error: {
status: apiResponse.status,
statusText: apiResponse.statusText,
error: text,
},
})
)
await metricsPriceChange.httpError(apiResponse)
return null
}
const apiJson = await apiResponse.json()
const verifiedData = createBookingSchema.safeParse(apiJson)
if (!verifiedData.success) {
priceChangeFailCounter.add(1, {
confirmationNumber,
error_type: "validation_error",
})
console.error(
"api.booking.priceChange validation error",
JSON.stringify({
query: { confirmationNumber },
error: verifiedData.error,
})
)
metricsPriceChange.validationError(verifiedData.error)
return null
}
priceChangeSuccessCounter.add(1, { confirmationNumber })
metricsPriceChange.success()
return verifiedData.data
}),
@@ -262,6 +116,14 @@ export const bookingMutationRouter = router({
const accessToken = ctx.session?.token.access_token ?? ctx.serviceToken
const { confirmationNumber, language } = input
const cancelBookingCounter = createCounter("trpc.booking", "cancel")
const metricsCancelBooking = cancelBookingCounter.init({
confirmationNumber,
language,
})
metricsCancelBooking.start()
const headers = {
Authorization: `Bearer ${accessToken}`,
}
@@ -271,21 +133,6 @@ export const bookingMutationRouter = router({
reason: "WEB-CANCEL",
}
const loggingAttributes = {
confirmationNumber,
language,
}
cancelBookingCounter.add(1, loggingAttributes)
console.info(
"api.booking.cancel start",
JSON.stringify({
request: loggingAttributes,
headers,
})
)
const apiResponse = await api.remove(
api.endpoints.v1.Booking.cancel(confirmationNumber),
{
@@ -296,25 +143,7 @@ export const bookingMutationRouter = router({
)
if (!apiResponse.ok) {
const text = await apiResponse.text()
cancelBookingFailCounter.add(1, {
confirmationNumber,
error_type: "http_error",
error: JSON.stringify({
status: apiResponse.status,
}),
})
console.error(
"api.booking.cancel error",
JSON.stringify({
error: {
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
},
query: loggingAttributes,
})
)
await metricsCancelBooking.httpError(apiResponse)
return false
}
@@ -323,29 +152,11 @@ export const bookingMutationRouter = router({
const verifiedData = createBookingSchema.safeParse(apiJson)
if (!verifiedData.success) {
cancelBookingFailCounter.add(1, {
confirmationNumber,
error_type: "validation_error",
})
console.error(
"api.booking.cancel validation error",
JSON.stringify({
query: loggingAttributes,
error: verifiedData.error,
})
)
metricsCancelBooking.validationError(verifiedData.error)
return null
}
cancelBookingSuccessCounter.add(1, loggingAttributes)
console.info(
"api.booking.cancel success",
JSON.stringify({
query: loggingAttributes,
})
)
metricsCancelBooking.success()
return verifiedData.data
}),
@@ -355,7 +166,10 @@ export const bookingMutationRouter = router({
const accessToken = ctx.session?.token.access_token ?? ctx.serviceToken
const { confirmationNumber, ...body } = input
addPackageCounter.add(1, { confirmationNumber })
const addPackageCounter = createCounter("trpc.booking", "package.add")
const metricsAddPackage = addPackageCounter.init({ confirmationNumber })
metricsAddPackage.start()
const headers = {
Authorization: `Bearer ${accessToken}`,
@@ -370,46 +184,18 @@ export const bookingMutationRouter = router({
)
if (!apiResponse.ok) {
const text = await apiResponse.text()
addPackageFailCounter.add(1, {
confirmationNumber,
error_type: "http_error",
error: JSON.stringify({
status: apiResponse.status,
}),
})
console.error(
"api.booking.addPackage error",
JSON.stringify({
query: { confirmationNumber },
error: {
status: apiResponse.status,
statusText: apiResponse.statusText,
error: text,
},
})
)
await metricsAddPackage.httpError(apiResponse)
return null
}
const apiJson = await apiResponse.json()
const verifiedData = createBookingSchema.safeParse(apiJson)
if (!verifiedData.success) {
addPackageFailCounter.add(1, {
confirmationNumber,
error_type: "validation_error",
})
console.error(
"api.booking.addPackage validation error",
JSON.stringify({
query: { confirmationNumber },
error: verifiedData.error,
})
)
metricsAddPackage.validationError(verifiedData.error)
return null
}
addPackageSuccessCounter.add(1, { confirmationNumber })
metricsAddPackage.success()
return verifiedData.data
}),
@@ -419,7 +205,12 @@ export const bookingMutationRouter = router({
const accessToken = ctx.session?.token.access_token ?? ctx.serviceToken
const { confirmationNumber, language, ...body } = input
guaranteeBookingCounter.add(1, { confirmationNumber })
const guaranteeBookingCounter = createCounter("trpc.booking", "guarantee")
const metricsGuaranteeBooking = guaranteeBookingCounter.init({
confirmationNumber,
})
metricsGuaranteeBooking.start()
const headers = {
Authorization: `Bearer ${accessToken}`,
@@ -435,46 +226,18 @@ export const bookingMutationRouter = router({
)
if (!apiResponse.ok) {
const text = await apiResponse.text()
guaranteeBookingFailCounter.add(1, {
confirmationNumber,
error_type: "http_error",
error: JSON.stringify({
status: apiResponse.status,
}),
})
console.error(
"api.booking.guarantee error",
JSON.stringify({
query: { confirmationNumber },
error: {
status: apiResponse.status,
statusText: apiResponse.statusText,
error: text,
},
})
)
await metricsGuaranteeBooking.httpError(apiResponse)
return null
}
const apiJson = await apiResponse.json()
const verifiedData = createBookingSchema.safeParse(apiJson)
if (!verifiedData.success) {
guaranteeBookingFailCounter.add(1, {
confirmationNumber,
error_type: "validation_error",
})
console.error(
"api.booking.guarantee validation error",
JSON.stringify({
query: { confirmationNumber },
error: verifiedData.error,
})
)
metricsGuaranteeBooking.validationError(verifiedData.error)
return null
}
guaranteeBookingSuccessCounter.add(1, { confirmationNumber })
metricsGuaranteeBooking.success()
return verifiedData.data
}),
@@ -484,7 +247,12 @@ export const bookingMutationRouter = router({
const accessToken = ctx.session?.token.access_token || ctx.serviceToken
const { confirmationNumber, ...body } = input
updateBookingCounter.add(1, { confirmationNumber })
const updateBookingCounter = createCounter("trpc.booking", "update")
const metricsUpdateBooking = updateBookingCounter.init({
confirmationNumber,
})
metricsUpdateBooking.start()
const apiResponse = await api.put(
api.endpoints.v1.Booking.booking(confirmationNumber),
@@ -497,25 +265,7 @@ export const bookingMutationRouter = router({
)
if (!apiResponse.ok) {
const text = await apiResponse.text()
updateBookingFailCounter.add(1, {
confirmationNumber,
error_type: "http_error",
error: JSON.stringify({
status: apiResponse.status,
}),
})
console.error(
"api.booking.updateBooking error",
JSON.stringify({
query: { confirmationNumber },
error: {
status: apiResponse.status,
statusText: apiResponse.statusText,
error: text,
},
})
)
await metricsUpdateBooking.httpError(apiResponse)
return null
}
@@ -523,21 +273,11 @@ export const bookingMutationRouter = router({
const verifiedData = bookingConfirmationSchema.safeParse(apiJson)
if (!verifiedData.success) {
updateBookingFailCounter.add(1, {
confirmationNumber,
error_type: "validation_error",
})
console.error(
"api.booking.updateBooking validation error",
JSON.stringify({
query: { confirmationNumber },
error: verifiedData.error,
})
)
metricsUpdateBooking.validationError(verifiedData.error)
return null
}
updateBookingSuccessCounter.add(1, { confirmationNumber })
metricsUpdateBooking.success()
return verifiedData.data
}),
@@ -547,25 +287,21 @@ export const bookingMutationRouter = router({
const accessToken = ctx.session?.token.access_token ?? ctx.serviceToken
const { confirmationNumber, codes, language } = input
const headers = {
Authorization: `Bearer ${accessToken}`,
}
const loggingAttributes = {
const removePackageCounter = createCounter(
"trpc.booking",
"package.remove"
)
const metricsRemovePackage = removePackageCounter.init({
confirmationNumber,
codes,
language,
}
removePackageCounter.add(1, loggingAttributes)
console.info(
"api.booking.remove-package start",
JSON.stringify({
request: loggingAttributes,
headers,
})
)
metricsRemovePackage.start()
const headers = {
Authorization: `Bearer ${accessToken}`,
}
const apiResponse = await api.remove(
api.endpoints.v1.Booking.packages(confirmationNumber),
@@ -576,36 +312,11 @@ export const bookingMutationRouter = router({
)
if (!apiResponse.ok) {
const text = await apiResponse.text()
removePackageFailCounter.add(1, {
confirmationNumber,
error_type: "http_error",
error: JSON.stringify({
status: apiResponse.status,
}),
})
console.error(
"api.booking.remove-package error",
JSON.stringify({
error: {
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
},
query: loggingAttributes,
})
)
await metricsRemovePackage.httpError(apiResponse)
return false
}
removePackageSuccessCounter.add(1, loggingAttributes)
console.info(
"api.booking.remove-package success",
JSON.stringify({
query: loggingAttributes,
})
)
metricsRemovePackage.success()
return true
}),

View File

@@ -1,7 +1,6 @@
import { metrics } from "@opentelemetry/api"
import * as api from "@/lib/api"
import { badRequestError, serverErrorByStatus } from "@/server/errors/trpc"
import { createCounter } from "@/server/telemetry"
import {
router,
safeProtectedServiceProcedure,
@@ -18,19 +17,6 @@ import {
import { bookingConfirmationSchema, createBookingSchema } from "./output"
import { getBookedHotelRoom } from "./utils"
const meter = metrics.getMeter("trpc.booking")
const getBookingCounter = meter.createCounter("trpc.booking.get")
const getBookingSuccessCounter = meter.createCounter("trpc.booking.get-success")
const getBookingFailCounter = meter.createCounter("trpc.booking.get-fail")
const getBookingStatusCounter = meter.createCounter("trpc.booking.status")
const getBookingStatusSuccessCounter = meter.createCounter(
"trpc.booking.status-success"
)
const getBookingStatusFailCounter = meter.createCounter(
"trpc.booking.status-fail"
)
export const bookingQueryRouter = router({
get: safeProtectedServiceProcedure
.input(getBookingInput)
@@ -38,7 +24,10 @@ export const bookingQueryRouter = router({
ctx,
input: { confirmationNumber, lang: inputLang },
}) {
getBookingCounter.add(1, { confirmationNumber })
const getBookingCounter = createCounter("trpc.booking", "get")
const metricsGetBooking = getBookingCounter.init({ confirmationNumber })
metricsGetBooking.start()
let lang = ctx.lang ?? inputLang
@@ -54,23 +43,7 @@ export const bookingQueryRouter = router({
)
if (!apiResponse.ok) {
const responseMessage = await apiResponse.text()
getBookingFailCounter.add(1, {
confirmationNumber,
error_type: "http_error",
error: responseMessage,
})
console.error(
"api.booking.confirmation error",
JSON.stringify({
query: { confirmationNumber },
error: {
status: apiResponse.status,
statusText: apiResponse.statusText,
text: responseMessage,
},
})
)
await metricsGetBooking.httpError(apiResponse)
// If the booking is not found, return null.
// This scenario is expected to happen when a logged in user trying to access a booking that doesn't belong to them.
@@ -84,18 +57,7 @@ export const bookingQueryRouter = router({
const apiJson = await apiResponse.json()
const booking = bookingConfirmationSchema.safeParse(apiJson)
if (!booking.success) {
getBookingFailCounter.add(1, {
confirmationNumber,
error_type: "validation_error",
error: JSON.stringify(booking.error),
})
console.error(
"api.booking.confirmation validation error",
JSON.stringify({
query: { confirmationNumber },
error: booking.error,
})
)
metricsGetBooking.validationError(booking.error)
throw badRequestError()
}
@@ -109,34 +71,17 @@ export const bookingQueryRouter = router({
)
if (!hotelData) {
getBookingFailCounter.add(1, {
confirmationNumber,
metricsGetBooking.dataError(
`Failed to get hotel data for ${booking.data.hotelId}`,
{
hotelId: booking.data.hotelId,
error_type: "http_error",
error: "Couldn`t get hotel",
})
console.error(
"api.booking.confirmation error",
JSON.stringify({
query: { confirmationNumber, hotelId: booking.data.hotelId },
error: {
status: apiResponse.status,
statusText: apiResponse.statusText,
text: "Couldn`t get hotel",
},
})
}
)
throw serverErrorByStatus(404)
}
getBookingSuccessCounter.add(1, { confirmationNumber })
console.info(
"api.booking.confirmation success",
JSON.stringify({
query: { confirmationNumber },
})
)
metricsGetBooking.success()
return {
...hotelData,
@@ -152,7 +97,13 @@ export const bookingQueryRouter = router({
input,
}) {
const { confirmationNumber } = input
getBookingStatusCounter.add(1, { confirmationNumber })
const getBookingStatusCounter = createCounter("trpc.booking", "status")
const metricsGetBookingStatus = getBookingStatusCounter.init({
confirmationNumber,
})
metricsGetBookingStatus.start()
const apiResponse = await api.get(
api.endpoints.v1.Booking.status(confirmationNumber),
@@ -164,52 +115,18 @@ export const bookingQueryRouter = router({
)
if (!apiResponse.ok) {
const responseMessage = await apiResponse.text()
getBookingStatusFailCounter.add(1, {
confirmationNumber,
error_type: "http_error",
error: responseMessage,
})
console.error(
"api.booking.status error",
JSON.stringify({
query: { confirmationNumber },
error: {
status: apiResponse.status,
statusText: apiResponse.statusText,
text: responseMessage,
},
})
)
await metricsGetBookingStatus.httpError(apiResponse)
throw serverErrorByStatus(apiResponse.status, apiResponse)
}
const apiJson = await apiResponse.json()
const verifiedData = createBookingSchema.safeParse(apiJson)
if (!verifiedData.success) {
getBookingStatusFailCounter.add(1, {
confirmationNumber,
error_type: "validation_error",
error: JSON.stringify(verifiedData.error),
})
console.error(
"api.booking.status validation error",
JSON.stringify({
query: { confirmationNumber },
error: verifiedData.error,
})
)
metricsGetBookingStatus.validationError(verifiedData.error)
throw badRequestError()
}
getBookingStatusSuccessCounter.add(1, { confirmationNumber })
console.info(
"api.booking.status success",
JSON.stringify({
query: { confirmationNumber },
})
)
metricsGetBookingStatus.success()
return verifiedData.data
}),

View File

@@ -1,11 +1,10 @@
import { metrics } from "@opentelemetry/api"
import {
GetAccountPage,
GetAccountPageRefs,
} from "@/lib/graphql/Query/AccountPage/AccountPage.graphql"
import { request } from "@/lib/graphql/request"
import { notFound } from "@/server/errors/trpc"
import { createCounter } from "@/server/telemetry"
import { contentstackExtendedProcedureUID, router } from "@/server/trpc"
import {
@@ -26,36 +25,17 @@ import type {
GetAccountPageSchema,
} from "@/types/trpc/routers/contentstack/accountPage"
const meter = metrics.getMeter("trpc.accountPage")
// OpenTelemetry metrics
const getAccountPageRefsCounter = meter.createCounter(
"trpc.contentstack.accountPage.get"
)
const getAccountPageRefsSuccessCounter = meter.createCounter(
"trpc.contentstack.accountPage.get-success"
)
const getAccountPageRefsFailCounter = meter.createCounter(
"trpc.contentstack.accountPage.get-fail"
)
const getAccountPageCounter = meter.createCounter(
"trpc.contentstack.accountPage.get"
)
const getAccountPageSuccessCounter = meter.createCounter(
"trpc.contentstack.accountPage.get-success"
)
const getAccountPageFailCounter = meter.createCounter(
"trpc.contentstack.accountPage.get-fail"
)
export const accountPageQueryRouter = router({
get: contentstackExtendedProcedureUID.query(async ({ ctx }) => {
const { lang, uid } = ctx
getAccountPageRefsCounter.add(1, { lang, uid })
console.info(
"contentstack.accountPage.refs start",
JSON.stringify({ query: { lang, uid } })
const getAccountPageRefsCounter = createCounter(
"trpc.contentstack",
"accountPage.get.refs"
)
const metricsRefs = getAccountPageRefsCounter.init({ lang, uid })
metricsRefs.start()
const refsResponse = await request<GetAccountPageRefsSchema>(
GetAccountPageRefs,
{
@@ -70,19 +50,7 @@ export const accountPageQueryRouter = router({
if (!refsResponse.data) {
const notFoundError = notFound(refsResponse)
getAccountPageRefsFailCounter.add(1, {
lang,
uid,
error_type: "not_found",
error: JSON.stringify({ code: notFoundError.code }),
})
console.error(
"contentstack.accountPage.refs not found error",
JSON.stringify({
query: { lang, uid },
error: { code: notFoundError.code },
})
)
metricsRefs.noDataError()
throw notFoundError
}
@@ -90,19 +58,7 @@ export const accountPageQueryRouter = router({
refsResponse.data
)
if (!validatedAccountPageRefs.success) {
getAccountPageRefsFailCounter.add(1, {
lang,
uid,
error_type: "validation_error",
error: JSON.stringify(validatedAccountPageRefs.error),
})
console.error(
"contentstack.accountPage.refs validation error",
JSON.stringify({
query: { lang, uid },
error: validatedAccountPageRefs.error,
})
)
metricsRefs.validationError(validatedAccountPageRefs.error)
return null
}
@@ -112,12 +68,16 @@ export const accountPageQueryRouter = router({
generateTagsFromSystem(lang, connections),
generateTag(lang, validatedAccountPageRefs.data.account_page.system.uid),
].flat()
getAccountPageRefsSuccessCounter.add(1, { lang, uid, tags })
getAccountPageCounter.add(1, { lang, uid })
console.info(
"contentstack.accountPage start",
JSON.stringify({ query: { lang, uid } })
metricsRefs.success()
const getAccountPageCounter = createCounter(
"trpc.contentstack",
"accountPage.get"
)
const metrics = getAccountPageCounter.init({ lang, uid })
metrics.start()
const response = await request<GetAccountPageSchema>(
GetAccountPage,
{
@@ -132,45 +92,18 @@ export const accountPageQueryRouter = router({
if (!response.data) {
const notFoundError = notFound(response)
getAccountPageFailCounter.add(1, {
lang,
uid,
error_type: "not_found",
error: JSON.stringify({ code: notFoundError.code }),
})
console.error(
"contentstack.accountPage not found error",
JSON.stringify({
query: { lang, uid },
error: { code: notFoundError.code },
})
)
metrics.noDataError()
throw notFoundError
}
const validatedAccountPage = accountPageSchema.safeParse(response.data)
if (!validatedAccountPage.success) {
getAccountPageFailCounter.add(1, {
lang,
uid,
error_type: "validation_error",
error: JSON.stringify(validatedAccountPage.error),
})
console.error(
"contentstack.accountPage validation error",
JSON.stringify({
query: { lang, uid },
error: validatedAccountPage.error,
})
)
metrics.validationError(validatedAccountPage.error)
return null
}
getAccountPageSuccessCounter.add(1, { lang, uid })
console.info(
"contentstack.accountPage success",
JSON.stringify({ query: { lang, uid } })
)
metrics.success()
const parsedtitle = response.data.account_page.title
.replaceAll(" ", "")

View File

@@ -17,6 +17,7 @@ import {
} from "@/lib/graphql/Query/SiteConfig.graphql"
import { request } from "@/lib/graphql/request"
import { notFound } from "@/server/errors/trpc"
import { createCounter } from "@/server/telemetry"
import { contentstackBaseProcedure, router } from "@/server/trpc"
import { langInput } from "@/server/utils"
@@ -43,37 +44,6 @@ import {
validateFooterConfigSchema,
validateFooterRefConfigSchema,
} from "./output"
import {
getContactConfigCounter,
getContactConfigFailCounter,
getContactConfigSuccessCounter,
getCurrentFooterCounter,
getCurrentFooterFailCounter,
getCurrentFooterRefCounter,
getCurrentFooterSuccessCounter,
getCurrentHeaderCounter,
getCurrentHeaderFailCounter,
getCurrentHeaderRefCounter,
getCurrentHeaderSuccessCounter,
getFooterCounter,
getFooterFailCounter,
getFooterRefCounter,
getFooterRefFailCounter,
getFooterRefSuccessCounter,
getFooterSuccessCounter,
getHeaderCounter,
getHeaderFailCounter,
getHeaderRefsCounter,
getHeaderRefsFailCounter,
getHeaderRefsSuccessCounter,
getHeaderSuccessCounter,
getSiteConfigCounter,
getSiteConfigFailCounter,
getSiteConfigRefCounter,
getSiteConfigRefFailCounter,
getSiteConfigRefSuccessCounter,
getSiteConfigSuccessCounter,
} from "./telemetry"
import {
getAlertPhoneContactData,
getConnections,
@@ -96,11 +66,14 @@ import type {
import type { Lang } from "@/constants/languages"
const getContactConfig = cache(async (lang: Lang) => {
getContactConfigCounter.add(1, { lang })
console.info(
"contentstack.contactConfig start",
JSON.stringify({ query: { lang } })
const getContactConfigCounter = createCounter(
"trpc.contentstack",
"contactConfig.get"
)
const metricsGetContactConfig = getContactConfigCounter.init({ lang })
metricsGetContactConfig.start()
const response = await request<ContactConfigData>(
GetContactConfig,
{
@@ -114,46 +87,20 @@ const getContactConfig = cache(async (lang: Lang) => {
if (!response.data) {
const notFoundError = notFound(response)
getContactConfigFailCounter.add(1, {
lang,
error_type: "not_found",
error: JSON.stringify({ code: notFoundError.code }),
})
console.error(
"contentstack.config not found error",
JSON.stringify({ query: { lang }, error: { code: notFoundError.code } })
)
metricsGetContactConfig.noDataError()
throw notFoundError
}
const validatedContactConfigConfig = validateContactConfigSchema.safeParse(
response.data
)
const verifiedData = validateContactConfigSchema.safeParse(response.data)
if (!validatedContactConfigConfig.success) {
getContactConfigFailCounter.add(1, {
lang,
error_type: "validation_error",
error: JSON.stringify(validatedContactConfigConfig.error),
})
console.error(
"contentstack.contactConfig validation error",
JSON.stringify({
query: { lang },
error: validatedContactConfigConfig.error,
})
)
if (!verifiedData.success) {
metricsGetContactConfig.validationError(verifiedData.error)
return null
}
getContactConfigSuccessCounter.add(1, { lang })
console.info(
"contentstack.contactConfig success",
JSON.stringify({ query: { lang } })
)
return validatedContactConfigConfig.data.all_contact_config.items[0]
metricsGetContactConfig.success()
return verifiedData.data.all_contact_config.items[0]
})
export const baseQueryRouter = router({
@@ -162,11 +109,14 @@ export const baseQueryRouter = router({
}),
header: contentstackBaseProcedure.query(async ({ ctx }) => {
const { lang } = ctx
getHeaderRefsCounter.add(1, { lang })
console.info(
"contentstack.header.refs start",
JSON.stringify({ query: { lang } })
const getHeaderRefsCounter = createCounter(
"trpc.contentstack",
"header.get.refs"
)
const metricsGetHeaderRefs = getHeaderRefsCounter.init({ lang })
metricsGetHeaderRefs.start()
const responseRef = await request<GetHeaderRefs>(
GetHeaderRef,
@@ -181,56 +131,25 @@ export const baseQueryRouter = router({
if (!responseRef.data) {
const notFoundError = notFound(responseRef)
getHeaderRefsFailCounter.add(1, {
lang,
error_type: "not_found",
error: JSON.stringify({ code: notFoundError.code }),
})
console.error(
"contentstack.header.refs not found error",
JSON.stringify({
query: {
lang,
},
error: { code: notFoundError.code },
})
)
metricsGetHeaderRefs.noDataError()
throw notFoundError
}
const validatedHeaderRefs = headerRefsSchema.safeParse(responseRef.data)
if (!validatedHeaderRefs.success) {
getHeaderRefsFailCounter.add(1, {
lang,
error_type: "validation_error",
error: JSON.stringify(validatedHeaderRefs.error),
})
console.error(
"contentstack.header.refs validation error",
JSON.stringify({
query: {
lang,
},
error: validatedHeaderRefs.error,
})
)
metricsGetHeaderRefs.validationError(validatedHeaderRefs.error)
return null
}
getHeaderRefsSuccessCounter.add(1, { lang })
console.info(
"contentstack.header.refs success",
JSON.stringify({ query: { lang } })
)
metricsGetHeaderRefs.success()
const connections = getConnections(validatedHeaderRefs.data)
getHeaderCounter.add(1, { lang })
console.info(
"contentstack.header start",
JSON.stringify({ query: { lang } })
)
const getHeaderCounter = createCounter("trpc.contentstack", "header.get")
const metricsGetHeader = getHeaderCounter.init({ lang })
metricsGetHeader.start()
const tags = [
generateTagsFromSystem(lang, connections),
@@ -245,43 +164,18 @@ export const baseQueryRouter = router({
if (!response.data) {
const notFoundError = notFound(response)
getHeaderFailCounter.add(1, {
lang,
error_type: "not_found",
error: JSON.stringify({ code: notFoundError.code }),
})
console.error(
"contentstack.header not found error",
JSON.stringify({
query: { lang },
error: { code: notFoundError.code },
})
)
metricsGetHeader.noDataError()
throw notFoundError
}
const validatedHeaderConfig = headerSchema.safeParse(response.data)
if (!validatedHeaderConfig.success) {
getHeaderFailCounter.add(1, {
lang,
error_type: "validation_error",
error: JSON.stringify(validatedHeaderConfig.error),
})
console.error(
"contentstack.header validation error",
JSON.stringify({
query: { lang },
error: validatedHeaderConfig.error,
})
)
metricsGetHeader.validationError(validatedHeaderConfig.error)
return null
}
getHeaderSuccessCounter.add(1, { lang })
console.info(
"contentstack.header success",
JSON.stringify({ query: { lang } })
)
metricsGetHeader.success()
return {
data: validatedHeaderConfig.data.header,
@@ -290,11 +184,16 @@ export const baseQueryRouter = router({
currentHeader: contentstackBaseProcedure
.input(langInput)
.query(async ({ input }) => {
getCurrentHeaderRefCounter.add(1, { lang: input.lang })
console.info(
"contentstack.currentHeader.ref start",
JSON.stringify({ query: { lang: input.lang } })
const getCurrentHeaderRefsCounter = createCounter(
"trpc.contentstack",
"currentHeader.get.refs"
)
const metricsGetCurrentHeaderRefs = getCurrentHeaderRefsCounter.init({
lang: input.lang,
})
metricsGetCurrentHeaderRefs.start()
const responseRef = await request<CurrentHeaderRefDataRaw>(
GetCurrentHeaderRef,
{
@@ -305,13 +204,16 @@ export const baseQueryRouter = router({
ttl: "max",
}
)
getCurrentHeaderCounter.add(1, { lang: input.lang })
console.info(
"contentstack.currentHeader start",
JSON.stringify({
query: { lang: input.lang },
})
const getCurrentHeaderCounter = createCounter(
"trpc.contentstack",
"currentHeader.get"
)
const metricsGetCurrentHeader = getCurrentHeaderCounter.init({
lang: input.lang,
})
metricsGetCurrentHeader.start()
const currentHeaderUID =
responseRef.data.all_current_header.items[0].system.uid
@@ -327,20 +229,7 @@ export const baseQueryRouter = router({
if (!response.data) {
const notFoundError = notFound(response)
getCurrentHeaderFailCounter.add(1, {
lang: input.lang,
error_type: "not_found",
error: JSON.stringify({ code: notFoundError.code }),
})
console.error(
"contentstack.currentHeader not found error",
JSON.stringify({
query: {
lang: input.lang,
},
error: { code: notFoundError.code },
})
)
metricsGetCurrentHeader.noDataError()
throw notFoundError
}
@@ -349,40 +238,27 @@ export const baseQueryRouter = router({
)
if (!validatedHeaderConfig.success) {
getCurrentHeaderFailCounter.add(1, {
lang: input.lang,
error_type: "validation_error",
error: JSON.stringify(validatedHeaderConfig.error),
})
console.error(
"contentstack.currentHeader validation error",
JSON.stringify({
query: {
lang: input.lang,
},
error: validatedHeaderConfig.error,
})
)
metricsGetCurrentHeader.validationError(validatedHeaderConfig.error)
return null
}
getCurrentHeaderSuccessCounter.add(1, { lang: input.lang })
console.info(
"contentstack.currentHeader success",
JSON.stringify({
query: { lang: input.lang },
})
)
metricsGetCurrentHeader.success()
return validatedHeaderConfig.data
}),
currentFooter: contentstackBaseProcedure
.input(langInput)
.query(async ({ input }) => {
getCurrentFooterRefCounter.add(1, { lang: input.lang })
console.info(
"contentstack.currentFooter.ref start",
JSON.stringify({ query: { lang: input.lang } })
const getCurrentFooterRefsCounter = createCounter(
"trpc.contentstack",
"currentFooter.get.refs"
)
const metricsGetCurrentFooterRefs = getCurrentFooterRefsCounter.init({
lang: input.lang,
})
metricsGetCurrentFooterRefs.start()
const responseRef = await request<CurrentFooterRefDataRaw>(
GetCurrentFooterRef,
{
@@ -393,16 +269,17 @@ export const baseQueryRouter = router({
ttl: "max",
}
)
// There's currently no error handling/validation for the responseRef, should it be added?
getCurrentFooterCounter.add(1, { lang: input.lang })
console.info(
"contentstack.currentFooter start",
JSON.stringify({
query: {
lang: input.lang,
},
})
const getCurrentFooterCounter = createCounter(
"trpc.contentstack",
"currentFooter.get"
)
const metricsGetCurrentFooter = getCurrentFooterCounter.init({
lang: input.lang,
})
metricsGetCurrentFooter.start()
const currentFooterUID =
responseRef.data.all_current_footer.items[0].system.uid
@@ -419,20 +296,7 @@ export const baseQueryRouter = router({
if (!response.data) {
const notFoundError = notFound(response)
getCurrentFooterFailCounter.add(1, {
lang: input.lang,
error_type: "not_found",
error: JSON.stringify({ code: notFoundError.code }),
})
console.error(
"contentstack.currentFooter not found error",
JSON.stringify({
query: {
lang: input.lang,
},
error: { code: notFoundError.code },
})
)
metricsGetCurrentFooter.noDataError()
throw notFoundError
}
@@ -440,34 +304,27 @@ export const baseQueryRouter = router({
validateCurrentFooterConfigSchema.safeParse(response.data)
if (!validatedCurrentFooterConfig.success) {
getFooterFailCounter.add(1, {
lang: input.lang,
error_type: "validation_error",
error: JSON.stringify(validatedCurrentFooterConfig.error),
})
console.error(
"contentstack.currentFooter validation error",
JSON.stringify({
query: { lang: input.lang },
error: validatedCurrentFooterConfig.error,
})
metricsGetCurrentFooter.validationError(
validatedCurrentFooterConfig.error
)
return null
}
getCurrentFooterSuccessCounter.add(1, { lang: input.lang })
console.info(
"contentstack.currentFooter success",
JSON.stringify({ query: { lang: input.lang } })
)
metricsGetCurrentFooter.success()
return validatedCurrentFooterConfig.data.all_current_footer.items[0]
}),
footer: contentstackBaseProcedure.query(async ({ ctx }) => {
const { lang } = ctx
getFooterRefCounter.add(1, { lang })
console.info(
"contentstack.footer.ref start",
JSON.stringify({ query: { lang } })
const getFooterRefsCounter = createCounter(
"trpc.contentstack",
"footer.get.refs"
)
const metricsGetFooterRefs = getFooterRefsCounter.init({ lang })
metricsGetFooterRefs.start()
const responseRef = await request<FooterRefDataRaw>(
GetFooterRef,
{
@@ -481,20 +338,7 @@ export const baseQueryRouter = router({
if (!responseRef.data) {
const notFoundError = notFound(responseRef)
getFooterRefFailCounter.add(1, {
lang,
error_type: "not_found",
error: JSON.stringify({ code: notFoundError.code }),
})
console.error(
"contentstack.footer.refs not found error",
JSON.stringify({
query: {
lang,
},
error: { code: notFoundError.code },
})
)
metricsGetFooterRefs.noDataError()
throw notFoundError
}
@@ -503,41 +347,20 @@ export const baseQueryRouter = router({
)
if (!validatedFooterRefs.success) {
getFooterRefFailCounter.add(1, {
lang,
error_type: "validation_error",
error: JSON.stringify(validatedFooterRefs.error),
})
console.error(
"contentstack.footer.refs validation error",
JSON.stringify({
query: {
lang,
},
error: validatedFooterRefs.error,
})
)
metricsGetFooterRefs.validationError(validatedFooterRefs.error)
return null
}
getFooterRefSuccessCounter.add(1, { lang })
console.info(
"contentstack.footer.refs success",
JSON.stringify({ query: { lang } })
)
metricsGetFooterRefs.success()
const connections = getFooterConnections(validatedFooterRefs.data)
const footerUID = responseRef.data.all_footer.items[0].system.uid
getFooterCounter.add(1, { lang: lang })
console.info(
"contentstack.footer start",
JSON.stringify({
query: {
lang,
},
})
)
const getFooterCounter = createCounter("trpc.contentstack", "footer.get")
const metricsGetFooter = getFooterCounter.init({ lang })
metricsGetFooter.start()
const tags = [
generateTags(lang, connections),
generateTag(lang, footerUID),
@@ -556,20 +379,7 @@ export const baseQueryRouter = router({
if (!response.data) {
const notFoundError = notFound(response)
getFooterFailCounter.add(1, {
lang,
error_type: "not_found",
error: JSON.stringify({ code: notFoundError.code }),
})
console.error(
"contentstack.footer not found error",
JSON.stringify({
query: {
lang,
},
error: { code: notFoundError.code },
})
)
metricsGetFooter.noDataError()
throw notFoundError
}
@@ -578,25 +388,11 @@ export const baseQueryRouter = router({
)
if (!validatedFooterConfig.success) {
getFooterFailCounter.add(1, {
lang,
error_type: "validation_error",
error: JSON.stringify(validatedFooterConfig.error),
})
console.error(
"contentstack.footer validation error",
JSON.stringify({
query: { lang: lang },
error: validatedFooterConfig.error,
})
)
metricsGetFooter.validationError(validatedFooterConfig.error)
return null
}
getFooterSuccessCounter.add(1, { lang })
console.info(
"contentstack.footer success",
JSON.stringify({ query: { lang } })
)
metricsGetFooter.success()
return validatedFooterConfig.data
}),
@@ -605,11 +401,14 @@ export const baseQueryRouter = router({
.query(async ({ input, ctx }) => {
const lang = input.lang ?? ctx.lang
getSiteConfigRefCounter.add(1, { lang })
console.info(
"contentstack.siteConfig.ref start",
JSON.stringify({ query: { lang } })
const getSiteConfigRefsCounter = createCounter(
"trpc.contentstack",
"siteConfig.get.refs"
)
const metricsGetSiteConfigRefs = getSiteConfigRefsCounter.init({ lang })
metricsGetSiteConfigRefs.start()
const responseRef = await request<GetSiteConfigRefData>(
GetSiteConfigRef,
{
@@ -623,20 +422,7 @@ export const baseQueryRouter = router({
if (!responseRef.data) {
const notFoundError = notFound(responseRef)
getSiteConfigRefFailCounter.add(1, {
lang,
error_type: "not_found",
error: JSON.stringify({ code: notFoundError.code }),
})
console.error(
"contentstack.siteConfig.refs not found error",
JSON.stringify({
query: {
lang,
},
error: { code: notFoundError.code },
})
)
metricsGetSiteConfigRefs.noDataError()
throw notFoundError
}
@@ -645,20 +431,7 @@ export const baseQueryRouter = router({
)
if (!validatedSiteConfigRef.success) {
getSiteConfigRefFailCounter.add(1, {
lang,
error_type: "validation_error",
error: JSON.stringify(validatedSiteConfigRef.error),
})
console.error(
"contentstack.siteConfig.refs validation error",
JSON.stringify({
query: {
lang,
},
error: validatedSiteConfigRef.error,
})
)
metricsGetSiteConfigRefs.validationError(validatedSiteConfigRef.error)
return null
}
@@ -670,17 +443,16 @@ export const baseQueryRouter = router({
generateTag(lang, siteConfigUid),
].flat()
getSiteConfigRefSuccessCounter.add(1, { lang })
console.info(
"contentstack.siteConfig.refs success",
JSON.stringify({ query: { lang } })
)
metricsGetSiteConfigRefs.success()
getSiteConfigCounter.add(1, { lang })
console.info(
"contentstack.siteConfig start",
JSON.stringify({ query: { lang } })
const getSiteConfigCounter = createCounter(
"trpc.contentstack",
"siteConfig.get"
)
const metricsGetSiteConfig = getSiteConfigCounter.init({ lang })
metricsGetSiteConfig.start()
const [siteConfigResponse, contactConfig] = await Promise.all([
request<GetSiteConfigData>(
GetSiteConfig,
@@ -697,21 +469,7 @@ export const baseQueryRouter = router({
if (!siteConfigResponse.data) {
const notFoundError = notFound(siteConfigResponse)
getSiteConfigFailCounter.add(1, {
lang,
error_type: "not_found",
error: JSON.stringify({ code: notFoundError.code }),
})
console.error(
"contentstack.siteConfig not found error",
JSON.stringify({
query: { lang },
error: { code: notFoundError.code },
})
)
metricsGetSiteConfig.noDataError()
throw notFoundError
}
@@ -720,26 +478,11 @@ export const baseQueryRouter = router({
)
if (!validatedSiteConfig.success) {
getSiteConfigFailCounter.add(1, {
lang,
error_type: "validation_error",
error: JSON.stringify(validatedSiteConfig.error),
})
console.error(
"contentstack.siteConfig validation error",
JSON.stringify({
query: { lang },
error: validatedSiteConfig.error,
})
)
metricsGetSiteConfig.validationError(validatedSiteConfig.error)
return null
}
getSiteConfigSuccessCounter.add(1, { lang })
console.info(
"contentstack.siteConfig success",
JSON.stringify({ query: { lang } })
)
metricsGetSiteConfig.success()
const { sitewideAlert } = validatedSiteConfig.data

View File

@@ -1,112 +0,0 @@
import { metrics } from "@opentelemetry/api"
const meter = metrics.getMeter("trpc.contentstack.base")
// OpenTelemetry metrics: ContactConfig
export const getContactConfigCounter = meter.createCounter(
"trpc.contentstack.contactConfig.get"
)
export const getContactConfigSuccessCounter = meter.createCounter(
"trpc.contentstack.contactConfig.get-success"
)
export const getContactConfigFailCounter = meter.createCounter(
"trpc.contentstack.contactConfig.get-fail"
)
// OpenTelemetry metrics: CurrentHeader
export const getCurrentHeaderRefCounter = meter.createCounter(
"trpc.contentstack.currentHeader.ref.get"
)
export const getCurrentHeaderRefSuccessCounter = meter.createCounter(
"trpc.contentstack.currentHeader.ref.get-success"
)
export const getCurrentHeaderRefFailCounter = meter.createCounter(
"trpc.contentstack.currentHeader.ref.get-fail"
)
export const getCurrentHeaderCounter = meter.createCounter(
"trpc.contentstack.currentHeader.get"
)
export const getCurrentHeaderSuccessCounter = meter.createCounter(
"trpc.contentstack.currentHeader.get-success"
)
export const getCurrentHeaderFailCounter = meter.createCounter(
"trpc.contentstack.currentHeader.get-fail"
)
// OpenTelemetry metrics: Header
export const getHeaderRefsCounter = meter.createCounter(
"trpc.contentstack.header.ref.get"
)
export const getHeaderRefsSuccessCounter = meter.createCounter(
"trpc.contentstack.header.ref.get-success"
)
export const getHeaderRefsFailCounter = meter.createCounter(
"trpc.contentstack.header.ref.get-fail"
)
export const getHeaderCounter = meter.createCounter(
"trpc.contentstack.header.get"
)
export const getHeaderSuccessCounter = meter.createCounter(
"trpc.contentstack.header.get-success"
)
export const getHeaderFailCounter = meter.createCounter(
"trpc.contentstack.header.get-fail"
)
// OpenTelemetry metrics: CurrentFooter
export const getCurrentFooterRefCounter = meter.createCounter(
"trpc.contentstack.currentFooter.ref.get"
)
export const getCurrentFooterRefSuccessCounter = meter.createCounter(
"trpc.contentstack.currentFooter.ref.get-success"
)
export const getCurrentFooterRefFailCounter = meter.createCounter(
"trpc.contentstack.currentFooter.ref.get-fail"
)
export const getCurrentFooterCounter = meter.createCounter(
"trpc.contentstack.currentFooter.get"
)
export const getCurrentFooterSuccessCounter = meter.createCounter(
"trpc.contentstack.currentFooter.get-success"
)
export const getCurrentFooterFailCounter = meter.createCounter(
"trpc.contentstack.currentFooter.get-fail"
)
// OpenTelemetry metrics: Footer
export const getFooterRefCounter = meter.createCounter(
"trpc.contentstack.footer.ref.get"
)
export const getFooterRefSuccessCounter = meter.createCounter(
"trpc.contentstack.footer.ref.get-success"
)
export const getFooterRefFailCounter = meter.createCounter(
"trpc.contentstack.footer.ref.get-fail"
)
export const getFooterCounter = meter.createCounter(
"trpc.contentstack.footer.get"
)
export const getFooterSuccessCounter = meter.createCounter(
"trpc.contentstack.footer.get-success"
)
export const getFooterFailCounter = meter.createCounter(
"trpc.contentstack.footer.get-fail"
)
// OpenTelemetry metrics: SiteConfig
export const getSiteConfigRefCounter = meter.createCounter(
"trpc.contentstack.SiteConfig.ref.get"
)
export const getSiteConfigRefSuccessCounter = meter.createCounter(
"trpc.contentstack.SiteConfig.ref.get-success"
)
export const getSiteConfigRefFailCounter = meter.createCounter(
"trpc.contentstack.SiteConfig.ref.get-fail"
)
export const getSiteConfigCounter = meter.createCounter(
"trpc.contentstack.SiteConfig.get"
)
export const getSiteConfigSuccessCounter = meter.createCounter(
"trpc.contentstack.SiteConfig.get-success"
)
export const getSiteConfigFailCounter = meter.createCounter(
"trpc.contentstack.SiteConfig.get-fail"
)

View File

@@ -1,4 +1,3 @@
import { metrics } from "@opentelemetry/api"
import { cache } from "react"
import {
@@ -35,6 +34,7 @@ import {
} from "@/lib/graphql/Query/Breadcrumbs/LoyaltyPage.graphql"
import { request } from "@/lib/graphql/request"
import { notFound } from "@/server/errors/trpc"
import { createCounter } from "@/server/telemetry"
import { contentstackExtendedProcedureUID, router } from "@/server/trpc"
import { generateRefsResponseTag } from "@/utils/generateTag"
@@ -49,28 +49,6 @@ import type {
} from "@/types/trpc/routers/contentstack/breadcrumbs"
import type { Lang } from "@/constants/languages"
const meter = metrics.getMeter("trpc.breadcrumbs")
// OpenTelemetry metrics
const getBreadcrumbsRefsCounter = meter.createCounter(
"trpc.contentstack.breadcrumbs.refs.get"
)
const getBreadcrumbsRefsSuccessCounter = meter.createCounter(
"trpc.contentstack.breadcrumbs.refs.get-success"
)
const getBreadcrumbsRefsFailCounter = meter.createCounter(
"trpc.contentstack.breadcrumbs.refs.get-fail"
)
const getBreadcrumbsCounter = meter.createCounter(
"trpc.contentstack.breadcrumbs.get"
)
const getBreadcrumbsSuccessCounter = meter.createCounter(
"trpc.contentstack.breadcrumbs.get-success"
)
const getBreadcrumbsFailCounter = meter.createCounter(
"trpc.contentstack.breadcrumbs.get-fail"
)
interface BreadcrumbsPageData<T> {
dataKey: keyof T
refQuery: string
@@ -81,11 +59,17 @@ const getBreadcrumbs = cache(async function fetchMemoizedBreadcrumbs<T>(
{ dataKey, refQuery, query }: BreadcrumbsPageData<T>,
{ uid, lang }: { uid: string; lang: Lang }
) {
getBreadcrumbsRefsCounter.add(1, { lang, uid })
console.info(
"contentstack.breadcrumbs refs get start",
JSON.stringify({ query: { lang, uid } })
const getBreadcrumbsRefsCounter = createCounter(
"trpc.contentstack",
"breadcrumbs.get.refs"
)
const metricsGetBreadcrumbsRefs = getBreadcrumbsRefsCounter.init({
lang,
uid,
})
metricsGetBreadcrumbsRefs.start()
const refsResponse = await request<{ [K in keyof T]: BreadcrumbsRefsSchema }>(
refQuery,
{ locale: lang, uid },
@@ -100,32 +84,25 @@ const getBreadcrumbs = cache(async function fetchMemoizedBreadcrumbs<T>(
)
if (!validatedRefsData.success) {
getBreadcrumbsRefsFailCounter.add(1, {
error_type: "validation_error",
error: JSON.stringify(validatedRefsData.error),
})
console.error(
"contentstack.breadcrumbs refs validation error",
JSON.stringify({
error: validatedRefsData.error,
})
)
metricsGetBreadcrumbsRefs.validationError(validatedRefsData.error)
return []
}
getBreadcrumbsRefsSuccessCounter.add(1, { lang, uid })
console.info(
"contentstack.breadcrumbs refs get success",
JSON.stringify({ query: { lang, uid } })
)
metricsGetBreadcrumbsRefs.success()
const tags = getTags(validatedRefsData.data, lang)
getBreadcrumbsCounter.add(1, { lang, uid })
console.info(
"contentstack.breadcrumbs get start",
JSON.stringify({ query: { lang, uid } })
const getBreadcrumbsCounter = createCounter(
"trpc.contentstack",
"breadcrumbs.get"
)
const metricsGetBreadcrumbs = getBreadcrumbsCounter.init({
lang,
uid,
})
metricsGetBreadcrumbs.start()
const response = await request<T>(
query,
{ locale: lang, uid },
@@ -137,19 +114,7 @@ const getBreadcrumbs = cache(async function fetchMemoizedBreadcrumbs<T>(
if (!response.data) {
const notFoundError = notFound(response)
getBreadcrumbsFailCounter.add(1, {
lang,
uid,
error_type: "not_found",
error: JSON.stringify({ code: notFoundError.code }),
})
console.error(
"contentstack.breadcrumbs get not found error",
JSON.stringify({
query: { lang, uid },
error: { code: notFoundError.code },
})
)
metricsGetBreadcrumbs.noDataError()
throw notFoundError
}
@@ -158,24 +123,11 @@ const getBreadcrumbs = cache(async function fetchMemoizedBreadcrumbs<T>(
)
if (!validatedBreadcrumbs.success) {
getBreadcrumbsFailCounter.add(1, {
error_type: "validation_error",
error: JSON.stringify(validatedBreadcrumbs.error),
})
console.error(
"contentstack.breadcrumbs validation error",
JSON.stringify({
error: validatedBreadcrumbs.error,
})
)
metricsGetBreadcrumbs.validationError(validatedBreadcrumbs.error)
return []
}
getBreadcrumbsSuccessCounter.add(1, { lang, uid })
console.info(
"contentstack.breadcrumbs get success",
JSON.stringify({ query: { lang, uid } })
)
metricsGetBreadcrumbs.success()
return validatedBreadcrumbs.data
})

View File

@@ -7,8 +7,8 @@ import {
cardsGridSchema,
} from "../schemas/blocks/cardsGrid"
import {
dynamicContentSchema as blockDynamicContentSchema,
dynamicContentRefsSchema,
dynamicContentSchema as blockDynamicContentSchema,
} from "../schemas/blocks/dynamicContent"
import {
shortcutsRefsSchema,

View File

@@ -1,12 +1,12 @@
import { GetCollectionPage } from "@/lib/graphql/Query/CollectionPage/CollectionPage.graphql"
import { request } from "@/lib/graphql/request"
import { createCounter } from "@/server/telemetry"
import { contentstackExtendedProcedureUID, router } from "@/server/trpc"
import { collectionPageSchema } from "./output"
import {
fetchCollectionPageRefs,
generatePageTags,
getCollectionPageCounter,
validateCollectionPageRefs,
} from "./utils"
@@ -32,13 +32,16 @@ export const collectionPageQueryRouter = router({
}
const tags = generatePageTags(collectionPageRefs, lang)
getCollectionPageCounter.add(1, { lang, uid })
console.info(
"contentstack.collectionPage start",
JSON.stringify({
query: { lang, uid },
})
const getCollectionPageCounter = createCounter(
"trpc.contentstack",
"collectionPage.get"
)
const metricsGetCollectionPage = getCollectionPageCounter.init({
lang,
uid,
})
metricsGetCollectionPage.start()
const response = await request<GetCollectionPageSchema>(
GetCollectionPage,
@@ -51,13 +54,12 @@ export const collectionPageQueryRouter = router({
const collectionPage = collectionPageSchema.safeParse(response.data)
if (!collectionPage.success) {
console.error(
`Failed to validate CollectionPage Data - (lang: ${lang}, uid: ${uid})`
)
console.error(collectionPage.error?.format())
metricsGetCollectionPage.validationError(collectionPage.error)
return null
}
metricsGetCollectionPage.success()
const tracking: TrackingSDKPageData = {
pageId: collectionPage.data.collection_page.system.uid,
domainLanguage: lang,

View File

@@ -1,8 +1,7 @@
import { metrics } from "@opentelemetry/api"
import { GetCollectionPageRefs } from "@/lib/graphql/Query/CollectionPage/CollectionPage.graphql"
import { request } from "@/lib/graphql/request"
import { notFound } from "@/server/errors/trpc"
import { createCounter } from "@/server/telemetry"
import { getCacheClient } from "@/services/dataCache"
import {
@@ -21,31 +20,17 @@ import type {
} from "@/types/trpc/routers/contentstack/collectionPage"
import type { Lang } from "@/constants/languages"
const meter = metrics.getMeter("trpc.collectionPage")
// OpenTelemetry metrics: CollectionPage
export const getCollectionPageCounter = meter.createCounter(
"trpc.contentstack.collectionPage.get"
)
const getCollectionPageRefsCounter = meter.createCounter(
"trpc.contentstack.collectionPage.get"
)
const getCollectionPageRefsFailCounter = meter.createCounter(
"trpc.contentstack.collectionPage.get-fail"
)
const getCollectionPageRefsSuccessCounter = meter.createCounter(
"trpc.contentstack.collectionPage.get-success"
)
export async function fetchCollectionPageRefs(lang: Lang, uid: string) {
getCollectionPageRefsCounter.add(1, { lang, uid })
console.info(
"contentstack.collectionPage.refs start",
JSON.stringify({
query: { lang, uid },
})
const getCollectionPageRefsCounter = createCounter(
"trpc.contentstack",
"collectionPage.get.refs"
)
const metricsGetCollectionPageRefs = getCollectionPageRefsCounter.init({
lang,
uid,
})
metricsGetCollectionPageRefs.start()
const cacheClient = await getCacheClient()
const cacheKey = generateRefsResponseTag(lang, uid)
@@ -61,24 +46,7 @@ export async function fetchCollectionPageRefs(lang: Lang, uid: string) {
if (!refsResponse.data) {
const notFoundError = notFound(refsResponse)
getCollectionPageRefsFailCounter.add(1, {
lang,
uid,
error_type: "http_error",
error: JSON.stringify({
code: notFoundError.code,
}),
})
console.error(
"contentstack.collectionPage.refs not found error",
JSON.stringify({
query: {
lang,
uid,
},
error: { code: notFoundError.code },
})
)
metricsGetCollectionPageRefs.noDataError()
throw notFoundError
}
@@ -90,30 +58,22 @@ export function validateCollectionPageRefs(
lang: Lang,
uid: string
) {
const validatedData = collectionPageRefsSchema.safeParse(data)
if (!validatedData.success) {
getCollectionPageRefsFailCounter.add(1, {
const getCollectionPageRefsCounter = createCounter(
"trpc.contentstack",
"collectionPage.get.refs"
)
const metricsGetCollectionPageRefs = getCollectionPageRefsCounter.init({
lang,
uid,
error_type: "validation_error",
error: JSON.stringify(validatedData.error),
})
console.error(
"contentstack.collectionPage.refs validation error",
JSON.stringify({
query: { lang, uid },
error: validatedData.error,
})
)
const validatedData = collectionPageRefsSchema.safeParse(data)
if (!validatedData.success) {
metricsGetCollectionPageRefs.validationError(validatedData.error)
return null
}
getCollectionPageRefsSuccessCounter.add(1, { lang, uid })
console.info(
"contentstack.collectionPage.refs success",
JSON.stringify({
query: { lang, uid },
})
)
metricsGetCollectionPageRefs.success()
return validatedData.data
}

View File

@@ -4,6 +4,7 @@ import {
GetContentPageBlocksBatch1,
GetContentPageBlocksBatch2,
} from "@/lib/graphql/Query/ContentPage/ContentPage.graphql"
import { createCounter } from "@/server/telemetry"
import { contentstackExtendedProcedureUID, router } from "@/server/trpc"
import { contentPageSchema } from "./output"
@@ -12,7 +13,6 @@ import {
createPageType,
fetchContentPageRefs,
generatePageTags,
getContentPageCounter,
} from "./utils"
import type { TrackingSDKPageData } from "@/types/components/tracking"
@@ -30,13 +30,16 @@ export const contentPageQueryRouter = router({
const tags = generatePageTags(contentPageRefs, lang)
getContentPageCounter.add(1, { lang, uid })
console.info(
"contentstack.contentPage start",
JSON.stringify({
query: { lang, uid },
})
const getContentPageCounter = createCounter(
"trpc.contentstack",
"contentPage.get"
)
const metricsGetContentPage = getContentPageCounter.init({
lang,
uid,
})
metricsGetContentPage.start()
const contentPageRequest = await batchRequest<GetContentPageSchema>([
{
@@ -69,13 +72,12 @@ export const contentPageQueryRouter = router({
const contentPage = contentPageSchema.safeParse(contentPageRequest.data)
if (!contentPage.success) {
console.error(
`Failed to validate Contentpage Data - (lang: ${lang}, uid: ${uid})`
)
console.error(contentPage.error?.format())
metricsGetContentPage.validationError(contentPage.error)
return null
}
metricsGetContentPage.success()
const tracking: TrackingSDKPageData = {
pageId: contentPage.data.content_page.system.uid,
domainLanguage: lang,

View File

@@ -1,11 +1,10 @@
import { metrics } from "@opentelemetry/api"
import { batchRequest } from "@/lib/graphql/batchRequest"
import {
GetContentPageBlocksRefs,
GetContentPageRefs,
} from "@/lib/graphql/Query/ContentPage/ContentPage.graphql"
import { notFound } from "@/server/errors/trpc"
import { createCounter } from "@/server/telemetry"
import {
generateRefsResponseTag,
@@ -18,37 +17,24 @@ import { contentPageRefsSchema } from "./output"
import { TrackingChannelEnum } from "@/types/components/tracking"
import { ContentPageEnum } from "@/types/enums/contentPage"
import type { System } from "@/types/requests/system"
import type {
ContentPageRefs,
GetContentPageRefsSchema,
import {
type ContentPageRefs,
type GetContentPageRefsSchema,
} from "@/types/trpc/routers/contentstack/contentPage"
import type { Lang } from "@/constants/languages"
const meter = metrics.getMeter("trpc.contentPage")
// OpenTelemetry metrics: ContentPage
export const getContentPageCounter = meter.createCounter(
"trpc.contentstack.contentPage.get"
)
const getContentPageRefsCounter = meter.createCounter(
"trpc.contentstack.contentPage.get"
)
const getContentPageRefsFailCounter = meter.createCounter(
"trpc.contentstack.contentPage.get-fail"
)
const getContentPageRefsSuccessCounter = meter.createCounter(
"trpc.contentstack.contentPage.get-success"
)
export async function fetchContentPageRefs(lang: Lang, uid: string) {
getContentPageRefsCounter.add(1, { lang, uid })
console.info(
"contentstack.contentPage.refs start",
JSON.stringify({
query: { lang, uid },
})
const getContentPageRefsCounter = createCounter(
"trpc.contentstack",
"contentPage.get.refs"
)
const metricsGetContentPageRefs = getContentPageRefsCounter.init({
lang,
uid,
})
metricsGetContentPageRefs.start()
const res = await batchRequest<GetContentPageRefsSchema>([
{
document: GetContentPageRefs,
@@ -69,50 +55,17 @@ export async function fetchContentPageRefs(lang: Lang, uid: string) {
])
if (!res.data) {
const notFoundError = notFound(res)
getContentPageRefsFailCounter.add(1, {
lang,
uid,
error_type: "http_error",
error: JSON.stringify({
code: notFoundError.code,
}),
})
console.error(
"contentstack.contentPage.refs not found error",
JSON.stringify({
query: {
lang,
uid,
},
error: { code: notFoundError.code },
})
)
metricsGetContentPageRefs.noDataError()
throw notFoundError
}
const validatedData = contentPageRefsSchema.safeParse(res.data)
if (!validatedData.success) {
getContentPageRefsFailCounter.add(1, {
lang,
uid,
error_type: "validation_error",
error: JSON.stringify(validatedData.error),
})
console.error(
"contentstack.contentPage.refs validation error",
JSON.stringify({
query: { lang, uid },
error: validatedData.error,
})
)
metricsGetContentPageRefs.validationError(validatedData.error)
return null
}
getContentPageRefsSuccessCounter.add(1, { lang, uid })
console.info(
"contentstack.contentPage.refs success",
JSON.stringify({
query: { lang, uid },
})
)
metricsGetContentPageRefs.success()
return validatedData.data
}
@@ -142,8 +95,7 @@ export function getConnections({ content_page }: ContentPageRefs) {
case ContentPageEnum.ContentStack.blocks.Content:
{
if (block.content.length) {
// TS has trouble infering the filtered types
// @ts-ignore
// @ts-expect-error: TS has trouble infering the filtered types
connections.push(...block.content)
}
}

View File

@@ -4,6 +4,7 @@ import {
} from "@/lib/graphql/Query/DestinationCityPage/DestinationCityPage.graphql"
import { request } from "@/lib/graphql/request"
import { notFound } from "@/server/errors/trpc"
import { createCounter } from "@/server/telemetry"
import { contentStackUidWithServiceProcedure, router } from "@/server/trpc"
import { generateRefsResponseTag } from "@/utils/generateTag"
@@ -13,14 +14,6 @@ import {
destinationCityPageRefsSchema,
destinationCityPageSchema,
} from "./output"
import {
getDestinationCityPageCounter,
getDestinationCityPageFailCounter,
getDestinationCityPageRefsCounter,
getDestinationCityPageRefsFailCounter,
getDestinationCityPageRefsSuccessCounter,
getDestinationCityPageSuccessCounter,
} from "./telemetry"
import { generatePageTags } from "./utils"
import {
@@ -36,11 +29,14 @@ export const destinationCityPageQueryRouter = router({
get: contentStackUidWithServiceProcedure.query(async ({ ctx }) => {
const { lang, uid, serviceToken } = ctx
getDestinationCityPageRefsCounter.add(1, { lang, uid })
console.info(
"contentstack.destinationCityPage.refs start",
JSON.stringify({ query: { lang, uid } })
const getDestinationCityPageRefsCounter = createCounter(
"trpc.contentstack",
"destinationCityPage.get.refs"
)
const metricsGetDestinationCityPageRefs =
getDestinationCityPageRefsCounter.init({ lang, uid })
metricsGetDestinationCityPageRefs.start()
const refsResponse = await request<GetDestinationCityPageRefsSchema>(
GetDestinationCityPageRefs,
@@ -53,19 +49,7 @@ export const destinationCityPageQueryRouter = router({
if (!refsResponse.data) {
const notFoundError = notFound(refsResponse)
getDestinationCityPageRefsFailCounter.add(1, {
lang,
uid: `${uid}`,
error_type: "not_found",
error: JSON.stringify({ code: notFoundError.code }),
})
console.error(
"contentstack.destinationCityPage.refs not found error",
JSON.stringify({
query: { lang, uid },
error: { code: notFoundError.code },
})
)
metricsGetDestinationCityPageRefs.noDataError()
throw notFoundError
}
@@ -73,33 +57,25 @@ export const destinationCityPageQueryRouter = router({
refsResponse.data
)
if (!validatedRefsData.success) {
getDestinationCityPageRefsFailCounter.add(1, {
lang,
uid: `${uid}`,
error_type: "validation_error",
error: JSON.stringify(validatedRefsData.error),
})
console.error(
"contentstack.destinationCityPage.refs validation error",
JSON.stringify({ query: { lang, uid }, error: validatedRefsData.error })
)
metricsGetDestinationCityPageRefs.validationError(validatedRefsData.error)
return null
}
getDestinationCityPageRefsSuccessCounter.add(1, { lang, uid: `${uid}` })
console.info(
"contentstack.destinationCityPage.refs success",
JSON.stringify({ query: { lang, uid } })
)
metricsGetDestinationCityPageRefs.success()
const tags = generatePageTags(validatedRefsData.data, lang)
getDestinationCityPageCounter.add(1, { lang, uid: `${uid}` })
console.info(
"contentstack.destinationCityPage start",
JSON.stringify({
query: { lang, uid },
})
const getDestinationCityPageCounter = createCounter(
"trpc.contentstack",
"destinationCityPage.get"
)
const metricsGetDestinationCityPage = getDestinationCityPageCounter.init({
lang,
uid,
})
metricsGetDestinationCityPage.start()
const response = await request<GetDestinationCityPageData>(
GetDestinationCityPage,
{
@@ -113,40 +89,17 @@ export const destinationCityPageQueryRouter = router({
)
if (!response.data) {
const notFoundError = notFound(response)
getDestinationCityPageFailCounter.add(1, {
lang,
uid: `${uid}`,
error_type: "not_found",
error: JSON.stringify({ code: notFoundError.code }),
})
console.error(
"contentstack.destinationCityPage not found error",
JSON.stringify({
query: { lang, uid },
error: { code: notFoundError.code },
})
)
metricsGetDestinationCityPage.noDataError()
throw notFoundError
}
const validatedResponse = destinationCityPageSchema.safeParse(response.data)
if (!validatedResponse.success) {
getDestinationCityPageFailCounter.add(1, {
lang,
uid: `${uid}`,
error_type: "validation_error",
error: JSON.stringify(validatedResponse.error),
})
console.error(
"contentstack.destinationCityPage validation error",
JSON.stringify({
query: { lang, uid },
error: validatedResponse.error,
})
)
metricsGetDestinationCityPage.validationError(validatedResponse.error)
return null
}
const destinationCityPage = validatedResponse.data.destination_city_page
const cityIdentifier = destinationCityPage.destination_settings.city
if (!cityIdentifier) {
@@ -160,30 +113,16 @@ export const destinationCityPageQueryRouter = router({
})
if (!city) {
getDestinationCityPageFailCounter.add(1, {
lang,
uid: `${uid}`,
error_type: "not_found",
error: `Couldn't find city with cityIdentifier: ${cityIdentifier}`,
})
console.error(
"contentstack.destinationCityPage not found error",
JSON.stringify({
query: { lang, uid },
error: `Couldn't find city with cityIdentifier: ${cityIdentifier}`,
})
metricsGetDestinationCityPage.dataError(
`Failed to get city data for ${cityIdentifier}`,
{
cityIdentifier,
}
)
return null
}
getDestinationCityPageSuccessCounter.add(1, { lang, uid: `${uid}` })
console.info(
"contentstack.destinationCityPage success",
JSON.stringify({
query: { lang, uid },
})
)
metricsGetDestinationCityPage.success()
const system = destinationCityPage.system
const pageName = `destinations|${city.country}|${city.name}`

View File

@@ -1,43 +0,0 @@
import { metrics } from "@opentelemetry/api"
const meter = metrics.getMeter("trpc.contentstack.destinationCityPage")
export const getDestinationCityPageRefsCounter = meter.createCounter(
"trpc.contentstack.destinationCityPage.get"
)
export const getDestinationCityPageRefsFailCounter = meter.createCounter(
"trpc.contentstack.destinationCityPage.get-fail"
)
export const getDestinationCityPageRefsSuccessCounter = meter.createCounter(
"trpc.contentstack.destinationCityPage.get-success"
)
export const getDestinationCityPageCounter = meter.createCounter(
"trpc.contentstack.destinationCityPage.get"
)
export const getDestinationCityPageSuccessCounter = meter.createCounter(
"trpc.contentstack.destinationCityPage.get-success"
)
export const getDestinationCityPageFailCounter = meter.createCounter(
"trpc.contentstack.destinationCityPage.get-fail"
)
export const getCityPageCountCounter = meter.createCounter(
"trpc.contentstack.cityPageCount.get"
)
export const getCityPageCountSuccessCounter = meter.createCounter(
"trpc.contentstack.cityPageCount.get-success"
)
export const getCityPageCountFailCounter = meter.createCounter(
"trpc.contentstack.cityPageCount.get-fail"
)
export const getCityPageUrlsCounter = meter.createCounter(
"trpc.contentstack.cityPageUrls.get"
)
export const getCityPageUrlsSuccessCounter = meter.createCounter(
"trpc.contentstack.cityPageUrls.get-success"
)
export const getCityPageUrlsFailCounter = meter.createCounter(
"trpc.contentstack.cityPageUrls.get-fail"
)

View File

@@ -1,18 +1,11 @@
import { GetCityPageCount } from "@/lib/graphql/Query/DestinationCityPage/DestinationCityPageCount.graphql"
import { GetCityPageUrls } from "@/lib/graphql/Query/DestinationCityPage/DestinationCityPageUrl.graphql"
import { request } from "@/lib/graphql/request"
import { createCounter } from "@/server/telemetry"
import { generateTag, generateTagsFromSystem } from "@/utils/generateTag"
import { batchedCityPageUrlsSchema, cityPageCountSchema } from "./output"
import {
getCityPageCountCounter,
getCityPageCountFailCounter,
getCityPageCountSuccessCounter,
getCityPageUrlsCounter,
getCityPageUrlsFailCounter,
getCityPageUrlsSuccessCounter,
} from "./telemetry"
import { DestinationCityPageEnum } from "@/types/enums/destinationCityPage"
import type { System } from "@/types/requests/system"
@@ -76,11 +69,13 @@ export function getConnections({
}
export async function getCityPageCount(lang: Lang) {
getCityPageCountCounter.add(1, { lang })
console.info(
"contentstack.cityPageCount start",
JSON.stringify({ query: { lang } })
const getCityPageCountCounter = createCounter(
"trpc.contentstack",
"cityPageCount.get"
)
const metricsGetCityPageCount = getCityPageCountCounter.init({ lang })
metricsGetCityPageCount.start()
const response = await request<GetCityPageCountData>(
GetCityPageCount,
@@ -92,15 +87,10 @@ export async function getCityPageCount(lang: Lang) {
ttl: "max",
}
)
if (!response.data) {
getCityPageCountFailCounter.add(1, {
lang,
error_type: "not_found",
error: `City pages count not found for lang: ${lang}`,
})
console.error(
"contentstack.cityPageCount not found error",
JSON.stringify({ query: { lang } })
metricsGetCityPageCount.dataError(
`Failed to get city pages count for ${lang}`
)
return 0
}
@@ -108,35 +98,24 @@ export async function getCityPageCount(lang: Lang) {
const validatedResponse = cityPageCountSchema.safeParse(response.data)
if (!validatedResponse.success) {
getCityPageCountFailCounter.add(1, {
lang,
error_type: "validation_error",
error: JSON.stringify(validatedResponse.error),
})
console.error(
"contentstack.hotelPageCount validation error",
JSON.stringify({
query: { lang },
error: validatedResponse.error,
})
)
metricsGetCityPageCount.validationError(validatedResponse.error)
return 0
}
getCityPageCountSuccessCounter.add(1, { lang })
console.info(
"contentstack.cityPageCount success",
JSON.stringify({ query: { lang } })
)
metricsGetCityPageCount.success()
return validatedResponse.data
}
export async function getCityPageUrls(lang: Lang) {
getCityPageUrlsCounter.add(1, { lang })
console.info(
"contentstack.cityPageUrls start",
JSON.stringify({ query: { lang } })
const getCityPageUrlsCounter = createCounter(
"trpc.contentstack",
"cityPageUrls.get"
)
const metricsGetCityPageUrls = getCityPageUrlsCounter.init({ lang })
metricsGetCityPageUrls.start()
const count = await getCityPageCount(lang)
if (count === 0) {
@@ -162,25 +141,11 @@ export async function getCityPageUrls(lang: Lang) {
const validatedResponse = batchedCityPageUrlsSchema.safeParse(batchedResponse)
if (!validatedResponse.success) {
getCityPageUrlsFailCounter.add(1, {
lang,
error_type: "validation_error",
error: JSON.stringify(validatedResponse.error),
})
console.error(
"contentstack.cityPageUrls validation error",
JSON.stringify({
query: { lang },
error: validatedResponse.error,
})
)
metricsGetCityPageUrls.validationError(validatedResponse.error)
return []
}
getCityPageUrlsSuccessCounter.add(1, { lang })
console.info(
"contentstack.cityPageUrls success",
JSON.stringify({ query: { lang } })
)
metricsGetCityPageUrls.success()
return validatedResponse.data
}

View File

@@ -4,6 +4,7 @@ import {
} from "@/lib/graphql/Query/DestinationCountryPage/DestinationCountryPage.graphql"
import { request } from "@/lib/graphql/request"
import { notFound } from "@/server/errors/trpc"
import { createCounter } from "@/server/telemetry"
import {
contentStackBaseWithServiceProcedure,
contentstackExtendedProcedureUID,
@@ -17,14 +18,6 @@ import {
destinationCountryPageRefsSchema,
destinationCountryPageSchema,
} from "./output"
import {
getDestinationCountryPageCounter,
getDestinationCountryPageFailCounter,
getDestinationCountryPageRefsCounter,
getDestinationCountryPageRefsFailCounter,
getDestinationCountryPageRefsSuccessCounter,
getDestinationCountryPageSuccessCounter,
} from "./telemetry"
import { generatePageTags, getCityPages } from "./utils"
import {
@@ -41,11 +34,14 @@ export const destinationCountryPageQueryRouter = router({
get: contentstackExtendedProcedureUID.query(async ({ ctx }) => {
const { lang, uid } = ctx
getDestinationCountryPageRefsCounter.add(1, { lang, uid })
console.info(
"contentstack.destinationCountryPage.refs start",
JSON.stringify({ query: { lang, uid } })
const getDestinationCountryPageRefsCounter = createCounter(
"trpc.contentstack",
"destinationCountryPage.get.refs"
)
const metricsGetDestinationCountryPageRefs =
getDestinationCountryPageRefsCounter.init({ lang, uid })
metricsGetDestinationCountryPageRefs.start()
const refsResponse = await request<GetDestinationCountryPageRefsSchema>(
GetDestinationCountryPageRefs,
@@ -58,19 +54,7 @@ export const destinationCountryPageQueryRouter = router({
if (!refsResponse.data) {
const notFoundError = notFound(refsResponse)
getDestinationCountryPageRefsFailCounter.add(1, {
lang,
uid: `${uid}`,
error_type: "not_found",
error: JSON.stringify({ code: notFoundError.code }),
})
console.error(
"contentstack.destinationCountryPage.refs not found error",
JSON.stringify({
query: { lang, uid },
error: { code: notFoundError.code },
})
)
metricsGetDestinationCountryPageRefs.noDataError()
throw notFoundError
}
@@ -78,33 +62,25 @@ export const destinationCountryPageQueryRouter = router({
refsResponse.data
)
if (!validatedRefsData.success) {
getDestinationCountryPageRefsFailCounter.add(1, {
lang,
uid: `${uid}`,
error_type: "validation_error",
error: JSON.stringify(validatedRefsData.error),
})
console.error(
"contentstack.destinationCountryPage.refs validation error",
JSON.stringify({ query: { lang, uid }, error: validatedRefsData.error })
metricsGetDestinationCountryPageRefs.validationError(
validatedRefsData.error
)
return null
}
getDestinationCountryPageRefsSuccessCounter.add(1, { lang, uid: `${uid}` })
console.info(
"contentstack.destinationCountryPage.refs success",
JSON.stringify({ query: { lang, uid } })
)
metricsGetDestinationCountryPageRefs.success()
const tags = generatePageTags(validatedRefsData.data, lang)
getDestinationCountryPageCounter.add(1, { lang, uid: `${uid}` })
console.info(
"contentstack.destinationCountryPage start",
JSON.stringify({
query: { lang, uid },
})
const getDestinationCountryPageCounter = createCounter(
"trpc.contentstack",
"destinationCountryPage.get"
)
const metricsGetDestinationCountryPage =
getDestinationCountryPageCounter.init({ lang, uid })
metricsGetDestinationCountryPage.start()
const response = await request<GetDestinationCountryPageData>(
GetDestinationCountryPage,
{
@@ -118,19 +94,7 @@ export const destinationCountryPageQueryRouter = router({
)
if (!response.data) {
const notFoundError = notFound(response)
getDestinationCountryPageFailCounter.add(1, {
lang,
uid: `${uid}`,
error_type: "not_found",
error: JSON.stringify({ code: notFoundError.code }),
})
console.error(
"contentstack.destinationCountryPage not found error",
JSON.stringify({
query: { lang, uid },
error: { code: notFoundError.code },
})
)
metricsGetDestinationCountryPage.noDataError()
throw notFoundError
}
@@ -139,32 +103,15 @@ export const destinationCountryPageQueryRouter = router({
)
if (!validatedResponse.success) {
getDestinationCountryPageFailCounter.add(1, {
lang,
uid: `${uid}`,
error_type: "validation_error",
error: JSON.stringify(validatedResponse.error),
})
console.error(
"contentstack.destinationCountryPage validation error",
JSON.stringify({
query: { lang, uid },
error: validatedResponse.error,
})
)
metricsGetDestinationCountryPage.validationError(validatedResponse.error)
return null
}
const destinationCountryPage =
validatedResponse.data.destination_country_page
const country = destinationCountryPage.destination_settings.country
getDestinationCountryPageSuccessCounter.add(1, { lang, uid: `${uid}` })
console.info(
"contentstack.destinationCountryPage success",
JSON.stringify({
query: { lang, uid },
})
)
metricsGetDestinationCountryPage.success()
const system = destinationCountryPage.system
const pageName = `destinations|${country}`

View File

@@ -1,45 +0,0 @@
import { metrics } from "@opentelemetry/api"
const meter = metrics.getMeter("trpc.contentstack.destinationCountryPage")
export const getDestinationCountryPageRefsCounter = meter.createCounter(
"trpc.contentstack.destinationCountryPage.get"
)
export const getDestinationCountryPageRefsFailCounter = meter.createCounter(
"trpc.contentstack.destinationCountryPage.get-fail"
)
export const getDestinationCountryPageRefsSuccessCounter = meter.createCounter(
"trpc.contentstack.destinationCountryPage.get-success"
)
export const getDestinationCountryPageCounter = meter.createCounter(
"trpc.contentstack.destinationCountryPage.get"
)
export const getDestinationCountryPageSuccessCounter = meter.createCounter(
"trpc.contentstack.destinationCountryPage.get-success"
)
export const getDestinationCountryPageFailCounter = meter.createCounter(
"trpc.contentstack.destinationCountryPage.get-fail"
)
export const getCityListDataCounter = meter.createCounter(
"trpc.contentstack.cityListData.get"
)
export const getCityListDataSuccessCounter = meter.createCounter(
"trpc.contentstack.cityListData.get-success"
)
export const getCityListDataFailCounter = meter.createCounter(
"trpc.contentstack.cityListData.get-fail"
)
export const getCountryPageUrlsCounter = meter.createCounter(
"trpc.contentstack.getCountryPageUrls"
)
export const getCountryPageUrlsSuccessCounter = meter.createCounter(
"trpc.contentstack.getCountryPageUrls-success"
)
export const getCountryPageUrlsFailCounter = meter.createCounter(
"trpc.contentstack.getCountryPageUrls-fail"
)

View File

@@ -1,20 +1,13 @@
import { GetDestinationCityListData } from "@/lib/graphql/Query/DestinationCityPage/DestinationCityListData.graphql"
import { GetCountryPageUrls } from "@/lib/graphql/Query/DestinationCountryPage/DestinationCountryPageUrl.graphql"
import { request } from "@/lib/graphql/request"
import { createCounter } from "@/server/telemetry"
import { generateTag, generateTagsFromSystem } from "@/utils/generateTag"
import { getCitiesByCountry } from "../../hotels/utils"
import { destinationCityListDataSchema } from "../destinationCityPage/output"
import { countryPageUrlsSchema } from "./output"
import {
getCityListDataCounter,
getCityListDataFailCounter,
getCityListDataSuccessCounter,
getCountryPageUrlsCounter,
getCountryPageUrlsFailCounter,
getCountryPageUrlsSuccessCounter,
} from "./telemetry"
import { ApiCountry, type Country } from "@/types/enums/country"
import { DestinationCountryPageEnum } from "@/types/enums/destinationCountryPage"
@@ -77,11 +70,16 @@ export async function getCityListDataByCityIdentifier(
lang: Lang,
cityIdentifier: string
) {
getCityListDataCounter.add(1, { lang, cityIdentifier })
console.info(
"contentstack.cityListData start",
JSON.stringify({ query: { lang, cityIdentifier } })
const getCityListDataCounter = createCounter(
"trpc.contentstack",
"cityListData.get"
)
const metricsGetCityListData = getCityListDataCounter.init({
lang,
cityIdentifier,
})
metricsGetCityListData.start()
const response = await request<GetDestinationCityListDataResponse>(
GetDestinationCityListData,
@@ -96,15 +94,8 @@ export async function getCityListDataByCityIdentifier(
)
if (!response.data) {
getCityListDataFailCounter.add(1, {
lang,
cityIdentifier,
error_type: "not_found",
error: `Destination city page not found for cityIdentifier: ${cityIdentifier}`,
})
console.error(
"contentstack.cityListData not found error",
JSON.stringify({ query: { lang, cityIdentifier } })
metricsGetCityListData.dataError(
`Failed to get destination city page for cityIdentifier: ${cityIdentifier}`
)
return null
}
@@ -114,26 +105,11 @@ export async function getCityListDataByCityIdentifier(
)
if (!validatedResponse.success) {
getCityListDataFailCounter.add(1, {
lang,
cityIdentifier,
error_type: "validation_error",
error: JSON.stringify(validatedResponse.error),
})
console.error(
"contentstack.cityListData validation error",
JSON.stringify({
query: { lang, cityIdentifier },
error: validatedResponse.error,
})
)
metricsGetCityListData.validationError(validatedResponse.error)
return null
}
getCityListDataSuccessCounter.add(1, { lang, cityIdentifier })
console.info(
"contentstack.cityListData success",
JSON.stringify({ query: { lang, cityIdentifier } })
)
metricsGetCityListData.success()
return validatedResponse.data
}
@@ -172,11 +148,13 @@ export async function getCityPages(
}
export async function getCountryPageUrls(lang: Lang) {
getCountryPageUrlsCounter.add(1, { lang })
console.info(
"contentstack.countryPageUrls start",
JSON.stringify({ query: { lang } })
const getCountryPageUrlsCounter = createCounter(
"trpc.contentstack",
"getCountryPageUrls"
)
const metricsGetCountryPageUrls = getCountryPageUrlsCounter.init({ lang })
metricsGetCountryPageUrls.start()
const tag = `${lang}:country_page_urls`
const response = await request<GetCountryPageUrlsData>(
@@ -191,14 +169,8 @@ export async function getCountryPageUrls(lang: Lang) {
)
if (!response.data) {
getCountryPageUrlsFailCounter.add(1, {
lang,
error_type: "not_found",
error: `Country pages not found for lang: ${lang}`,
})
console.error(
"contentstack.countryPageUrls not found error",
JSON.stringify({ query: { lang } })
metricsGetCountryPageUrls.dataError(
`Failed to get country pages for lang: ${lang}`
)
return []
}
@@ -208,26 +180,11 @@ export async function getCountryPageUrls(lang: Lang) {
)
if (!validatedCountryPageUrls.success) {
getCountryPageUrlsFailCounter.add(1, {
lang,
error_type: "validation_error",
error: JSON.stringify(validatedCountryPageUrls.error),
})
console.error(
"contentstack.countryPageUrls validation error",
JSON.stringify({
query: { lang },
error: validatedCountryPageUrls.error,
})
)
metricsGetCountryPageUrls.validationError(validatedCountryPageUrls.error)
return []
}
getCountryPageUrlsSuccessCounter.add(1, { lang })
console.info(
"contentstack.countryPageUrls success",
JSON.stringify({ query: { lang } })
)
metricsGetCountryPageUrls.success()
return validatedCountryPageUrls.data
}

View File

@@ -5,6 +5,7 @@ import {
} from "@/lib/graphql/Query/DestinationOverviewPage/DestinationOverviewPage.graphql"
import { request } from "@/lib/graphql/request"
import { notFound } from "@/server/errors/trpc"
import { createCounter } from "@/server/telemetry"
import {
contentstackExtendedProcedureUID,
router,
@@ -31,14 +32,6 @@ import {
destinationOverviewPageRefsSchema,
destinationOverviewPageSchema,
} from "./output"
import {
getDestinationOverviewPageCounter,
getDestinationOverviewPageFailCounter,
getDestinationOverviewPageRefsCounter,
getDestinationOverviewPageRefsFailCounter,
getDestinationOverviewPageRefsSuccessCounter,
getDestinationOverviewPageSuccessCounter,
} from "./telemetry"
import type {
Cities,
@@ -57,13 +50,15 @@ export const destinationOverviewPageQueryRouter = router({
get: contentstackExtendedProcedureUID.query(async ({ ctx }) => {
const { lang, uid } = ctx
getDestinationOverviewPageRefsCounter.add(1, { lang, uid: `${uid}` })
console.info(
"contentstack.destinationOverviewPage.refs start",
JSON.stringify({
query: { lang, uid },
})
const getDestinationOverviewPageRefsCounter = createCounter(
"trpc.contentstack",
"destinationOverviewPage.get.refs"
)
const metricsGetDestinationOverviewPageRefs =
getDestinationOverviewPageRefsCounter.init({ lang, uid })
metricsGetDestinationOverviewPageRefs.start()
const refsResponse = await request<GetDestinationOverviewPageRefsSchema>(
GetDestinationOverviewPageRefs,
{
@@ -77,19 +72,7 @@ export const destinationOverviewPageQueryRouter = router({
)
if (!refsResponse.data) {
const notFoundError = notFound(refsResponse)
getDestinationOverviewPageRefsFailCounter.add(1, {
lang,
uid,
error_type: "not_found",
error: JSON.stringify({ code: notFoundError.code }),
})
console.error(
"contentstack.destinationOverviewPage.refs not found error",
JSON.stringify({
query: { lang, uid },
error: { code: notFoundError.code },
})
)
metricsGetDestinationOverviewPageRefs.noDataError()
throw notFoundError
}
@@ -98,37 +81,23 @@ export const destinationOverviewPageQueryRouter = router({
)
if (!validatedRefsData.success) {
getDestinationOverviewPageRefsFailCounter.add(1, {
lang,
uid,
error_type: "validation_error",
error: JSON.stringify(validatedRefsData.error),
})
console.error(
"contentstack.destinationOverviewPage.refs validation error",
JSON.stringify({
query: { lang, uid },
error: validatedRefsData.error,
})
metricsGetDestinationOverviewPageRefs.validationError(
validatedRefsData.error
)
return null
}
getDestinationOverviewPageRefsSuccessCounter.add(1, { lang, uid: `${uid}` })
console.info(
"contentstack.destinationOverviewPage.refs success",
JSON.stringify({
query: { lang, uid },
})
)
metricsGetDestinationOverviewPageRefs.success()
getDestinationOverviewPageCounter.add(1, { lang, uid: `${uid}` })
console.info(
"contentstack.destinationOverviewPage start",
JSON.stringify({
query: { lang, uid },
})
const getDestinationOverviewPageCounter = createCounter(
"trpc.contentstack",
"destinationOverviewPage.get"
)
const metricsGetDestinationOverviewPage =
getDestinationOverviewPageCounter.init({ lang, uid })
metricsGetDestinationOverviewPage.start()
const response = await request<GetDestinationOverviewPageData>(
GetDestinationOverviewPage,
{
@@ -142,19 +111,7 @@ export const destinationOverviewPageQueryRouter = router({
)
if (!response.data) {
const notFoundError = notFound(response)
getDestinationOverviewPageFailCounter.add(1, {
lang,
uid: `${uid}`,
error_type: "not_found",
error: JSON.stringify({ code: notFoundError.code }),
})
console.error(
"contentstack.destinationOverviewPage not found error",
JSON.stringify({
query: { lang, uid },
error: { code: notFoundError.code },
})
)
metricsGetDestinationOverviewPage.noDataError()
throw notFoundError
}
@@ -163,29 +120,13 @@ export const destinationOverviewPageQueryRouter = router({
)
if (!destinationOverviewPage.success) {
getDestinationOverviewPageFailCounter.add(1, {
lang,
uid: `${uid}`,
error_type: "validation_error",
error: JSON.stringify(destinationOverviewPage.error),
})
console.error(
"contentstack.destinationOverviewPage validation error",
JSON.stringify({
query: { lang, uid },
error: destinationOverviewPage.error,
})
metricsGetDestinationOverviewPage.validationError(
destinationOverviewPage.error
)
return null
}
getDestinationOverviewPageSuccessCounter.add(1, { lang, uid: `${uid}` })
console.info(
"contentstack.destinationOverviewPage success",
JSON.stringify({
query: { lang, uid },
})
)
metricsGetDestinationOverviewPage.success()
const system = destinationOverviewPage.data.destination_overview_page.system
const tracking: TrackingSDKPageData = {

View File

@@ -1,23 +0,0 @@
import { metrics } from "@opentelemetry/api"
const meter = metrics.getMeter("trpc.contentstack.destinationOverviewPage")
export const getDestinationOverviewPageRefsCounter = meter.createCounter(
"trpc.contentstack.destinationOverviewPage.get"
)
export const getDestinationOverviewPageRefsFailCounter = meter.createCounter(
"trpc.contentstack.destinationOverviewPage.get-fail"
)
export const getDestinationOverviewPageRefsSuccessCounter = meter.createCounter(
"trpc.contentstack.destinationOverviewPage.get-success"
)
export const getDestinationOverviewPageCounter = meter.createCounter(
"trpc.contentstack.destinationOverviewPage.get"
)
export const getDestinationOverviewPageSuccessCounter = meter.createCounter(
"trpc.contentstack.destinationOverviewPage.get-success"
)
export const getDestinationOverviewPageFailCounter = meter.createCounter(
"trpc.contentstack.destinationOverviewPage.get-fail"
)

View File

@@ -1,29 +1,27 @@
import { GetHotelPage } from "@/lib/graphql/Query/HotelPage/HotelPage.graphql"
import { request } from "@/lib/graphql/request"
import { notFound } from "@/server/errors/trpc"
import { createCounter } from "@/server/telemetry"
import { contentstackExtendedProcedureUID, router } from "@/server/trpc"
import { generateTag } from "@/utils/generateTag"
import { hotelPageSchema } from "./output"
import {
getHotelPageCounter,
getHotelPageFailCounter,
getHotelPageSuccessCounter,
} from "./telemetry"
import type { GetHotelPageData } from "@/types/trpc/routers/contentstack/hotelPage"
export const hotelPageQueryRouter = router({
get: contentstackExtendedProcedureUID.query(async ({ ctx }) => {
const { lang, uid } = ctx
getHotelPageCounter.add(1, { lang, uid: `${uid}` })
console.info(
"contentstack.hotelPage start",
JSON.stringify({
query: { lang, uid },
})
const getHotelPageCounter = createCounter(
"trpc.contentstack",
"hotelPage.get"
)
const metricsGetHotelPage = getHotelPageCounter.init({ lang, uid })
metricsGetHotelPage.start()
const response = await request<GetHotelPageData>(
GetHotelPage,
{
@@ -37,48 +35,19 @@ export const hotelPageQueryRouter = router({
)
if (!response.data) {
const notFoundError = notFound(response)
getHotelPageFailCounter.add(1, {
lang,
uid: `${uid}`,
error_type: "not_found",
error: JSON.stringify({ code: notFoundError.code }),
})
console.error(
"contentstack.hotelPage not found error",
JSON.stringify({
query: { lang, uid },
error: { code: notFoundError.code },
})
)
metricsGetHotelPage.noDataError()
throw notFoundError
}
const validatedHotelPage = hotelPageSchema.safeParse(response.data)
if (!validatedHotelPage.success) {
getHotelPageFailCounter.add(1, {
lang,
uid: `${uid}`,
error_type: "validation_error",
error: JSON.stringify(validatedHotelPage.error),
})
console.error(
"contentstack.hotelPage validation error",
JSON.stringify({
query: { lang, uid },
error: validatedHotelPage.error,
})
)
metricsGetHotelPage.validationError(validatedHotelPage.error)
return null
}
getHotelPageSuccessCounter.add(1, { lang, uid: `${uid}` })
console.info(
"contentstack.hotelPage success",
JSON.stringify({
query: { lang, uid },
})
)
metricsGetHotelPage.success()
return validatedHotelPage.data.hotel_page
}),
})

View File

@@ -1,43 +0,0 @@
import { metrics } from "@opentelemetry/api"
const meter = metrics.getMeter("trpc.contentstack.hotelPage")
export const getHotelPageRefsCounter = meter.createCounter(
"trpc.contentstack.hotelPage.get"
)
export const getHotelPageRefsFailCounter = meter.createCounter(
"trpc.contentstack.hotelPage.get-fail"
)
export const getHotelPageRefsSuccessCounter = meter.createCounter(
"trpc.contentstack.hotelPage.get-success"
)
export const getHotelPageCounter = meter.createCounter(
"trpc.contentstack.hotelPage.get"
)
export const getHotelPageSuccessCounter = meter.createCounter(
"trpc.contentstack.hotelPage.get-success"
)
export const getHotelPageFailCounter = meter.createCounter(
"trpc.contentstack.hotelPage.get-fail"
)
export const getHotelPageUrlsCounter = meter.createCounter(
"trpc.contentstack.hotelPageUrls.get"
)
export const getHotelPageUrlsSuccessCounter = meter.createCounter(
"trpc.contentstack.hotelPageUrls.get-success"
)
export const getHotelPageUrlsFailCounter = meter.createCounter(
"trpc.contentstack.hotelPageUrls.get-fail"
)
export const getHotelPageCountCounter = meter.createCounter(
"trpc.contentstack.hotelPageCount.get"
)
export const getHotelPageCountSuccessCounter = meter.createCounter(
"trpc.contentstack.hotelPageCount.get-success"
)
export const getHotelPageCountFailCounter = meter.createCounter(
"trpc.contentstack.hotelPageCount.get-fail"
)

View File

@@ -3,6 +3,7 @@ import { GetHotelPageCount } from "@/lib/graphql/Query/HotelPage/HotelPageCount.
import { GetHotelPageUrls } from "@/lib/graphql/Query/HotelPage/HotelPageUrl.graphql"
import { request } from "@/lib/graphql/request"
import { notFound } from "@/server/errors/trpc"
import { createCounter } from "@/server/telemetry"
import {
generateRefsResponseTag,
@@ -15,17 +16,6 @@ import {
hotelPageCountSchema,
hotelPageRefsSchema,
} from "./output"
import {
getHotelPageCountCounter,
getHotelPageCountFailCounter,
getHotelPageCountSuccessCounter,
getHotelPageRefsCounter,
getHotelPageRefsFailCounter,
getHotelPageRefsSuccessCounter,
getHotelPageUrlsCounter,
getHotelPageUrlsFailCounter,
getHotelPageUrlsSuccessCounter,
} from "./telemetry"
import { HotelPageEnum } from "@/types/enums/hotelPage"
import type { System } from "@/types/requests/system"
@@ -38,13 +28,13 @@ import type {
import type { Lang } from "@/constants/languages"
export async function fetchHotelPageRefs(lang: Lang, uid: string) {
getHotelPageRefsCounter.add(1, { lang, uid })
console.info(
"contentstack.hotelPage.refs start",
JSON.stringify({
query: { lang, uid },
})
const getHotelPageRefsCounter = createCounter(
"trpc.contentstack",
"hotelPage.get.refs"
)
const metricsGetHotelPageRefs = getHotelPageRefsCounter.init({ lang, uid })
metricsGetHotelPageRefs.start()
const refsResponse = await request<GetHotelPageRefsSchema>(
GetHotelPageRefs,
@@ -56,26 +46,10 @@ export async function fetchHotelPageRefs(lang: Lang, uid: string) {
)
if (!refsResponse.data) {
const notFoundError = notFound(refsResponse)
getHotelPageRefsFailCounter.add(1, {
lang,
uid,
error_type: "http_error",
error: JSON.stringify({
code: notFoundError.code,
}),
})
console.error(
"contentstack.hotelPage.refs not found error",
JSON.stringify({
query: {
lang,
uid,
},
error: { code: notFoundError.code },
})
)
metricsGetHotelPageRefs.noDataError()
throw notFoundError
}
return refsResponse.data
}
@@ -84,30 +58,20 @@ export function validateHotelPageRefs(
lang: Lang,
uid: string
) {
const validatedData = hotelPageRefsSchema.safeParse(data)
if (!validatedData.success) {
getHotelPageRefsFailCounter.add(1, {
lang,
uid,
error_type: "validation_error",
error: JSON.stringify(validatedData.error),
})
console.error(
"contentstack.hotelPage.refs validation error",
JSON.stringify({
query: { lang, uid },
error: validatedData.error,
})
const getHotelPageRefsCounter = createCounter(
"trpc.contentstack",
"hotelPage.get.refs"
)
const metricsGetHotelPageRefs = getHotelPageRefsCounter.init({ lang, uid })
const validatedData = hotelPageRefsSchema.safeParse(data)
if (!validatedData.success) {
metricsGetHotelPageRefs.validationError(validatedData.error)
return null
}
getHotelPageRefsSuccessCounter.add(1, { lang, uid })
console.info(
"contentstack.hotelPage.refs success",
JSON.stringify({
query: { lang, uid },
})
)
metricsGetHotelPageRefs.success()
return validatedData.data
}
@@ -144,11 +108,14 @@ export function getConnections({ hotel_page }: HotelPageRefs) {
}
export async function getHotelPageCount(lang: Lang) {
getHotelPageCountCounter.add(1, { lang })
console.info(
"contentstack.hotelPageCount start",
JSON.stringify({ query: { lang } })
const getHotelPageCountCounter = createCounter(
"trpc.contentstack",
"hotelPageCount.get"
)
const metricsGetHotelPageCount = getHotelPageCountCounter.init({ lang })
metricsGetHotelPageCount.start()
const response = await request<GetHotelPageCountData>(
GetHotelPageCount,
{
@@ -161,50 +128,31 @@ export async function getHotelPageCount(lang: Lang) {
)
if (!response.data) {
getHotelPageCountFailCounter.add(1, {
lang,
error_type: "not_found",
error: `Hotel pages count not found for lang: ${lang}`,
})
console.error(
"contentstack.hotelPageCount not found error",
JSON.stringify({ query: { lang } })
)
metricsGetHotelPageCount.noDataError()
return 0
}
const validatedResponse = hotelPageCountSchema.safeParse(response.data)
if (!validatedResponse.success) {
getHotelPageCountFailCounter.add(1, {
lang,
error_type: "validation_error",
error: JSON.stringify(validatedResponse.error),
})
console.error(
"contentstack.hotelPageCount validation error",
JSON.stringify({
query: { lang },
error: validatedResponse.error,
})
)
metricsGetHotelPageCount.validationError(validatedResponse.error)
return 0
}
getHotelPageCountSuccessCounter.add(1, { lang })
console.info(
"contentstack.hotelPageCount success",
JSON.stringify({ query: { lang } })
)
metricsGetHotelPageCount.success()
return validatedResponse.data
}
export async function getHotelPageUrls(lang: Lang) {
getHotelPageUrlsCounter.add(1, { lang })
console.info(
"contentstack.hotelPageUrls start",
JSON.stringify({ query: { lang } })
const getHotelPageUrlsCounter = createCounter(
"trpc.contentstack",
"hotelPageUrls.get"
)
const metricsGetHotelPageUrls = getHotelPageUrlsCounter.init({ lang })
metricsGetHotelPageUrls.start()
const count = await getHotelPageCount(lang)
if (count === 0) {
@@ -236,25 +184,11 @@ export async function getHotelPageUrls(lang: Lang) {
batchedHotelPageUrlsSchema.safeParse(batchedResponse)
if (!validatedResponse.success) {
getHotelPageUrlsFailCounter.add(1, {
lang,
error_type: "validation_error",
error: JSON.stringify(validatedResponse.error),
})
console.error(
"contentstack.hotelPageUrls validation error",
JSON.stringify({
query: { lang },
error: validatedResponse.error,
})
)
metricsGetHotelPageUrls.validationError(validatedResponse.error)
return []
}
getHotelPageUrlsSuccessCounter.add(1, { lang })
console.info(
"contentstack.hotelPageUrl success",
JSON.stringify({ query: { lang } })
)
metricsGetHotelPageUrls.success()
return validatedResponse.data
}

View File

@@ -1,12 +0,0 @@
import { metrics } from "@opentelemetry/api"
const meter = metrics.getMeter("trpc.contentstack.languageSwitcher")
export const getLanguageSwitcherCounter = meter.createCounter(
"trpc.contentstack.languageSwitcher.get"
)
export const getLanguageSwitcherSuccessCounter = meter.createCounter(
"trpc.contentstack.languageSwitcher.get-success"
)
export const getLanguageSwitcherFailCounter = meter.createCounter(
"trpc.contentstack.languageSwitcher.get-fail"
)

View File

@@ -41,16 +41,12 @@ import {
GetFiNoSvUrlsStartPage,
} from "@/lib/graphql/Query/StartPage/StartPage.graphql"
import { internalServerError } from "@/server/errors/trpc"
import { createCounter } from "@/server/telemetry"
import { generateTag } from "@/utils/generateTag"
import { removeTrailingSlash } from "@/utils/url"
import { validateLanguageSwitcherData } from "./output"
import {
getLanguageSwitcherCounter,
getLanguageSwitcherFailCounter,
getLanguageSwitcherSuccessCounter,
} from "./telemetry"
import { PageContentTypeEnum } from "@/types/requests/contentType"
import type {
@@ -65,21 +61,17 @@ export async function getUrlsOfAllLanguages(
uid: string,
contentType: string
) {
getLanguageSwitcherCounter.add(1, {
uid,
lang,
contentType,
})
console.info(
"contentstack.languageSwitcher start",
JSON.stringify({
query: {
uid,
lang,
contentType,
},
})
const getLanguageSwitcherCounter = createCounter(
"trpc.contentstack",
"languageSwitcher.get"
)
const metricsGetLanguageSwitcher = getLanguageSwitcherCounter.init({
lang,
uid,
contentType,
})
metricsGetLanguageSwitcher.start()
const variables = { uid }
const tagsDaDeEn = [
@@ -184,42 +176,13 @@ export async function getUrlsOfAllLanguages(
validateLanguageSwitcherData.safeParse(urls)
if (!validatedLanguageSwitcherData.success) {
getLanguageSwitcherFailCounter.add(1, {
uid,
lang,
contentType,
error_type: "validation_error",
error: JSON.stringify(validatedLanguageSwitcherData.error),
})
console.error(
"contentstack.languageSwitcher validation error",
JSON.stringify({
query: {
uid,
lang,
contentType,
},
error: validatedLanguageSwitcherData.error,
})
metricsGetLanguageSwitcher.validationError(
validatedLanguageSwitcherData.error
)
return null
}
getLanguageSwitcherSuccessCounter.add(1, {
uid,
lang,
contentType,
})
console.info(
"contentstack.languageSwitcher success",
JSON.stringify({
query: {
uid,
lang,
contentType,
},
})
)
metricsGetLanguageSwitcher.success()
return urls
}

View File

@@ -1,4 +1,3 @@
import { metrics } from "@opentelemetry/api"
import { cache } from "react"
import {
@@ -11,6 +10,7 @@ import {
} from "@/lib/graphql/Query/LoyaltyLevels.graphql"
import { request } from "@/lib/graphql/request"
import { notFound } from "@/server/errors/trpc"
import { createCounter } from "@/server/telemetry"
import { contentstackBaseProcedure, router } from "@/server/trpc"
import { generateLoyaltyConfigTag } from "@/utils/generateTag"
@@ -24,32 +24,14 @@ import {
import type { Context } from "@/server/context"
const meter = metrics.getMeter("trpc.loyaltyLevel")
// OpenTelemetry metrics: Loyalty Level
const getAllLoyaltyLevelCounter = meter.createCounter(
"trpc.contentstack.loyaltyLevel.all"
)
const getAllLoyaltyLevelSuccessCounter = meter.createCounter(
"trpc.contentstack.loyaltyLevel.all-success"
)
const getAllLoyaltyLevelFailCounter = meter.createCounter(
"trpc.contentstack.loyaltyLevel.all-fail"
)
const getByLevelLoyaltyLevelCounter = meter.createCounter(
"trpc.contentstack.loyaltyLevel.byLevel"
)
const getByLevelLoyaltyLevelSuccessCounter = meter.createCounter(
"trpc.contentstack.loyaltyLevel.byLevel-success"
)
const getByLevelLoyaltyLevelFailCounter = meter.createCounter(
"trpc.contentstack.loyaltyLevel.byLevel-fail"
)
export const getAllLoyaltyLevels = cache(async (ctx: Context) => {
getAllLoyaltyLevelCounter.add(1)
const getLoyaltyLevelAllCounter = createCounter(
"trpc.contentstack",
"loyaltyLevel.all"
)
const metricsGetLoyaltyLevelAll = getLoyaltyLevelAllCounter.init()
metricsGetLoyaltyLevelAll.start()
// Ideally we should fetch all available tiers from API, but since they
// are static, we can just use the enum values. We want to know which
@@ -67,17 +49,8 @@ export const getAllLoyaltyLevels = cache(async (ctx: Context) => {
)
if (!loyaltyLevelsConfigResponse.data) {
getAllLoyaltyLevelFailCounter.add(1)
const notFoundError = notFound(loyaltyLevelsConfigResponse)
console.error(
"contentstack.loyaltyLevels not found error",
JSON.stringify({
query: {
lang: ctx.lang,
},
error: { code: notFoundError.code },
})
)
metricsGetLoyaltyLevelAll.noDataError()
throw notFoundError
}
@@ -85,30 +58,28 @@ export const getAllLoyaltyLevels = cache(async (ctx: Context) => {
loyaltyLevelsConfigResponse.data
)
if (!validatedLoyaltyLevels.success) {
getAllLoyaltyLevelFailCounter.add(1)
console.error(validatedLoyaltyLevels.error)
console.error(
"contentstack.rewards validation error",
JSON.stringify({
query: {
lang: ctx.lang,
},
error: validatedLoyaltyLevels.error,
})
)
metricsGetLoyaltyLevelAll.validationError(validatedLoyaltyLevels.error)
return []
}
getAllLoyaltyLevelSuccessCounter.add(1)
metricsGetLoyaltyLevelAll.success()
return validatedLoyaltyLevels.data
})
export const getLoyaltyLevel = cache(
async (ctx: Context, level_id: MembershipLevel) => {
getByLevelLoyaltyLevelCounter.add(1, {
query: JSON.stringify({ lang: ctx.lang, level_id }),
const getLoyaltyLevelCounter = createCounter(
"trpc.contentstack",
"loyaltyLevel.get"
)
const metricsGetLoyaltyLevel = getLoyaltyLevelCounter.init({
lang: ctx.lang,
level_id,
})
metricsGetLoyaltyLevel.start()
const loyaltyLevelsConfigResponse = await request<LoyaltyLevelsResponse>(
GetLoyaltyLevel,
{ lang: ctx.lang, level_id },
@@ -121,15 +92,8 @@ export const getLoyaltyLevel = cache(
!loyaltyLevelsConfigResponse.data ||
!loyaltyLevelsConfigResponse.data.all_loyalty_level.items.length
) {
getByLevelLoyaltyLevelFailCounter.add(1)
const notFoundError = notFound(loyaltyLevelsConfigResponse)
console.error(
"contentstack.loyaltyLevel not found error",
JSON.stringify({
query: { lang: ctx.lang, level_id },
error: { code: notFoundError.code },
})
)
metricsGetLoyaltyLevel.noDataError()
throw notFoundError
}
@@ -137,19 +101,12 @@ export const getLoyaltyLevel = cache(
loyaltyLevelsConfigResponse.data
)
if (!validatedLoyaltyLevels.success) {
getByLevelLoyaltyLevelFailCounter.add(1)
console.error(validatedLoyaltyLevels.error)
console.error(
"contentstack.loyaltyLevel validation error",
JSON.stringify({
query: { lang: ctx.lang, level_id },
error: validatedLoyaltyLevels.error,
})
)
metricsGetLoyaltyLevel.validationError(validatedLoyaltyLevels.error)
return null
}
getByLevelLoyaltyLevelSuccessCounter.add(1)
metricsGetLoyaltyLevel.success()
const result: LoyaltyLevel = validatedLoyaltyLevels.data[0]
return result
}

View File

@@ -1,11 +1,10 @@
import { metrics } from "@opentelemetry/api"
import {
GetLoyaltyPage,
GetLoyaltyPageRefs,
} from "@/lib/graphql/Query/LoyaltyPage/LoyaltyPage.graphql"
import { request } from "@/lib/graphql/request"
import { notFound } from "@/server/errors/trpc"
import { createCounter } from "@/server/telemetry"
import { contentstackExtendedProcedureUID, router } from "@/server/trpc"
import {
@@ -26,39 +25,22 @@ import type {
GetLoyaltyPageSchema,
} from "@/types/trpc/routers/contentstack/loyaltyPage"
const meter = metrics.getMeter("trpc.loyaltyPage")
// OpenTelemetry metrics: LoyaltyPage
const getLoyaltyPageRefsCounter = meter.createCounter(
"trpc.contentstack.loyaltyPage.get"
)
const getLoyaltyPageRefsSuccessCounter = meter.createCounter(
"trpc.contentstack.loyaltyPage.get-success"
)
const getLoyaltyPageRefsFailCounter = meter.createCounter(
"trpc.contentstack.loyaltyPage.get-fail"
)
const getLoyaltyPageCounter = meter.createCounter(
"trpc.contentstack.loyaltyPage.get"
)
const getLoyaltyPageSuccessCounter = meter.createCounter(
"trpc.contentstack.loyaltyPage.get-success"
)
const getLoyaltyPageFailCounter = meter.createCounter(
"trpc.contentstack.loyaltyPage.get-fail"
)
export const loyaltyPageQueryRouter = router({
get: contentstackExtendedProcedureUID.query(async ({ ctx }) => {
const { lang, uid } = ctx
const metricsVariables = { lang, uid }
const variables = { locale: lang, uid }
getLoyaltyPageRefsCounter.add(1, metricsVariables)
console.info(
"contentstack.loyaltyPage.refs start",
JSON.stringify({
query: metricsVariables,
})
const getLoyaltyPageRefsCounter = createCounter(
"trpc.contentstack",
"loyaltyPage.get.refs"
)
const metricsGetLoyaltyPageRefs = getLoyaltyPageRefsCounter.init({
lang,
uid,
})
metricsGetLoyaltyPageRefs.start()
const variables = { locale: lang, uid }
const refsResponse = await request<GetLoyaltyPageRefsSchema>(
GetLoyaltyPageRefs,
variables,
@@ -70,20 +52,7 @@ export const loyaltyPageQueryRouter = router({
if (!refsResponse.data) {
const notFoundError = notFound(refsResponse)
getLoyaltyPageRefsFailCounter.add(1, {
...metricsVariables,
error_type: "http_error",
error: JSON.stringify({
code: notFoundError.code,
}),
})
console.error(
"contentstack.loyaltyPage.refs not found error",
JSON.stringify({
query: metricsVariables,
error: { code: notFoundError.code },
})
)
metricsGetLoyaltyPageRefs.noDataError()
throw notFoundError
}
@@ -91,27 +60,11 @@ export const loyaltyPageQueryRouter = router({
refsResponse.data
)
if (!validatedLoyaltyPageRefs.success) {
getLoyaltyPageRefsFailCounter.add(1, {
...metricsVariables,
error_type: "validation_error",
error: JSON.stringify(validatedLoyaltyPageRefs.error),
})
console.error(
"contentstack.loyaltyPage.refs validation error",
JSON.stringify({
query: metricsVariables,
error: validatedLoyaltyPageRefs.error,
})
)
metricsGetLoyaltyPageRefs.validationError(validatedLoyaltyPageRefs.error)
return null
}
getLoyaltyPageRefsSuccessCounter.add(1, metricsVariables)
console.info(
"contentstack.loyaltyPage.refs success",
JSON.stringify({
query: metricsVariables,
})
)
metricsGetLoyaltyPageRefs.success()
const connections = getConnections(validatedLoyaltyPageRefs.data)
@@ -119,13 +72,15 @@ export const loyaltyPageQueryRouter = router({
generateTagsFromSystem(lang, connections),
generateTag(lang, validatedLoyaltyPageRefs.data.loyalty_page.system.uid),
].flat()
getLoyaltyPageCounter.add(1, metricsVariables)
console.info(
"contentstack.loyaltyPage start",
JSON.stringify({
query: metricsVariables,
})
const getLoyaltyPageCounter = createCounter(
"trpc.contentstack",
"loyaltyPage.get"
)
const metricsGetLoyaltyPage = getLoyaltyPageCounter.init({ lang, uid })
metricsGetLoyaltyPage.start()
const response = await request<GetLoyaltyPageSchema>(
GetLoyaltyPage,
variables,
@@ -137,35 +92,13 @@ export const loyaltyPageQueryRouter = router({
if (!response.data) {
const notFoundError = notFound(response)
getLoyaltyPageFailCounter.add(1, {
...metricsVariables,
error_type: "http_error",
error: JSON.stringify({ code: notFoundError.code }),
})
console.error(
"contentstack.loyaltyPage not found error",
JSON.stringify({
query: metricsVariables,
error: { code: notFoundError.code },
})
)
throw notFound(response)
metricsGetLoyaltyPage.noDataError()
throw notFoundError
}
const validatedLoyaltyPage = loyaltyPageSchema.safeParse(response.data)
if (!validatedLoyaltyPage.success) {
getLoyaltyPageFailCounter.add(1, {
...metricsVariables,
error_type: "validation_error",
error: JSON.stringify(validatedLoyaltyPage.error),
})
console.error(
"contentstack.loyaltyPage validation error",
JSON.stringify({
query: metricsVariables,
error: validatedLoyaltyPage.error,
})
)
metricsGetLoyaltyPage.validationError(validatedLoyaltyPage.error)
return null
}
@@ -182,11 +115,8 @@ export const loyaltyPageQueryRouter = router({
siteSections: validatedLoyaltyPage.data.trackingProps.url,
siteVersion: "new-web",
}
getLoyaltyPageSuccessCounter.add(1, metricsVariables)
console.info(
"contentstack.loyaltyPage success",
JSON.stringify({ query: metricsVariables })
)
metricsGetLoyaltyPage.success()
// Assert LoyaltyPage type to get correct typings for RTE fields
return {

View File

@@ -1,4 +1,3 @@
import { metrics } from "@opentelemetry/api"
import { cache } from "react"
import { GetAccountPageMetadata } from "@/lib/graphql/Query/AccountPage/Metadata.graphql"
@@ -12,6 +11,7 @@ import { GetLoyaltyPageMetadata } from "@/lib/graphql/Query/LoyaltyPage/Metadata
import { GetStartPageMetadata } from "@/lib/graphql/Query/StartPage/Metadata.graphql"
import { request } from "@/lib/graphql/request"
import { notFound } from "@/server/errors/trpc"
import { createCounter } from "@/server/telemetry"
import { contentStackUidWithServiceProcedure, router } from "@/server/trpc"
import { generateTag } from "@/utils/generateTag"
@@ -29,37 +29,15 @@ import type { LanguageSwitcherData } from "@/types/requests/languageSwitcher"
import type { RawMetadataSchema } from "@/types/trpc/routers/contentstack/metadata"
import type { Lang } from "@/constants/languages"
const meter = metrics.getMeter("trpc.metadata")
// OpenTelemetry metrics
const fetchMetadataCounter = meter.createCounter(
"trpc.contentstack.metadata.get"
)
const fetchMetadataSuccessCounter = meter.createCounter(
"trpc.contentstack.metadata.get-success"
)
const fetchMetadataFailCounter = meter.createCounter(
"trpc.contentstack.metadata.get-fail"
)
const transformMetadataCounter = meter.createCounter(
"trpc.contentstack.metadata.transform"
)
const transformMetadataSuccessCounter = meter.createCounter(
"trpc.contentstack.metadata.transform-success"
)
const transformMetadataFailCounter = meter.createCounter(
"trpc.contentstack.metadata.transform-fail"
)
const fetchMetadata = cache(async function fetchMemoizedMetadata<T>(
query: string,
{ uid, lang }: { uid: string; lang: Lang }
) {
fetchMetadataCounter.add(1, { lang, uid })
console.info(
"contentstack.metadata fetch start",
JSON.stringify({ query: { lang, uid } })
)
const getMetadataCounter = createCounter("trpc.contentstack", "metadata.get")
const metricsGetMetadata = getMetadataCounter.init({ lang, uid })
metricsGetMetadata.start()
const response = await request<T>(
query,
{ locale: lang, uid },
@@ -68,54 +46,35 @@ const fetchMetadata = cache(async function fetchMemoizedMetadata<T>(
ttl: "max",
}
)
if (!response.data) {
const notFoundError = notFound(response)
fetchMetadataFailCounter.add(1, {
lang,
uid,
error_type: "not_found",
error: JSON.stringify({ code: notFoundError.code }),
})
console.error(
"contentstack.metadata fetch not found error",
JSON.stringify({
query: { lang, uid },
error: { code: notFoundError.code },
})
)
metricsGetMetadata.noDataError()
throw notFoundError
}
fetchMetadataSuccessCounter.add(1, { lang, uid })
console.info(
"contentstack.metadata fetch success",
JSON.stringify({ query: { lang, uid } })
)
metricsGetMetadata.success()
return response.data
})
async function getTransformedMetadata(data: unknown) {
transformMetadataCounter.add(1)
console.info("contentstack.metadata transform start")
const transformMetadataCounter = createCounter(
"trpc.contentstack",
"metadata.transform"
)
const metricsTransformMetadata = transformMetadataCounter.init()
metricsTransformMetadata.start()
const validatedMetadata = await metadataSchema.safeParseAsync(data)
if (!validatedMetadata.success) {
transformMetadataFailCounter.add(1, {
error_type: "validation_error",
error: JSON.stringify(validatedMetadata.error),
})
console.error(
"contentstack.metadata validation error",
JSON.stringify({
error: validatedMetadata.error,
})
)
metricsTransformMetadata.validationError(validatedMetadata.error)
return null
}
transformMetadataSuccessCounter.add(1)
console.info("contentstack.metadata transform success")
metricsTransformMetadata.success()
return validatedMetadata.data
}

View File

@@ -1,9 +1,9 @@
import { metrics } from "@opentelemetry/api"
import { cache } from "react"
import { GetAllSasTierComparison } from "@/lib/graphql/Query/SASTierComparison.graphql"
import { request } from "@/lib/graphql/request"
import { notFound } from "@/server/errors/trpc"
import { createCounter } from "@/server/telemetry"
import { contentstackBaseProcedure, router } from "@/server/trpc"
import { validateSasTierComparisonSchema } from "./output"
@@ -11,20 +11,16 @@ import { validateSasTierComparisonSchema } from "./output"
import type { SasTierComparisonResponse } from "@/types/trpc/routers/contentstack/partner"
import type { Context } from "@/server/context"
const meter = metrics.getMeter("trpc.partner")
const getSasTierComparisonCounter = meter.createCounter(
"trpc.contentstack.partner.getSasTierComparison"
)
const getSasTierComparisonSuccessCounter = meter.createCounter(
"trpc.contentstack.partner.getSasTierComparison-success"
)
const getSasTierComparisonFailCounter = meter.createCounter(
"trpc.contentstack.partner.getSasTierComparison-fail"
)
export const getSasTierComparison = cache(async (ctx: Context) => {
getSasTierComparisonCounter.add(1)
const getSasTierComparisonCounter = createCounter(
"trpc.contentstack",
"partner.getSasTierComparison"
)
const metricsGetSasTierComparison = getSasTierComparisonCounter.init({
lang: ctx.lang,
})
metricsGetSasTierComparison.start()
const tag = `${ctx.lang}:sas_tier_comparison`
const sasTierComparisonConfigResponse =
@@ -38,17 +34,8 @@ export const getSasTierComparison = cache(async (ctx: Context) => {
)
if (!sasTierComparisonConfigResponse.data) {
getSasTierComparisonFailCounter.add(1)
const notFoundError = notFound(sasTierComparisonConfigResponse)
console.error(
"contentstack.sas not found error",
JSON.stringify({
query: {
lang: ctx.lang,
},
error: { code: notFoundError.code },
})
)
metricsGetSasTierComparison.noDataError()
throw notFoundError
}
@@ -57,21 +44,14 @@ export const getSasTierComparison = cache(async (ctx: Context) => {
)
if (!validatedSasTierComparison.success) {
getSasTierComparisonFailCounter.add(1)
console.error(validatedSasTierComparison.error)
console.error(
"contentstack.sas validation error",
JSON.stringify({
query: {
lang: ctx.lang,
},
error: validatedSasTierComparison.error,
})
metricsGetSasTierComparison.validationError(
validatedSasTierComparison.error
)
return null
}
getSasTierComparisonSuccessCounter.add(1)
metricsGetSasTierComparison.success()
return validatedSasTierComparison.data
})

View File

@@ -1,5 +1,6 @@
import * as api from "@/lib/api"
import { notFound } from "@/server/errors/trpc"
import { createCounter } from "@/server/telemetry"
import {
contentStackBaseWithProtectedProcedure,
contentStackBaseWithServiceProcedure,
@@ -22,24 +23,9 @@ import {
} from "./input"
import { validateCategorizedRewardsSchema } from "./output"
import {
getAllRewardCounter,
getAllRewardFailCounter,
getAllRewardSuccessCounter,
getByLevelRewardCounter,
getByLevelRewardFailCounter,
getByLevelRewardSuccessCounter,
getCachedAllTierRewards,
getCmsRewards,
getCurrentRewardCounter,
getCurrentRewardFailCounter,
getCurrentRewardSuccessCounter,
getRedeemCounter,
getRedeemFailCounter,
getRedeemSuccessCounter,
getUniqueRewardIds,
getUnwrapSurpriseCounter,
getUnwrapSurpriseFailCounter,
getUnwrapSurpriseSuccessCounter,
} from "./utils"
import type { BaseReward, Surprise } from "@/types/components/myPages/rewards"
@@ -50,7 +36,14 @@ export const rewardQueryRouter = router({
all: contentStackBaseWithServiceProcedure
.input(rewardsAllInput)
.query(async function ({ input, ctx }) {
getAllRewardCounter.add(1)
const getContentstackRewardAllCounter = createCounter(
"trpc.contentstack",
"reward.all"
)
const metricsGetContentstackRewardAll =
getContentstackRewardAllCounter.init()
metricsGetContentstackRewardAll.start()
const allApiRewards = await getCachedAllTierRewards(ctx.serviceToken)
@@ -75,16 +68,23 @@ export const rewardQueryRouter = router({
const levelsWithRewards = Object.entries(allApiRewards).map(
([level, rewards]) => {
const combinedRewards = rewards
.filter((r) => (input.unique ? r?.rewardTierLevel === level : true))
.filter((reward) =>
input.unique ? reward.rewardTierLevel === level : true
)
.map((reward) => {
const contentStackReward = contentStackRewards.find((r) => {
return r.reward_id === reward?.rewardId
return r.reward_id === reward.rewardId
})
if (contentStackReward) {
return contentStackReward
} else {
console.error("No contentStackReward found", reward?.rewardId)
metricsGetContentstackRewardAll.dataError(
`Failed to find reward in CMS for reward ${reward.rewardId} `,
{
rewardId: reward.rewardId,
}
)
}
})
.filter((reward): reward is CMSReward => Boolean(reward))
@@ -94,9 +94,9 @@ export const rewardQueryRouter = router({
)
if (!levelConfig) {
getAllRewardFailCounter.add(1)
console.error("contentstack.loyaltyLevels level not found")
metricsGetContentstackRewardAll.dataError(
`Failed to matched loyalty level between API and CMS for level ${level}`
)
throw notFound()
}
const result: LevelWithRewards = {
@@ -107,21 +107,31 @@ export const rewardQueryRouter = router({
}
)
getAllRewardSuccessCounter.add(1)
metricsGetContentstackRewardAll.success()
return levelsWithRewards
}),
byLevel: contentStackBaseWithServiceProcedure
.input(rewardsByLevelInput)
.query(async function ({ input, ctx }) {
getByLevelRewardCounter.add(1)
const { level_id } = input
const getRewardByLevelCounter = createCounter(
"trpc.contentstack",
"reward.byLevel"
)
const metricsGetRewardByLevel = getRewardByLevelCounter.init({
level_id,
})
metricsGetRewardByLevel.start()
const allUpcomingApiRewards = await getCachedAllTierRewards(
ctx.serviceToken
)
if (!allUpcomingApiRewards || !allUpcomingApiRewards[level_id]) {
getByLevelRewardFailCounter.add(1)
metricsGetRewardByLevel.noDataError()
return null
}
@@ -150,24 +160,36 @@ export const rewardQueryRouter = router({
const levelsWithRewards = apiRewards
.map((reward) => {
const contentStackReward = contentStackRewards.find((r) => {
return r.reward_id === reward?.rewardId
return r.reward_id === reward.rewardId
})
if (contentStackReward) {
return contentStackReward
} else {
console.info("No contentStackReward found", reward?.rewardId)
metricsGetRewardByLevel.dataError(
`Failed to find reward in Contentstack with rewardId: ${reward.rewardId}`,
{
rewardId: reward.rewardId,
}
)
}
})
.filter((reward): reward is CMSReward => Boolean(reward))
getByLevelRewardSuccessCounter.add(1)
metricsGetRewardByLevel.success()
return { level: loyaltyLevelsConfig, rewards: levelsWithRewards }
}),
current: contentStackBaseWithProtectedProcedure
.input(langInput.optional()) // lang is required for client, but not for server
.query(async function ({ ctx }) {
getCurrentRewardCounter.add(1)
const getCurrentRewardCounter = createCounter(
"trpc.contentstack",
"reward.current"
)
const metricsGetCurrentReward = getCurrentRewardCounter.init()
metricsGetCurrentReward.start()
const apiResponse = await api.get(
api.endpoints.v1.Profile.Reward.reward,
@@ -179,25 +201,7 @@ export const rewardQueryRouter = router({
)
if (!apiResponse.ok) {
const text = await apiResponse.text()
getCurrentRewardFailCounter.add(1, {
error_type: "http_error",
error: JSON.stringify({
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
}),
})
console.error(
"api.reward error ",
JSON.stringify({
error: {
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
},
})
)
await metricsGetCurrentReward.httpError(apiResponse)
return null
}
@@ -207,19 +211,7 @@ export const rewardQueryRouter = router({
validateCategorizedRewardsSchema.safeParse(data)
if (!validatedApiRewards.success) {
getCurrentRewardFailCounter.add(1, {
locale: ctx.lang,
error_type: "validation_error",
error: JSON.stringify(validatedApiRewards.error),
})
console.error(validatedApiRewards.error)
console.error(
"contentstack.rewards validation error",
JSON.stringify({
query: { locale: ctx.lang },
error: validatedApiRewards.error,
})
)
metricsGetCurrentReward.validationError(validatedApiRewards.error)
return null
}
@@ -243,14 +235,20 @@ export const rewardQueryRouter = router({
}
})
getCurrentRewardSuccessCounter.add(1)
metricsGetCurrentReward.success()
return { rewards }
}),
surprises: contentStackBaseWithProtectedProcedure
.input(langInput.optional()) // lang is required for client, but not for server
.query(async ({ ctx }) => {
getCurrentRewardCounter.add(1)
const getSurprisesCounter = createCounter(
"trpc.contentstack",
"surprises"
)
const metricsGetSurprises = getSurprisesCounter.init()
metricsGetSurprises.start()
const endpoint = api.endpoints.v1.Profile.Reward.reward
@@ -262,25 +260,7 @@ export const rewardQueryRouter = router({
})
if (!apiResponse.ok) {
const text = await apiResponse.text()
getCurrentRewardFailCounter.add(1, {
error_type: "http_error",
error: JSON.stringify({
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
}),
})
console.error(
"api.reward error ",
JSON.stringify({
error: {
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
},
})
)
await metricsGetSurprises.httpError(apiResponse)
return null
}
@@ -289,19 +269,7 @@ export const rewardQueryRouter = router({
validateCategorizedRewardsSchema.safeParse(data)
if (!validatedApiRewards.success) {
getCurrentRewardFailCounter.add(1, {
locale: ctx.lang,
error_type: "validation_error",
error: JSON.stringify(validatedApiRewards.error),
})
console.error(validatedApiRewards.error)
console.error(
"contentstack.surprises validation error",
JSON.stringify({
query: { locale: ctx.lang },
error: validatedApiRewards.error,
})
)
metricsGetSurprises.validationError(validatedApiRewards.error)
return null
}
@@ -316,8 +284,6 @@ export const rewardQueryRouter = router({
return null
}
getCurrentRewardSuccessCounter.add(1)
const surprises: Surprise[] = cmsRewards
.map((cmsReward) => {
// Non-null assertion is used here because we know our reward exist
@@ -336,15 +302,32 @@ export const rewardQueryRouter = router({
})
.flatMap((surprises) => (surprises ? [surprises] : []))
metricsGetSurprises.success()
return surprises
}),
unwrap: protectedProcedure
.input(rewardsUpdateInput)
.mutation(async ({ input, ctx }) => {
getUnwrapSurpriseCounter.add(1)
const results = await Promise.allSettled(
// Execute each unwrap individually
input.map(({ rewardId, couponCode }) => {
async function handleUnwrap() {
const getUnwrapSurpriseCounter = createCounter(
"trpc.contentstack",
"reward.unwrap"
)
const promises = input.map(({ rewardId, couponCode }) => {
return api.post(api.endpoints.v1.Profile.Reward.unwrap, {
const metricsGetUnwrapSurprise = getUnwrapSurpriseCounter.init({
rewardId,
couponCode,
})
metricsGetUnwrapSurprise.start()
const apiResponse = await api.post(
api.endpoints.v1.Profile.Reward.unwrap,
{
body: {
rewardId,
couponCode,
@@ -352,57 +335,47 @@ export const rewardQueryRouter = router({
headers: {
Authorization: `Bearer ${ctx.session.token.access_token}`,
},
})
})
const responses = await Promise.all(promises)
const errors = await Promise.all(
responses.map(async (apiResponse) => {
if (!apiResponse.ok) {
const text = await apiResponse.text()
getUnwrapSurpriseFailCounter.add(1, {
error_type: "http_error",
error: JSON.stringify({
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
}),
})
console.error(
"contentstack.unwrap API error",
JSON.stringify({
error: {
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
},
query: {},
})
}
)
if (!apiResponse.ok) {
metricsGetUnwrapSurprise.httpError(apiResponse)
return false
}
metricsGetUnwrapSurprise.success()
return true
}
return handleUnwrap()
})
)
if (errors.filter((ok) => !ok).length > 0) {
if (
results.some(
(result) => result.status === "rejected" || result.value === false
)
) {
return null
}
getUnwrapSurpriseSuccessCounter.add(1)
return true
}),
redeem: protectedProcedure
.input(rewardsRedeemInput)
.mutation(async ({ input, ctx }) => {
getRedeemCounter.add(1)
const { rewardId, couponCode } = input
const getRedeemCounter = createCounter(
"trpc.contentstack",
"reward.redeem"
)
const metricGetRedeem = getRedeemCounter.init({ rewardId, couponCode })
metricGetRedeem.start()
const apiResponse = await api.post(
api.endpoints.v1.Profile.Reward.redeem,
{
@@ -417,29 +390,11 @@ export const rewardQueryRouter = router({
)
if (!apiResponse.ok) {
const text = await apiResponse.text()
getRedeemFailCounter.add(1, {
error_type: "http_error",
error: JSON.stringify({
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
}),
})
console.error(
"api.redeem error ",
JSON.stringify({
error: {
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
},
})
)
metricGetRedeem.httpError(apiResponse)
return null
}
getRedeemSuccessCounter.add(1)
metricGetRedeem.success()
return true
}),

View File

@@ -1,5 +1,3 @@
import { metrics } from "@opentelemetry/api"
import * as api from "@/lib/api"
import {
GetRewards as GetRewards,
@@ -7,6 +5,7 @@ import {
} from "@/lib/graphql/Query/RewardsWithRedeem.graphql"
import { request } from "@/lib/graphql/request"
import { notFound } from "@/server/errors/trpc"
import { createCounter } from "@/server/telemetry"
import { getCacheClient } from "@/services/dataCache"
import {
@@ -26,63 +25,6 @@ import type {
} from "@/types/trpc/routers/contentstack/reward"
import type { Lang } from "@/constants/languages"
const meter = metrics.getMeter("trpc.reward")
export const getAllRewardCounter = meter.createCounter(
"trpc.contentstack.reward.all"
)
export const getAllRewardFailCounter = meter.createCounter(
"trpc.contentstack.reward.all-fail"
)
export const getAllRewardSuccessCounter = meter.createCounter(
"trpc.contentstack.reward.all-success"
)
export const getCurrentRewardCounter = meter.createCounter(
"trpc.contentstack.reward.current"
)
export const getCurrentRewardFailCounter = meter.createCounter(
"trpc.contentstack.reward.current-fail"
)
export const getCurrentRewardSuccessCounter = meter.createCounter(
"trpc.contentstack.reward.current-success"
)
export const getByLevelRewardCounter = meter.createCounter(
"trpc.contentstack.reward.byLevel"
)
export const getByLevelRewardFailCounter = meter.createCounter(
"trpc.contentstack.reward.byLevel-fail"
)
export const getByLevelRewardSuccessCounter = meter.createCounter(
"trpc.contentstack.reward.byLevel-success"
)
export const getUnwrapSurpriseCounter = meter.createCounter(
"trpc.contentstack.reward.unwrap"
)
export const getUnwrapSurpriseFailCounter = meter.createCounter(
"trpc.contentstack.reward.unwrap-fail"
)
export const getUnwrapSurpriseSuccessCounter = meter.createCounter(
"trpc.contentstack.reward.unwrap-success"
)
export const getRedeemCounter = meter.createCounter(
"trpc.contentstack.reward.redeem"
)
export const getRedeemFailCounter = meter.createCounter(
"trpc.contentstack.reward.redeem-fail"
)
export const getRedeemSuccessCounter = meter.createCounter(
"trpc.contentstack.reward.redeem-success"
)
export const getAllCMSRewardRefsCounter = meter.createCounter(
"trpc.contentstack.reward.all"
)
export const getAllCMSRewardRefsFailCounter = meter.createCounter(
"trpc.contentstack.reward.all-fail"
)
export const getAllCMSRewardRefsSuccessCounter = meter.createCounter(
"trpc.contentstack.reward.all-success"
)
export function getUniqueRewardIds(rewardIds: string[]) {
const uniqueRewardIds = new Set(rewardIds)
return Array.from(uniqueRewardIds)
@@ -97,6 +39,14 @@ export async function getCachedAllTierRewards(token: string) {
return await cacheClient.cacheOrGet(
"getAllTierRewards",
async () => {
const getApiRewardAllTiersCounter = createCounter(
"trpc.api",
"reward.allTiers"
)
const metricsGetApiRewardAllTiers = getApiRewardAllTiersCounter.init()
metricsGetApiRewardAllTiers.start()
const apiResponse = await api.get(
api.endpoints.v1.Profile.Reward.allTiers,
{
@@ -107,26 +57,7 @@ export async function getCachedAllTierRewards(token: string) {
)
if (!apiResponse.ok) {
const text = await apiResponse.text()
getAllRewardFailCounter.add(1, {
error_type: "http_error",
error: JSON.stringify({
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
}),
})
console.error(
"api.rewards.allTiers error ",
JSON.stringify({
error: {
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
},
})
)
metricsGetApiRewardAllTiers.httpError(apiResponse)
throw apiResponse
}
@@ -135,20 +66,14 @@ export async function getCachedAllTierRewards(token: string) {
validateApiAllTiersSchema.safeParse(data)
if (!validatedApiAllTierRewards.success) {
getAllRewardFailCounter.add(1, {
error_type: "validation_error",
error: JSON.stringify(validatedApiAllTierRewards.error),
})
console.error(validatedApiAllTierRewards.error)
console.error(
"api.rewards validation error",
JSON.stringify({
error: validatedApiAllTierRewards.error,
})
metricsGetApiRewardAllTiers.validationError(
validatedApiAllTierRewards.error
)
throw validatedApiAllTierRewards.error
}
metricsGetApiRewardAllTiers.success()
return validatedApiAllTierRewards.data
},
"1h"
@@ -164,13 +89,15 @@ export async function getCmsRewards(lang: Lang, rewardIds: string[]) {
generateLoyaltyConfigTag(lang, "reward", id)
)
getAllCMSRewardRefsCounter.add(1, { lang, rewardIds })
console.info(
"contentstack.reward.refs start",
JSON.stringify({
query: { lang, rewardIds },
})
const getContentstackRewardAllRefsCounter = createCounter(
"trpc.contentstack",
"reward.all.refs"
)
const metricsGetContentstackRewardAllRefs =
getContentstackRewardAllRefsCounter.init({ lang, rewardIds })
metricsGetContentstackRewardAllRefs.start()
const refsResponse = await request<GetRewardRefsSchema>(
GetRewardsRef,
{
@@ -182,50 +109,30 @@ export async function getCmsRewards(lang: Lang, rewardIds: string[]) {
ttl: "max",
}
)
if (!refsResponse.data) {
const notFoundError = notFound(refsResponse)
getAllCMSRewardRefsFailCounter.add(1, {
lang,
rewardIds,
error_type: "not_found",
error: JSON.stringify({ code: notFoundError.code }),
})
console.error(
"contentstack.reward.refs not found error",
JSON.stringify({
query: { lang, rewardIds },
error: { code: notFoundError.code },
})
)
metricsGetContentstackRewardAllRefs.noDataError()
throw notFoundError
}
const validatedRefsData = rewardRefsSchema.safeParse(refsResponse)
if (!validatedRefsData.success) {
getAllCMSRewardRefsFailCounter.add(1, {
lang,
rewardIds,
error_type: "validation_error",
error: JSON.stringify(validatedRefsData.error),
})
console.error(
"contentstack.reward.refs validation error",
JSON.stringify({
query: { lang, rewardIds },
error: validatedRefsData.error,
})
)
metricsGetContentstackRewardAllRefs.validationError(validatedRefsData.error)
return null
}
getAllCMSRewardRefsSuccessCounter.add(1, { lang, rewardIds })
console.info(
"contentstack.startPage.refs success",
JSON.stringify({
query: { lang, rewardIds },
})
metricsGetContentstackRewardAllRefs.success()
const getContentstackRewardAllCounter = createCounter(
"trpc.contentstack",
"reward.all"
)
const metricsGetContentstackRewardAll = getContentstackRewardAllCounter.init({
lang,
rewardIds,
})
const cmsRewardsResponse = await request<CMSRewardsResponse>(
GetRewards,
@@ -240,22 +147,8 @@ export async function getCmsRewards(lang: Lang, rewardIds: string[]) {
)
if (!cmsRewardsResponse.data) {
getAllRewardFailCounter.add(1, {
lang,
error_type: "validation_error",
error: JSON.stringify(cmsRewardsResponse.data),
})
const notFoundError = notFound(cmsRewardsResponse)
console.error(
"contentstack.rewards not found error",
JSON.stringify({
query: {
locale: lang,
rewardIds,
},
error: { code: notFoundError.code },
})
)
metricsGetContentstackRewardAll.noDataError()
throw notFoundError
}
@@ -263,22 +156,11 @@ export async function getCmsRewards(lang: Lang, rewardIds: string[]) {
validateCmsRewardsSchema.safeParse(cmsRewardsResponse)
if (!validatedCmsRewards.success) {
getAllRewardFailCounter.add(1, {
locale: lang,
rewardIds,
error_type: "validation_error",
error: JSON.stringify(validatedCmsRewards.error),
})
console.error(validatedCmsRewards.error)
console.error(
"contentstack.rewards validation error",
JSON.stringify({
query: { locale: lang, rewardIds },
error: validatedCmsRewards.error,
})
)
metricsGetContentstackRewardAll.validationError(validatedCmsRewards.error)
return null
}
metricsGetContentstackRewardAll.success()
return validatedCmsRewards.data
}

View File

@@ -4,6 +4,7 @@ import {
} from "@/lib/graphql/Query/StartPage/StartPage.graphql"
import { request } from "@/lib/graphql/request"
import { notFound } from "@/server/errors/trpc"
import { createCounter } from "@/server/telemetry"
import { contentstackExtendedProcedureUID, router } from "@/server/trpc"
import {
@@ -13,14 +14,6 @@ import {
} from "@/utils/generateTag"
import { startPageRefsSchema, startPageSchema } from "./output"
import {
getStartPageCounter,
getStartPageFailCounter,
getStartPageRefsCounter,
getStartPageRefsFailCounter,
getStartPageRefsSuccessCounter,
getStartPageSuccessCounter,
} from "./telemetry"
import { getConnections } from "./utils"
import {
@@ -36,13 +29,14 @@ export const startPageQueryRouter = router({
get: contentstackExtendedProcedureUID.query(async ({ ctx }) => {
const { lang, uid } = ctx
getStartPageRefsCounter.add(1, { lang, uid: `${uid}` })
console.info(
"contentstack.startPage.refs start",
JSON.stringify({
query: { lang, uid },
})
const getStartPageRefsCounter = createCounter(
"trpc.contentstack",
"startPage.get.refs"
)
const metricsGetStartPageRefs = getStartPageRefsCounter.init({ lang, uid })
metricsGetStartPageRefs.start()
const refsResponse = await request<GetStartPageRefsSchema>(
GetStartPageRefs,
{
@@ -56,56 +50,26 @@ export const startPageQueryRouter = router({
)
if (!refsResponse.data) {
const notFoundError = notFound(refsResponse)
getStartPageRefsFailCounter.add(1, {
lang,
uid,
error_type: "not_found",
error: JSON.stringify({ code: notFoundError.code }),
})
console.error(
"contentstack.startPage.refs not found error",
JSON.stringify({
query: { lang, uid },
error: { code: notFoundError.code },
})
)
metricsGetStartPageRefs.noDataError()
throw notFoundError
}
const validatedRefsData = startPageRefsSchema.safeParse(refsResponse.data)
if (!validatedRefsData.success) {
getStartPageRefsFailCounter.add(1, {
lang,
uid,
error_type: "validation_error",
error: JSON.stringify(validatedRefsData.error),
})
console.error(
"contentstack.startPage.refs validation error",
JSON.stringify({
query: { lang, uid },
error: validatedRefsData.error,
})
)
metricsGetStartPageRefs.validationError(validatedRefsData.error)
return null
}
getStartPageRefsSuccessCounter.add(1, { lang, uid: `${uid}` })
console.info(
"contentstack.startPage.refs success",
JSON.stringify({
query: { lang, uid },
})
)
metricsGetStartPageRefs.success()
getStartPageCounter.add(1, { lang, uid: `${uid}` })
console.info(
"contentstack.startPage start",
JSON.stringify({
query: { lang, uid },
})
const getStartPageCounter = createCounter(
"trpc.contentstack",
"startPage.get"
)
const metricsGetStartPage = getStartPageCounter.init({ lang, uid })
metricsGetStartPage.start()
const connections = getConnections(validatedRefsData.data)
@@ -113,6 +77,7 @@ export const startPageQueryRouter = router({
generateTagsFromSystem(lang, connections),
generateTag(lang, validatedRefsData.data.start_page.system.uid),
].flat()
const response = await request<GetStartPageData>(
GetStartPage,
{
@@ -127,48 +92,18 @@ export const startPageQueryRouter = router({
if (!response.data) {
const notFoundError = notFound(response)
getStartPageFailCounter.add(1, {
lang,
uid: `${uid}`,
error_type: "not_found",
error: JSON.stringify({ code: notFoundError.code }),
})
console.error(
"contentstack.startPage not found error",
JSON.stringify({
query: { lang, uid },
error: { code: notFoundError.code },
})
)
metricsGetStartPage.noDataError()
throw notFoundError
}
const startPage = startPageSchema.safeParse(response.data)
if (!startPage.success) {
getStartPageFailCounter.add(1, {
lang,
uid: `${uid}`,
error_type: "validation_error",
error: JSON.stringify(startPage.error),
})
console.error(
"contentstack.startPage validation error",
JSON.stringify({
query: { lang, uid },
error: startPage.error,
})
)
metricsGetStartPage.validationError(startPage.error)
return null
}
getStartPageSuccessCounter.add(1, { lang, uid: `${uid}` })
console.info(
"contentstack.startPage success",
JSON.stringify({
query: { lang, uid },
})
)
metricsGetStartPage.success()
const system = startPage.data.start_page.system
const tracking: TrackingSDKPageData = {

View File

@@ -1,23 +0,0 @@
import { metrics } from "@opentelemetry/api"
const meter = metrics.getMeter("trpc.contentstack.startPage")
export const getStartPageRefsCounter = meter.createCounter(
"trpc.contentstack.startPage.get"
)
export const getStartPageRefsFailCounter = meter.createCounter(
"trpc.contentstack.startPage.get-fail"
)
export const getStartPageRefsSuccessCounter = meter.createCounter(
"trpc.contentstack.startPage.get-success"
)
export const getStartPageCounter = meter.createCounter(
"trpc.contentstack.startPage.get"
)
export const getStartPageSuccessCounter = meter.createCounter(
"trpc.contentstack.startPage.get-success"
)
export const getStartPageFailCounter = meter.createCounter(
"trpc.contentstack.startPage.get-fail"
)

View File

@@ -1,88 +0,0 @@
import { metrics as opentelemetryMetrics } from "@opentelemetry/api"
const meter = opentelemetryMetrics.getMeter("trpc.hotels")
export const metrics = {
additionalData: {
counter: meter.createCounter("trpc.hotels.additionalData"),
fail: meter.createCounter("trpc.hotels.additionalData-fail"),
success: meter.createCounter("trpc.hotels.additionalData-success"),
},
breakfastPackage: {
counter: meter.createCounter("trpc.package.breakfast"),
fail: meter.createCounter("trpc.package.breakfast-fail"),
success: meter.createCounter("trpc.package.breakfast-success"),
},
ancillaryPackage: {
counter: meter.createCounter("trpc.package.ancillary"),
fail: meter.createCounter("trpc.package.ancillary-fail"),
success: meter.createCounter("trpc.package.ancillary-success"),
},
hotel: {
counter: meter.createCounter("trpc.hotel.get"),
fail: meter.createCounter("trpc.hotel.get-fail"),
success: meter.createCounter("trpc.hotel.get-success"),
},
hotels: {
counter: meter.createCounter("trpc.hotel.hotels.get"),
fail: meter.createCounter("trpc.hotel.hotels.get-fail"),
success: meter.createCounter("trpc.hotel.hotels.get-success"),
},
hotelIds: {
counter: meter.createCounter("trpc.hotel.hotel-ids.get"),
fail: meter.createCounter("trpc.hotel.hotel-ids.get-fail"),
success: meter.createCounter("trpc.hotel.hotel-ids.get-success"),
},
hotelsAvailability: {
counter: meter.createCounter("trpc.hotel.availability.hotels"),
fail: meter.createCounter("trpc.hotel.availability.hotels-fail"),
success: meter.createCounter("trpc.hotel.availability.hotels-success"),
},
hotelsAvailabilityBookingCode: {
counter: meter.createCounter("trpc.hotel.availability.hotels-booking-code"),
fail: meter.createCounter(
"trpc.hotel.availability.hotels-booking-code-fail"
),
success: meter.createCounter(
"trpc.hotel.availability.hotels-booking-code-success"
),
},
hotelsByHotelIdAvailability: {
counter: meter.createCounter("trpc.hotel.availability.hotels-by-hotel-id"),
fail: meter.createCounter(
"trpc.hotel.availability.hotels-by-hotel-id-fail"
),
success: meter.createCounter(
"trpc.hotel.availability.hotels-by-hotel-id-success"
),
},
meetingRooms: {
counter: meter.createCounter("trpc.hotels.meetingRooms"),
fail: meter.createCounter("trpc.hotels.meetingRooms-fail"),
success: meter.createCounter("trpc.hotels.meetingRooms-success"),
},
nearbyHotelIds: {
counter: meter.createCounter("trpc.hotel.nearby-hotel-ids.get"),
fail: meter.createCounter("trpc.hotel.nearby-hotel-ids.get-fail"),
success: meter.createCounter("trpc.hotel.nearby-hotel-ids.get-success"),
},
packages: {
counter: meter.createCounter("trpc.hotel.packages.get"),
fail: meter.createCounter("trpc.hotel.packages.get-fail"),
success: meter.createCounter("trpc.hotel.packages.get-success"),
},
roomsAvailability: {
counter: meter.createCounter("trpc.hotel.roomsAvailability.rooms"),
fail: meter.createCounter("trpc.hotel.roomsAvailability.rooms-fail"),
success: meter.createCounter("trpc.hotel.roomsAvailability.rooms-success"),
},
selectedRoomAvailability: {
counter: meter.createCounter("trpc.hotel.availability.room"),
fail: meter.createCounter("trpc.hotel.availability.room-fail"),
success: meter.createCounter("trpc.hotel.availability.room-success"),
},
roomFeatures: {
counter: meter.createCounter("trpc.availability.roomfeature"),
fail: meter.createCounter("trpc.availability.roomfeature-fail"),
success: meter.createCounter("trpc.availability.roomfeature-success"),
},
}

View File

@@ -5,6 +5,8 @@ import * as api from "@/lib/api"
import { dt } from "@/lib/dt"
import { badRequestError, unauthorizedError } from "@/server/errors/trpc"
import { getCityPageUrls } from "@/server/routers/contentstack/destinationCityPage/utils"
import { getVerifiedUser } from "@/server/routers/user/utils"
import { createCounter } from "@/server/telemetry"
import {
contentStackBaseWithServiceProcedure,
publicProcedure,
@@ -17,7 +19,6 @@ import { toApiLang } from "@/server/utils"
import { getCacheClient } from "@/services/dataCache"
import { getHotelPageUrls } from "../contentstack/hotelPage/utils"
import { getVerifiedUser } from "../user/query"
import { additionalDataSchema } from "./schemas/hotel/include/additionalData"
import { meetingRoomsSchema } from "./schemas/meetingRoom"
import {
@@ -42,17 +43,11 @@ import {
selectRateRoomAvailabilityInputSchema,
selectRateRoomsAvailabilityInputSchema,
} from "./input"
import { metrics } from "./metrics"
import {
ancillaryPackagesSchema,
breakfastPackagesSchema,
getNearbyHotelIdsSchema,
} from "./output"
import {
locationsUrlsCounter,
locationsUrlsFailCounter,
locationsUrlsSuccessCounter,
} from "./telemetry"
import {
getBedTypes,
getCitiesByCountry,
@@ -431,24 +426,11 @@ export const hotelQueryRouter = router({
const { lang } = ctx
const apiLang = toApiLang(lang)
metrics.hotelsAvailabilityBookingCode.counter.add(1, {
...input,
})
const bookingCodeAvailabilityResponse =
await getHotelsAvailabilityByCity(input, apiLang, ctx.serviceToken)
// If API or network failed with no response
if (!bookingCodeAvailabilityResponse) {
metrics.hotelsAvailabilityBookingCode.fail.add(1, {
...input,
error_type: "unknown",
})
return null
}
// Get regular availability of hotels which don't have availability with booking code.
const unavailableHotelIds =
bookingCodeAvailabilityResponse?.availability
const unavailableHotelIds = bookingCodeAvailabilityResponse.availability
.filter((hotel) => {
return hotel.status === "NotAvailable"
})
@@ -461,6 +443,7 @@ export const hotelQueryRouter = router({
if (!unavailableHotelIds || !unavailableHotelIds.length) {
return bookingCodeAvailabilityResponse
}
const unavailableHotelsInput = {
...input,
bookingCode: "",
@@ -472,11 +455,6 @@ export const hotelQueryRouter = router({
ctx.serviceToken
)
metrics.hotelsAvailabilityBookingCode.success.add(1, {
...input,
})
console.info("api.hotels.hotelsAvailabilityBookingCode success")
// No regular rates available due to network or API failure (no need to filter & merge).
if (!unavailableHotels) {
return bookingCodeAvailabilityResponse
@@ -537,19 +515,16 @@ export const hotelQueryRouter = router({
const language = ctx.lang
let hotelsToFetch: string[] = []
metrics.hotels.counter.add(1, {
input: JSON.stringify(input),
language,
})
console.info(
"api.hotel.hotels start",
JSON.stringify({
query: {
...input,
language,
},
})
const getHotelsByCSFilterCounter = createCounter(
"trpc.hotel.hotels",
"byCSFilter"
)
const metricsGetHotelsByCSFilter = getHotelsByCSFilterCounter.init({
input,
language,
})
metricsGetHotelsByCSFilter.start()
if (hotelsToInclude.length) {
hotelsToFetch = hotelsToInclude
@@ -571,20 +546,13 @@ export const hotelQueryRouter = router({
.find((loc) => loc.cityIdentifier === locationFilter.city)?.id
if (!cityId) {
metrics.hotels.fail.add(1, {
input: JSON.stringify(input),
language,
error_type: "not_found",
error: `CityId not found for cityIdentifier: ${locationFilter.city}`,
})
console.error(
"api.hotel.hotels not found error",
JSON.stringify({
query: { ...input, language },
error: `CityId not found for cityIdentifier: ${locationFilter.city}`,
})
metricsGetHotelsByCSFilter.dataError(
`CityId not found for cityIdentifier: ${locationFilter.city}`,
{
cityIdentifier: locationFilter.city,
}
)
return []
}
@@ -594,20 +562,13 @@ export const hotelQueryRouter = router({
})
if (!hotelIds?.length) {
metrics.hotels.fail.add(1, {
metricsGetHotelsByCSFilter.dataError(
`No hotelIds found for cityId: ${cityId}`,
{
cityId,
language,
error_type: "not_found",
error: `No hotelIds found for cityId: ${cityId}`,
})
console.error(
"api.hotel.hotels not found error",
JSON.stringify({
query: { cityId, language },
error: `No hotelIds found for cityId: ${cityId}`,
})
}
)
return []
}
@@ -623,20 +584,13 @@ export const hotelQueryRouter = router({
})
if (!hotelIds?.length) {
metrics.hotels.fail.add(1, {
metricsGetHotelsByCSFilter.dataError(
`No hotelIds found for country: ${locationFilter.country}`,
{
country: locationFilter.country,
language,
error_type: "not_found",
error: `No hotelIds found for country: ${locationFilter.country}`,
})
console.error(
"api.hotel.hotels not found error",
JSON.stringify({
query: { country: locationFilter.country, language },
error: `No hotelIds found for cityId: ${locationFilter.country}`,
})
}
)
return []
}
@@ -648,20 +602,11 @@ export const hotelQueryRouter = router({
}
if (!hotelsToFetch.length) {
metrics.hotels.fail.add(1, {
input: JSON.stringify(input),
language,
error_type: "not_found",
error: `Couldn't find any hotels for given input: ${JSON.stringify(input)}`,
})
console.error(
"api.hotel.hotels not found error",
JSON.stringify({
query: JSON.stringify(input),
error: `Couldn't find any hotels for given input: ${JSON.stringify(input)}`,
})
metricsGetHotelsByCSFilter.dataError(
`Couldn't find any hotels for given input: ${JSON.stringify(input)}`,
input
)
return []
}
const hotelPages = await getHotelPageUrls(language)
@@ -684,20 +629,7 @@ export const hotelQueryRouter = router({
})
)
metrics.hotels.success.add(1, {
input: JSON.stringify(input),
language,
})
console.info(
"api.hotels success",
JSON.stringify({
query: {
input: JSON.stringify(input),
language,
},
})
)
metricsGetHotelsByCSFilter.success()
return hotels.filter((hotel): hotel is HotelDataWithUrl => !!hotel)
}),
@@ -769,13 +701,17 @@ export const hotelQueryRouter = router({
return cacheClient.cacheOrGet(
`${apiLang}:nearbyHotels:${hotelId}`,
async () => {
metrics.nearbyHotelIds.counter.add(1, {
const nearbyHotelsCounter = createCounter(
"trpc.hotel",
"nearbyHotelIds"
)
const metricsNearbyHotels = nearbyHotelsCounter.init({
params,
hotelId,
})
console.info(
"api.hotels.nearbyHotelIds start",
JSON.stringify({ query: { hotelId, params } })
)
metricsNearbyHotels.start()
const apiResponse = await api.get(
api.endpoints.v1.Hotel.Hotels.nearbyHotels(hotelId),
{
@@ -786,55 +722,18 @@ export const hotelQueryRouter = router({
params
)
if (!apiResponse.ok) {
const text = await apiResponse.text()
metrics.nearbyHotelIds.fail.add(1, {
hotelId,
error_type: "http_error",
error: JSON.stringify({
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
}),
})
console.error(
"api.hotels.nearbyHotelIds error",
JSON.stringify({
query: { hotelId, params },
error: {
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
},
})
)
await metricsNearbyHotels.httpError(apiResponse)
return null
}
const apiJson = await apiResponse.json()
const validateHotelData = getNearbyHotelIdsSchema.safeParse(apiJson)
if (!validateHotelData.success) {
metrics.nearbyHotelIds.fail.add(1, {
hotelId,
error_type: "validation_error",
error: JSON.stringify(validateHotelData.error),
})
console.error(
"api.hotels.nearbyHotelIds validation error",
JSON.stringify({
query: { hotelId, params },
error: validateHotelData.error,
})
)
metricsNearbyHotels.validationError(validateHotelData.error)
throw badRequestError()
}
metrics.nearbyHotelIds.success.add(1, {
hotelId,
})
console.info(
"api.hotels.nearbyHotelIds success",
JSON.stringify({
query: { hotelId, params },
})
)
metricsNearbyHotels.success()
return validateHotelData.data.map((id: string) => parseInt(id, 10))
},
@@ -884,16 +783,17 @@ export const hotelQueryRouter = router({
urls: publicProcedure
.input(getLocationsUrlsInput)
.query(async ({ input }) => {
const procedureName = "hotels.locations.urls"
const { lang } = input
locationsUrlsCounter.add(1, { lang })
console.info(
`${procedureName}: start`,
JSON.stringify({ query: { lang } })
const locationsUrlsCounter = createCounter(
"trpc.hotel.locations",
"urls"
)
const metricsLocationsUrls = locationsUrlsCounter.init({
lang,
})
metricsLocationsUrls.start()
const [hotelPageUrlsResult, cityPageUrlsResult] =
await Promise.allSettled([
@@ -905,32 +805,15 @@ export const hotelQueryRouter = router({
hotelPageUrlsResult.status === "rejected" ||
cityPageUrlsResult.status === "rejected"
) {
locationsUrlsFailCounter.add(1, {
lang,
error_type: "no_data",
response: JSON.stringify({
metricsLocationsUrls.dataError(`Failed to get data for page URLs`, {
hotelPageUrlsResult,
cityPageUrlsResult,
}),
})
console.error(`${procedureName}: no data`, {
variables: { lang },
error_type: "no_data",
response: {
hotelPageUrlsResult,
cityPageUrlsResult,
},
})
return null
}
locationsUrlsSuccessCounter.add(1, { lang })
console.info(`${procedureName}: success`, {
variables: { lang },
})
metricsLocationsUrls.success()
return {
hotels: hotelPageUrlsResult.value,
@@ -992,12 +875,12 @@ export const hotelQueryRouter = router({
hotelId,
language,
}
const metricsData = { ...params, hotelId: input.hotelId }
metrics.meetingRooms.counter.add(1, metricsData)
console.info(
"api.hotels.meetingRooms start",
JSON.stringify({ query: { hotelId, params } })
)
const meetingRoomsCounter = createCounter("trpc.hotel", "meetingRooms")
const metricsMeetingRooms = meetingRoomsCounter.init({
params,
})
metricsMeetingRooms.start()
const cacheClient = await getCacheClient()
return cacheClient.cacheOrGet(
@@ -1014,28 +897,7 @@ export const hotelQueryRouter = router({
)
if (!apiResponse.ok) {
const text = await apiResponse.text()
metrics.meetingRooms.fail.add(1, {
...metricsData,
error_type: "http_error",
error: JSON.stringify({
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
}),
})
console.error(
"api.hotels.meetingRooms error",
JSON.stringify({
query: { params },
error: {
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
},
})
)
await metricsMeetingRooms.httpError(apiResponse)
throw new Error("Failed to fetch meeting rooms")
}
@@ -1043,22 +905,10 @@ export const hotelQueryRouter = router({
const validatedMeetingRooms = meetingRoomsSchema.safeParse(apiJson)
if (!validatedMeetingRooms.success) {
console.error(
"api.hotels.meetingRooms validation error",
JSON.stringify({
query: { params },
error: validatedMeetingRooms.error,
})
)
metricsMeetingRooms.validationError(validatedMeetingRooms.error)
throw badRequestError()
}
metrics.meetingRooms.success.add(1, {
hotelId,
})
console.info(
"api.hotels.meetingRooms success",
JSON.stringify({ query: { params } })
)
metricsMeetingRooms.success()
return validatedMeetingRooms.data.data
},
@@ -1074,12 +924,16 @@ export const hotelQueryRouter = router({
hotelId,
language,
}
const metricsData = { ...params, hotelId: input.hotelId }
metrics.additionalData.counter.add(1, metricsData)
console.info(
"api.hotels.additionalData start",
JSON.stringify({ query: { hotelId, params } })
const additionalDataCounter = createCounter(
"trpc.hotel",
"additionalData"
)
const metricsAdditionalData = additionalDataCounter.init({
params,
})
metricsAdditionalData.start()
const cacheClient = await getCacheClient()
return cacheClient.cacheOrGet(
@@ -1096,28 +950,7 @@ export const hotelQueryRouter = router({
)
if (!apiResponse.ok) {
const text = await apiResponse.text()
metrics.additionalData.fail.add(1, {
...metricsData,
error_type: "http_error",
error: JSON.stringify({
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
}),
})
console.error(
"api.hotels.additionalData error",
JSON.stringify({
query: { params },
error: {
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
},
})
)
await metricsAdditionalData.httpError(apiResponse)
throw new Error("Unable to fetch additional data for hotel")
}
@@ -1126,22 +959,11 @@ export const hotelQueryRouter = router({
additionalDataSchema.safeParse(apiJson)
if (!validatedAdditionalData.success) {
console.error(
"api.hotels.additionalData validation error",
JSON.stringify({
query: { params },
error: validatedAdditionalData.error,
})
)
metricsAdditionalData.validationError(validatedAdditionalData.error)
throw badRequestError()
}
metrics.additionalData.success.add(1, {
hotelId,
})
console.info(
"api.hotels.additionalData success",
JSON.stringify({ query: { params } })
)
metricsAdditionalData.success()
return validatedAdditionalData.data
},
@@ -1167,12 +989,16 @@ export const hotelQueryRouter = router({
language: apiLang,
}
const metricsData = { ...params, hotelId: input.hotelId }
metrics.breakfastPackage.counter.add(1, metricsData)
console.info(
"api.package.breakfast start",
JSON.stringify({ query: metricsData })
const breakfastCounter = createCounter(
"trpc.hotel.packages",
"breakfast"
)
const metricsBreakfast = breakfastCounter.init({
params,
hotelId: input.hotelId,
})
metricsBreakfast.start()
const cacheClient = await getCacheClient()
const breakfastPackages = await cacheClient.cacheOrGet(
@@ -1189,57 +1015,17 @@ export const hotelQueryRouter = router({
)
if (!apiResponse.ok) {
const text = await apiResponse.text()
metrics.breakfastPackage.fail.add(1, {
...metricsData,
error_type: "http_error",
error: JSON.stringify({
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
}),
})
console.error(
"api.package.breakfast error",
JSON.stringify({
query: metricsData,
error: {
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
},
})
)
await metricsBreakfast.httpError(apiResponse)
throw new Error("Unable to fetch breakfast packages")
}
const apiJson = await apiResponse.json()
const breakfastPackages = breakfastPackagesSchema.safeParse(apiJson)
if (!breakfastPackages.success) {
metrics.breakfastPackage.fail.add(1, {
...metricsData,
error_type: "validation_error",
error: JSON.stringify(breakfastPackages.error),
})
console.error(
"api.package.breakfast validation error",
JSON.stringify({
query: metricsData,
error: breakfastPackages.error,
})
)
metricsBreakfast.validationError(breakfastPackages.error)
throw new Error("Unable to parse breakfast packages")
}
metrics.breakfastPackage.success.add(1, metricsData)
console.info(
"api.package.breakfast success",
JSON.stringify({
query: metricsData,
})
)
return breakfastPackages.data
},
"1h"
@@ -1265,6 +1051,8 @@ export const hotelQueryRouter = router({
// }
// }
metricsBreakfast.success()
return breakfastPackages.filter(
(pkg) => pkg.code !== BreakfastPackageEnum.FREE_MEMBER_BREAKFAST
)
@@ -1281,16 +1069,21 @@ export const hotelQueryRouter = router({
language: apiLang,
}
const ancillaryCounter = createCounter(
"trpc.hotel.packages",
"ancillary"
)
const metricsAncillary = ancillaryCounter.init({
params,
hotelId: input.hotelId,
})
metricsAncillary.start()
const cacheClient = await getCacheClient()
return await cacheClient.cacheOrGet(
const result = await cacheClient.cacheOrGet(
`${apiLang}:hotel:${input.hotelId}:ancillaries:startDate:${params.StartDate}:endDate:${params.EndDate}`,
async () => {
const metricsData = { ...params, hotelId: input.hotelId }
metrics.ancillaryPackage.counter.add(1, metricsData)
console.info(
"api.package.ancillary start",
JSON.stringify({ query: metricsData })
)
const apiResponse = await api.get(
api.endpoints.v1.Package.Ancillary.hotel(input.hotelId),
{
@@ -1302,59 +1095,25 @@ export const hotelQueryRouter = router({
)
if (!apiResponse.ok) {
const text = await apiResponse.text()
metrics.ancillaryPackage.fail.add(1, {
...metricsData,
error_type: "http_error",
error: JSON.stringify({
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
}),
})
console.error(
"api.package.ancillary start error",
JSON.stringify({
query: metricsData,
error: {
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
},
})
)
await metricsAncillary.httpError(apiResponse)
return null
}
const apiJson = await apiResponse.json()
const ancillaryPackages = ancillaryPackagesSchema.safeParse(apiJson)
if (!ancillaryPackages.success) {
metrics.ancillaryPackage.fail.add(1, {
...metricsData,
error_type: "validation_error",
error: JSON.stringify(ancillaryPackages.error),
})
console.error(
"api.package.ancillary validation error",
JSON.stringify({
query: metricsData,
error: ancillaryPackages.error,
})
)
metricsAncillary.validationError(ancillaryPackages.error)
return null
}
metrics.ancillaryPackage.success.add(1, metricsData)
console.info(
"api.package.ancillary success",
JSON.stringify({
query: metricsData,
})
)
return ancillaryPackages.data
},
"1h"
)
metricsAncillary.success()
return result
}),
}),
})

View File

@@ -1,114 +0,0 @@
import { metrics } from "@opentelemetry/api"
const meter = metrics.getMeter("trpc.hotels")
export const getHotelCounter = meter.createCounter("trpc.hotel.get")
export const getHotelSuccessCounter = meter.createCounter(
"trpc.hotel.get-success"
)
export const getHotelFailCounter = meter.createCounter("trpc.hotel.get-fail")
export const getPackagesCounter = meter.createCounter("trpc.hotel.packages.get")
export const getPackagesSuccessCounter = meter.createCounter(
"trpc.hotel.packages.get-success"
)
export const getPackagesFailCounter = meter.createCounter(
"trpc.hotel.packages.get-fail"
)
export const hotelsAvailabilityCounter = meter.createCounter(
"trpc.hotel.availability.hotels"
)
export const hotelsAvailabilitySuccessCounter = meter.createCounter(
"trpc.hotel.availability.hotels-success"
)
export const hotelsAvailabilityFailCounter = meter.createCounter(
"trpc.hotel.availability.hotels-fail"
)
export const hotelsByHotelIdAvailabilityCounter = meter.createCounter(
"trpc.hotel.availability.hotels-by-hotel-id"
)
export const hotelsByHotelIdAvailabilitySuccessCounter = meter.createCounter(
"trpc.hotel.availability.hotels-by-hotel-id-success"
)
export const hotelsByHotelIdAvailabilityFailCounter = meter.createCounter(
"trpc.hotel.availability.hotels-by-hotel-id-fail"
)
export const selectedRoomAvailabilityCounter = meter.createCounter(
"trpc.hotel.availability.room"
)
export const selectedRoomAvailabilitySuccessCounter = meter.createCounter(
"trpc.hotel.availability.room-success"
)
export const selectedRoomAvailabilityFailCounter = meter.createCounter(
"trpc.hotel.availability.room-fail"
)
export const breakfastPackagesCounter = meter.createCounter(
"trpc.package.breakfast"
)
export const breakfastPackagesSuccessCounter = meter.createCounter(
"trpc.package.breakfast-success"
)
export const breakfastPackagesFailCounter = meter.createCounter(
"trpc.package.breakfast-fail"
)
export const getHotelsCounter = meter.createCounter("trpc.hotel.hotels.get")
export const getHotelsSuccessCounter = meter.createCounter(
"trpc.hotel.hotels.get-success"
)
export const getHotelsFailCounter = meter.createCounter(
"trpc.hotel.hotels.get-fail"
)
export const getHotelIdsCounter = meter.createCounter(
"trpc.hotel.hotel-ids.get"
)
export const getHotelIdsSuccessCounter = meter.createCounter(
"trpc.hotel.hotel-ids.get-success"
)
export const getHotelIdsFailCounter = meter.createCounter(
"trpc.hotel.hotel-ids.get-fail"
)
export const nearbyHotelIdsCounter = meter.createCounter(
"trpc.hotel.nearby-hotel-ids.get"
)
export const nearbyHotelIdsSuccessCounter = meter.createCounter(
"trpc.hotel.nearby-hotel-ids.get-success"
)
export const nearbyHotelIdsFailCounter = meter.createCounter(
"trpc.hotel.nearby-hotel-ids.get-fail"
)
export const meetingRoomsCounter = meter.createCounter(
"trpc.hotels.meetingRooms"
)
export const meetingRoomsSuccessCounter = meter.createCounter(
"trpc.hotels.meetingRooms-success"
)
export const meetingRoomsFailCounter = meter.createCounter(
"trpc.hotels.meetingRooms-fail"
)
export const additionalDataCounter = meter.createCounter(
"trpc.hotels.additionalData"
)
export const additionalDataSuccessCounter = meter.createCounter(
"trpc.hotels.additionalData-success"
)
export const additionalDataFailCounter = meter.createCounter(
"trpc.hotels.additionalData-fail"
)
export const locationsUrlsCounter = meter.createCounter(
"trpc.hotels.locations.urls"
)
export const locationsUrlsSuccessCounter = meter.createCounter(
"trpc.hotels.locations.urls-success"
)
export const locationsUrlsFailCounter = meter.createCounter(
"trpc.hotels.locations.urls-fail"
)

View File

@@ -6,6 +6,7 @@ import { Lang } from "@/constants/languages"
import { env } from "@/env/server"
import * as api from "@/lib/api"
import { badRequestError } from "@/server/errors/trpc"
import { createCounter } from "@/server/telemetry"
import { toApiLang } from "@/server/utils"
import { generateChildrenString } from "@/components/HotelReservation/utils"
@@ -13,8 +14,7 @@ import { getCacheClient } from "@/services/dataCache"
import { cache } from "@/utils/cache"
import { getHotelPageUrls } from "../contentstack/hotelPage/utils"
import { type RoomFeaturesInput, roomPackagesInputSchema } from "./input"
import { metrics } from "./metrics"
import { type RoomFeaturesInput } from "./input"
import {
type Cities,
citiesByCountrySchema,
@@ -38,12 +38,12 @@ import type {
DestinationPagesHotelData,
Room as RoomCategory,
} from "@/types/hotel"
import type { PackagesInput } from "@/types/requests/packages"
import type { PackagesOutput } from "@/types/requests/packages"
import type {
HotelsAvailabilityInputSchema,
HotelsByHotelIdsAvailabilityInputSchema,
RoomsAvailabilityInputRoom,
RoomsAvailabilityInputSchema,
RoomsAvailabilityOutputSchema,
} from "@/types/trpc/routers/hotel/availability"
import type { HotelInput } from "@/types/trpc/routers/hotel/hotel"
import type {
@@ -332,18 +332,23 @@ export async function getHotelIdsByCityId({
cityId: string
serviceToken: string
}) {
const getHotelIdsByCityIdCounter = createCounter(
"hotel",
"getHotelIdsByCityId"
)
const metricsGetHotelIdsByCityId = getHotelIdsByCityIdCounter.init({
cityId,
})
metricsGetHotelIdsByCityId.start()
const cacheClient = await getCacheClient()
return await cacheClient.cacheOrGet(
const result = await cacheClient.cacheOrGet(
`${cityId}:hotelsByCityId`,
async () => {
const searchParams = new URLSearchParams({
city: cityId,
})
metrics.hotelIds.counter.add(1, { params: searchParams.toString() })
console.info(
"api.hotel.hotel-ids start",
JSON.stringify({ params: searchParams.toString() })
)
const apiResponse = await api.get(
api.endpoints.v1.Hotel.hotels,
@@ -356,59 +361,25 @@ export async function getHotelIdsByCityId({
)
if (!apiResponse.ok) {
const responseMessage = await apiResponse.text()
metrics.hotelIds.fail.add(1, {
params: searchParams.toString(),
error_type: "http_error",
error: responseMessage,
})
console.error(
"api.hotel.hotel-ids fetch error",
JSON.stringify({
params: searchParams.toString(),
error: {
status: apiResponse.status,
statusText: apiResponse.statusText,
text: responseMessage,
},
})
)
await metricsGetHotelIdsByCityId.httpError(apiResponse)
throw new Error("Unable to fetch hotelIds by cityId")
}
const apiJson = await apiResponse.json()
const validatedHotelIds = getHotelIdsSchema.safeParse(apiJson)
if (!validatedHotelIds.success) {
metrics.hotelIds.fail.add(1, {
params: searchParams.toString(),
error_type: "validation_error",
error: JSON.stringify(validatedHotelIds.error),
})
console.error(
"api.hotel.hotel-ids validation error",
JSON.stringify({
params: searchParams.toString(),
error: validatedHotelIds.error,
})
)
metricsGetHotelIdsByCityId.validationError(validatedHotelIds.error)
throw new Error("Unable to parse data for hotelIds by cityId")
}
metrics.hotelIds.success.add(1, { cityId })
console.info(
"api.hotel.hotel-ids success",
JSON.stringify({
params: searchParams.toString(),
response: validatedHotelIds.data,
})
)
return validatedHotelIds.data
},
env.CACHE_TIME_HOTELS
)
metricsGetHotelIdsByCityId.success()
return result
}
export async function getHotelIdsByCountry({
@@ -418,17 +389,22 @@ export async function getHotelIdsByCountry({
country: string
serviceToken: string
}) {
const cacheClient = await getCacheClient()
return await cacheClient.cacheOrGet(
`${country}:hotelsByCountry`,
async () => {
metrics.hotelIds.counter.add(1, { country })
console.info(
"api.hotel.hotel-ids start",
JSON.stringify({ query: { country } })
const getHotelIdsByCountryCounter = createCounter(
"hotel",
"getHotelIdsByCountry"
)
const metricsGetHotelIdsByCountry = getHotelIdsByCountryCounter.init({
country,
})
metricsGetHotelIdsByCountry.start()
const cacheClient = await getCacheClient()
const result = await cacheClient.cacheOrGet(
`${country}:hotelsByCountry`,
async () => {
const hotelIdsParams = new URLSearchParams({
country,
})
@@ -444,55 +420,25 @@ export async function getHotelIdsByCountry({
)
if (!apiResponse.ok) {
const responseMessage = await apiResponse.text()
metrics.hotelIds.fail.add(1, {
country,
error_type: "http_error",
error: responseMessage,
})
console.error(
"api.hotel.hotel-ids fetch error",
JSON.stringify({
query: { country },
error: {
status: apiResponse.status,
statusText: apiResponse.statusText,
text: responseMessage,
},
})
)
await metricsGetHotelIdsByCountry.httpError(apiResponse)
throw new Error("Unable to fetch hotelIds by country")
}
const apiJson = await apiResponse.json()
const validatedHotelIds = getHotelIdsSchema.safeParse(apiJson)
if (!validatedHotelIds.success) {
metrics.hotelIds.fail.add(1, {
country,
error_type: "validation_error",
error: JSON.stringify(validatedHotelIds.error),
})
console.error(
"api.hotel.hotel-ids validation error",
JSON.stringify({
query: { country },
error: validatedHotelIds.error,
})
)
metricsGetHotelIdsByCountry.validationError(validatedHotelIds.error)
throw new Error("Unable to parse hotelIds by country")
}
metrics.hotelIds.success.add(1, { country })
console.info(
"api.hotel.hotel-ids success",
JSON.stringify({ query: { country } })
)
return validatedHotelIds.data
},
env.CACHE_TIME_HOTELS
)
metricsGetHotelIdsByCountry.success()
return result
}
export async function getHotelIdsByCityIdentifier(
@@ -631,11 +577,22 @@ function findProduct(product: Products, rateDefinition: RateDefinition) {
export const getHotel = cache(
async (input: HotelInput, serviceToken: string) => {
const callable = async function (
hotelId: HotelInput["hotelId"],
language: HotelInput["language"],
isCardOnlyPayment?: HotelInput["isCardOnlyPayment"]
) {
const { hotelId, language, isCardOnlyPayment } = input
const getHotelCounter = createCounter("hotel", "getHotel")
const metricsGetHotel = getHotelCounter.init({
hotelId,
language,
isCardOnlyPayment,
})
metricsGetHotel.start()
const cacheClient = await getCacheClient()
const result = await cacheClient.cacheOrGet(
`${input.language}:hotel:${input.hotelId}:${!!input.isCardOnlyPayment}`,
async () => {
/**
* Since API expects the params appended and not just
* a comma separated string we need to initialize the
@@ -650,14 +607,6 @@ export const getHotel = cache(
["include", "RoomCategories"],
["language", toApiLang(language)],
])
metrics.hotel.counter.add(1, {
hotelId,
language,
})
console.info(
"api.hotels.hotelData start",
JSON.stringify({ query: { hotelId, params: params.toString() } })
)
const apiResponse = await api.get(
api.endpoints.v1.Hotel.Hotels.hotel(hotelId),
@@ -670,28 +619,7 @@ export const getHotel = cache(
)
if (!apiResponse.ok) {
const text = await apiResponse.text()
metrics.hotel.fail.add(1, {
hotelId,
language,
error_type: "http_error",
error: JSON.stringify({
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
}),
})
console.error(
"api.hotels.hotelData error",
JSON.stringify({
query: { hotelId, params: params.toString() },
error: {
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
},
})
)
await metricsGetHotel.httpError(apiResponse)
return null
}
@@ -699,33 +627,10 @@ export const getHotel = cache(
const validateHotelData = hotelSchema.safeParse(apiJson)
if (!validateHotelData.success) {
metrics.hotel.fail.add(1, {
hotelId,
language,
error_type: "validation_error",
error: JSON.stringify(validateHotelData.error),
})
console.error(
"api.hotels.hotelData validation error",
JSON.stringify({
query: { hotelId, params: params.toString() },
error: validateHotelData.error,
})
)
metricsGetHotel.validationError(validateHotelData.error)
throw badRequestError()
}
metrics.hotel.success.add(1, {
hotelId,
language,
})
console.info(
"api.hotels.hotelData success",
JSON.stringify({
query: { hotelId, params: params.toString() },
})
)
const hotelData = validateHotelData.data
if (isCardOnlyPayment) {
@@ -743,16 +648,13 @@ export const getHotel = cache(
}
return hotelData
}
const cacheClient = await getCacheClient()
return await cacheClient.cacheOrGet(
`${input.language}:hotel:${input.hotelId}:${!!input.isCardOnlyPayment}`,
async () => {
return callable(input.hotelId, input.language, input.isCardOnlyPayment)
},
env.CACHE_TIME_HOTELS
)
metricsGetHotel.success()
return result
}
)
@@ -781,7 +683,14 @@ export async function getHotelsAvailabilityByCity(
...(redemption ? { isRedemption: "true" } : {}),
language: apiLang,
}
metrics.hotelsAvailability.counter.add(1, {
const getHotelsAvailabilityByCityCounter = createCounter(
"hotel",
"getHotelsAvailabilityByCity"
)
const metricsGetHotelsAvailabilityByCity =
getHotelsAvailabilityByCityCounter.init({
apiLang,
cityId,
roomStayStartDate,
roomStayEndDate,
@@ -790,10 +699,9 @@ export async function getHotelsAvailabilityByCity(
bookingCode,
redemption,
})
console.info(
"api.hotels.hotelsAvailability start",
JSON.stringify({ query: { cityId, params } })
)
metricsGetHotelsAvailabilityByCity.start()
const apiResponse = await api.get(
api.endpoints.v1.Availability.city(cityId),
{
@@ -804,74 +712,19 @@ export async function getHotelsAvailabilityByCity(
params
)
if (!apiResponse.ok) {
const text = await apiResponse.text()
metrics.hotelsAvailability.fail.add(1, {
cityId,
roomStayStartDate,
roomStayEndDate,
adults,
children,
bookingCode,
error_type: "http_error",
error: JSON.stringify({
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
}),
})
console.error(
"api.hotels.hotelsAvailability error",
JSON.stringify({
query: { cityId, params },
error: {
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
},
})
)
await metricsGetHotelsAvailabilityByCity.httpError(apiResponse)
throw new Error("Failed to fetch hotels availability by city")
}
const apiJson = await apiResponse.json()
const validateAvailabilityData = hotelsAvailabilitySchema.safeParse(apiJson)
if (!validateAvailabilityData.success) {
metrics.hotelsAvailability.fail.add(1, {
cityId,
roomStayStartDate,
roomStayEndDate,
adults,
children,
bookingCode,
redemption,
error_type: "validation_error",
error: JSON.stringify(validateAvailabilityData.error),
})
console.error(
"api.hotels.hotelsAvailability validation error",
JSON.stringify({
query: { cityId, params },
error: validateAvailabilityData.error,
})
metricsGetHotelsAvailabilityByCity.validationError(
validateAvailabilityData.error
)
throw badRequestError()
}
metrics.hotelsAvailability.success.add(1, {
cityId,
roomStayStartDate,
roomStayEndDate,
adults,
children,
bookingCode,
redemption,
})
console.info(
"api.hotels.hotelsAvailability success",
JSON.stringify({
query: { cityId, params: params },
})
)
if (redemption) {
validateAvailabilityData.data.data.forEach((data) => {
data.attributes.productType?.redemptions?.forEach((r) => {
@@ -880,11 +733,15 @@ export async function getHotelsAvailabilityByCity(
})
}
return {
const result = {
availability: validateAvailabilityData.data.data.flatMap(
(hotels) => hotels.attributes
),
}
metricsGetHotelsAvailabilityByCity.success()
return result
}
export async function getHotelsAvailabilityByHotelIds(
@@ -910,8 +767,26 @@ export async function getHotelsAvailabilityByHotelIds(
["language", apiLang],
])
const getHotelsAvailabilityByHotelIdsCounter = createCounter(
"hotel",
"getHotelsAvailabilityByHotelIds"
)
const metricsGetHotelsAvailabilityByHotelIds =
getHotelsAvailabilityByHotelIdsCounter.init({
apiLang,
hotelIds,
roomStayStartDate,
roomStayEndDate,
adults,
children,
bookingCode,
})
metricsGetHotelsAvailabilityByHotelIds.start()
const cacheClient = await getCacheClient()
return cacheClient.cacheOrGet(
const result = cacheClient.cacheOrGet(
`${apiLang}:hotels:availability:${hotelIds.join(",")}:${roomStayStartDate}:${roomStayEndDate}:${adults}:${children}:${bookingCode}`,
async () => {
/**
@@ -924,18 +799,7 @@ export async function getHotelsAvailabilityByHotelIds(
hotelIds.forEach((hotelId) =>
params.append("hotelIds", hotelId.toString())
)
metrics.hotelsByHotelIdAvailability.counter.add(1, {
hotelIds,
roomStayStartDate,
roomStayEndDate,
adults,
children,
bookingCode,
})
console.info(
"api.hotels.hotelsByHotelIdAvailability start",
JSON.stringify({ query: { params } })
)
const apiResponse = await api.get(
api.endpoints.v1.Availability.hotels(),
{
@@ -946,72 +810,20 @@ export async function getHotelsAvailabilityByHotelIds(
params
)
if (!apiResponse.ok) {
const text = await apiResponse.text()
metrics.hotelsByHotelIdAvailability.fail.add(1, {
hotelIds,
roomStayStartDate,
roomStayEndDate,
adults,
children,
bookingCode,
error_type: "http_error",
error: JSON.stringify({
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
}),
})
console.error(
"api.hotels.hotelsByHotelIdAvailability error",
JSON.stringify({
query: { params },
error: {
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
},
})
)
await metricsGetHotelsAvailabilityByHotelIds.httpError(apiResponse)
throw new Error("Failed to fetch hotels availability by hotelIds")
}
const apiJson = await apiResponse.json()
const validateAvailabilityData =
hotelsAvailabilitySchema.safeParse(apiJson)
if (!validateAvailabilityData.success) {
metrics.hotelsByHotelIdAvailability.fail.add(1, {
hotelIds,
roomStayStartDate,
roomStayEndDate,
adults,
children,
bookingCode,
error_type: "validation_error",
error: JSON.stringify(validateAvailabilityData.error),
})
console.error(
"api.hotels.hotelsByHotelIdAvailability validation error",
JSON.stringify({
query: { params },
error: validateAvailabilityData.error,
})
metricsGetHotelsAvailabilityByHotelIds.validationError(
validateAvailabilityData.error
)
throw badRequestError()
}
metrics.hotelsByHotelIdAvailability.success.add(1, {
hotelIds,
roomStayStartDate,
roomStayEndDate,
adults,
children,
bookingCode,
})
console.info(
"api.hotels.hotelsByHotelIdAvailability success",
JSON.stringify({
query: { params },
})
)
return {
availability: validateAvailabilityData.data.data.flatMap(
(hotels) => hotels.attributes
@@ -1020,6 +832,10 @@ export async function getHotelsAvailabilityByHotelIds(
},
env.CACHE_TIME_CITY_SEARCH
)
metricsGetHotelsAvailabilityByHotelIds.success()
return result
}
async function getRoomFeaturesInventory(
@@ -1034,10 +850,7 @@ async function getRoomFeaturesInventory(
roomFeatureCodes,
startDate,
} = input
const cacheClient = await getCacheClient()
return cacheClient.cacheOrGet(
stringify(input),
async function () {
const params = {
adults,
hotelId,
@@ -1049,8 +862,20 @@ async function getRoomFeaturesInventory(
}),
}
metrics.roomFeatures.counter.add(1, params)
const getRoomFeaturesInventoryCounter = createCounter(
"hotel",
"getRoomFeaturesInventory"
)
const metricsGetRoomFeaturesInventory =
getRoomFeaturesInventoryCounter.init(params)
metricsGetRoomFeaturesInventory.start()
const cacheClient = await getCacheClient()
const result = cacheClient.cacheOrGet(
stringify(input),
async function () {
const apiResponse = await api.get(
api.endpoints.v1.Availability.roomFeatures(hotelId),
{
@@ -1062,67 +887,45 @@ async function getRoomFeaturesInventory(
)
if (!apiResponse.ok) {
const text = apiResponse.text()
console.error(
"api.availability.roomfeature error",
JSON.stringify({
query: { hotelId, params },
error: {
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
},
})
)
metrics.roomFeatures.fail.add(1, params)
await metricsGetRoomFeaturesInventory.httpError(apiResponse)
return null
}
const data = await apiResponse.json()
const validatedRoomFeaturesData = roomFeaturesSchema.safeParse(data)
if (!validatedRoomFeaturesData.success) {
console.error(
"api.availability.roomfeature error",
JSON.stringify({
query: { hotelId, params },
error: validatedRoomFeaturesData.error,
})
metricsGetRoomFeaturesInventory.validationError(
validatedRoomFeaturesData.error
)
return null
}
metrics.roomFeatures.success.add(1, params)
return validatedRoomFeaturesData.data
},
"5m"
)
metricsGetRoomFeaturesInventory.success()
return result
}
export async function getPackages(
rawInput: PackagesInput,
serviceToken: string
) {
const parsedInput = roomPackagesInputSchema.safeParse(rawInput)
if (!parsedInput.success) {
console.info(`Failed to parse input for Get Packages`)
console.error(parsedInput.error)
return null
}
const input = parsedInput.data
export async function getPackages(input: PackagesOutput, serviceToken: string) {
const { adults, children, endDate, hotelId, lang, packageCodes, startDate } =
input
const getPackagesCounter = createCounter("hotel", "getPackages")
const metricsGetPackages = getPackagesCounter.init({
input,
})
metricsGetPackages.start()
const cacheClient = await getCacheClient()
return cacheClient.cacheOrGet(
const result = cacheClient.cacheOrGet(
stringify(input),
async function () {
const {
adults,
children,
endDate,
hotelId,
lang,
packageCodes,
startDate,
} = input
const apiLang = toApiLang(lang)
const searchParams = new URLSearchParams({
@@ -1137,16 +940,6 @@ export async function getPackages(
searchParams.append("packageCodes", code)
})
const params = searchParams.toString()
metrics.packages.counter.add(1, {
hotelId,
})
console.info(
"api.hotels.packages start",
JSON.stringify({ query: { hotelId, params } })
)
const apiResponse = await api.get(
api.endpoints.v1.Package.Packages.hotel(hotelId),
{
@@ -1158,56 +951,29 @@ export async function getPackages(
)
if (!apiResponse.ok) {
metrics.packages.fail.add(1, {
hotelId,
error_type: "http_error",
error: JSON.stringify({
status: apiResponse.status,
statusText: apiResponse.statusText,
}),
})
console.error(
"api.hotels.packages error",
JSON.stringify({ query: { hotelId, params } })
)
await metricsGetPackages.httpError(apiResponse)
return null
}
const apiJson = await apiResponse.json()
const validatedPackagesData = packagesSchema.safeParse(apiJson)
if (!validatedPackagesData.success) {
metrics.packages.fail.add(1, {
hotelId,
error_type: "validation_error",
error: JSON.stringify(validatedPackagesData.error),
})
console.error(
"api.hotels.packages validation error",
JSON.stringify({
query: { hotelId, params },
error: validatedPackagesData.error,
})
)
metricsGetPackages.validationError(validatedPackagesData.error)
return null
}
metrics.packages.success.add(1, {
hotelId,
})
console.info(
"api.hotels.packages success",
JSON.stringify({ query: { hotelId, params: params } })
)
return validatedPackagesData.data
},
"3h"
)
metricsGetPackages.success()
return result
}
export async function getRoomsAvailability(
input: RoomsAvailabilityInputSchema,
input: RoomsAvailabilityOutputSchema,
token: string,
serviceToken: string,
userPoints: number | undefined
@@ -1219,27 +985,18 @@ export async function getRoomsAvailability(
const redemption = searchType === REDEMPTION
const apiLang = toApiLang(lang)
const kids = rooms
.map((r) => r.childrenInRoom)
.filter(Boolean)
.map((kid) => JSON.stringify(kid))
const metricsData = {
adultsCount: rooms.map((r) => r.adults),
bookingCode,
childArray: kids.length ? kids : undefined,
hotelId,
roomStayEndDate: toDate,
roomStayStartDate: fromDate,
}
metrics.roomsAvailability.counter.add(1, metricsData)
console.info(
"api.hotels.roomsAvailability start",
JSON.stringify({ query: { hotelId, params: metricsData } })
const getRoomsAvailabilityCounter = createCounter(
"hotel",
"getRoomsAvailability"
)
const metricsGetRoomsAvailability = getRoomsAvailabilityCounter.init({
input,
redemption,
})
metricsGetRoomsAvailability.start()
const apiLang = toApiLang(lang)
const baseCacheKey = {
bookingCode,
@@ -1257,7 +1014,7 @@ export async function getRoomsAvailability(
...baseCacheKey,
room,
}
return cacheClient.cacheOrGet(
const result = cacheClient.cacheOrGet(
stringify(cacheKey),
async function () {
{
@@ -1287,9 +1044,8 @@ export async function getRoomsAvailability(
)
if (!apiResponse.ok) {
await metricsGetRoomsAvailability.httpError(apiResponse)
const text = await apiResponse.text()
metrics.roomsAvailability.fail.add(1, metricsData)
console.error("Failed API call", { params, text })
return { error: "http_error", details: text }
}
@@ -1297,11 +1053,10 @@ export async function getRoomsAvailability(
const validateAvailabilityData =
roomsAvailabilitySchema.safeParse(apiJson)
if (!validateAvailabilityData.success) {
console.error("Validation error", {
params,
error: validateAvailabilityData.error,
})
metrics.roomsAvailability.fail.add(1, metricsData)
metricsGetRoomsAvailability.validationError(
validateAvailabilityData.error
)
return {
error: "validation_error",
details: validateAvailabilityData.error,
@@ -1323,7 +1078,7 @@ export async function getRoomsAvailability(
const roomFeatures = await getPackages(
{
adults: room.adults,
children: room.childrenInRoom?.length,
children: room.childrenInRoom?.length || 0,
endDate: input.booking.toDate,
hotelId: input.booking.hotelId,
lang,
@@ -1387,9 +1142,10 @@ export async function getRoomsAvailability(
},
"1m"
)
return result
})
)
metrics.roomsAvailability.success.add(1, metricsData)
const data = availabilityResponses.map((availability) => {
if (availability.status === "fulfilled") {
@@ -1401,6 +1157,8 @@ export async function getRoomsAvailability(
}
})
metricsGetRoomsAvailability.success()
return data
}

View File

@@ -2,11 +2,11 @@ import { TRPCError } from "@trpc/server"
import { z } from "zod"
import { Lang } from "@/constants/languages"
import { getVerifiedUser } from "@/server/routers/user/utils"
import { safeProtectedProcedure } from "@/server/trpc"
import { isValidSession } from "@/utils/session"
import { getVerifiedUser } from "../../user/query"
import { getPrimaryLinks } from "./getPrimaryLinks"
import { getSecondaryLinks } from "./getSecondaryLinks"

View File

@@ -1,13 +1,9 @@
import { createCounter } from "@/server/telemetry"
import { publicProcedure, router } from "@/server/trpc"
import { getCacheClient } from "@/services/dataCache"
import { jobylonFeedSchema } from "./output"
import {
getJobylonFeedCounter,
getJobylonFeedFailCounter,
getJobylonFeedSuccessCounter,
} from "./telemetry"
export const TWENTYFOUR_HOURS = 60 * 60 * 24
@@ -19,20 +15,19 @@ const feedUrl =
export const jobylonQueryRouter = router({
feed: router({
get: publicProcedure.query(async function () {
const jobylonFeedGetCounter = createCounter("trpc.jobylon.feed", "get")
const metricsJobylonFeedGet = jobylonFeedGetCounter.init()
metricsJobylonFeedGet.start()
const url = new URL(feedUrl)
url.search = new URLSearchParams({
format: "json",
}).toString()
const urlString = url.toString()
getJobylonFeedCounter.add(1, { url: urlString })
console.info(
"jobylon.feed start",
JSON.stringify({ query: { url: urlString } })
)
const cacheClient = await getCacheClient()
return await cacheClient.cacheOrGet(
const result = cacheClient.cacheOrGet(
"jobylon:feed",
async () => {
const response = await fetch(url, {
@@ -40,27 +35,14 @@ export const jobylonQueryRouter = router({
})
if (!response.ok) {
await metricsJobylonFeedGet.httpError(response)
const text = await response.text()
const error = {
throw new Error(
`Failed to fetch Jobylon feed: ${JSON.stringify({
status: response.status,
statusText: response.statusText,
text,
}
getJobylonFeedFailCounter.add(1, {
url: urlString,
error_type: "http_error",
error: JSON.stringify(error),
})
console.error(
"jobylon.feed error",
JSON.stringify({
query: { url: urlString },
error,
})
)
throw new Error(
`Failed to fetch Jobylon feed: ${JSON.stringify(error)}`
})}`
)
}
@@ -68,37 +50,20 @@ export const jobylonQueryRouter = router({
const validatedResponse = jobylonFeedSchema.safeParse(responseJson)
if (!validatedResponse.success) {
getJobylonFeedFailCounter.add(1, {
urlString,
error_type: "validation_error",
error: JSON.stringify(validatedResponse.error),
})
const errorData = JSON.stringify({
query: { url: urlString },
error: validatedResponse.error,
})
console.error("jobylon.feed error", errorData)
metricsJobylonFeedGet.validationError(validatedResponse.error)
throw new Error(
`Failed to parse Jobylon feed: ${JSON.stringify(errorData)}`
`Failed to parse Jobylon feed: ${JSON.stringify(validatedResponse.error)}`
)
}
getJobylonFeedSuccessCounter.add(1, {
url: urlString,
})
console.info(
"jobylon.feed success",
JSON.stringify({
query: { url: urlString },
})
)
return validatedResponse.data
},
"1d"
)
metricsJobylonFeedGet.success()
return result
}),
}),
})

View File

@@ -1,10 +0,0 @@
import { metrics } from "@opentelemetry/api"
const meter = metrics.getMeter("trpc.booking")
export const getJobylonFeedCounter = meter.createCounter("trpc.jobylon-feed")
export const getJobylonFeedSuccessCounter = meter.createCounter(
"trpc.jobylon-feed-success"
)
export const getJobylonFeedFailCounter = meter.createCounter(
"trpc.jobylon-feed-fail"
)

View File

@@ -11,11 +11,7 @@ export const staysInput = z
.number()
.optional()
.transform((num) => (num ? String(num) : undefined)),
limit: z
.number()
.min(0)
.default(6)
.transform((num) => String(num)),
limit: z.number().min(0).default(6),
lang: z.nativeEnum(Lang).optional(),
})
.default({})

View File

@@ -1,5 +1,3 @@
import { metrics } from "@opentelemetry/api"
import { signupVerify } from "@/constants/routes/signup"
import { env } from "@/env/server"
import * as api from "@/lib/api"
@@ -8,6 +6,7 @@ import {
initiateSaveCardSchema,
subscriberIdSchema,
} from "@/server/routers/user/output"
import { createCounter } from "@/server/telemetry"
import { protectedProcedure, router, serviceProcedure } from "@/server/trpc"
import {
@@ -17,20 +16,6 @@ import {
signupInput,
} from "./input"
const meter = metrics.getMeter("trpc.user")
const generatePreferencesLinkCounter = meter.createCounter(
"trpc.user.generatePreferencesLink"
)
const generatePreferencesLinkSuccessCounter = meter.createCounter(
"trpc.user.generatePreferencesLink-success"
)
const generatePreferencesLinkFailCounter = meter.createCounter(
"trpc.user.generatePreferencesLink-fail"
)
const signupCounter = meter.createCounter("trpc.user.signup")
const signupSuccessCounter = meter.createCounter("trpc.user.signup-success")
const signupFailCounter = meter.createCounter("trpc.user.signup-fail")
export const userMutationRouter = router({
creditCard: router({
add: protectedProcedure.input(addCreditCardInput).mutation(async function ({
@@ -159,7 +144,15 @@ export const userMutationRouter = router({
generatePreferencesLink: protectedProcedure.mutation(async function ({
ctx,
}) {
generatePreferencesLinkCounter.add(1)
const generatePreferencesLinkCounter = createCounter(
"trpc.user",
"generatePreferencesLink"
)
const metricsGeneratePreferencesLink = generatePreferencesLinkCounter.init()
metricsGeneratePreferencesLink.start()
const apiResponse = await api.get(api.endpoints.v1.Profile.subscriberId, {
headers: {
Authorization: `Bearer ${ctx.session.token.access_token}`,
@@ -167,25 +160,7 @@ export const userMutationRouter = router({
})
if (!apiResponse.ok) {
const text = await apiResponse.text()
generatePreferencesLinkFailCounter.add(1, {
error_type: "http_error",
error: JSON.stringify({
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
}),
})
console.error(
"api.user.subscriberId error ",
JSON.stringify({
error: {
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
},
})
)
await metricsGeneratePreferencesLink.httpError(apiResponse)
return null
}
@@ -194,31 +169,23 @@ export const userMutationRouter = router({
const validatedData = subscriberIdSchema.safeParse(data)
if (!validatedData.success) {
generatePreferencesLinkSuccessCounter.add(1, {
error_type: "validation_error",
error: JSON.stringify(validatedData.error),
})
console.error(
"api.user.generatePreferencesLink validation error",
JSON.stringify({
error: validatedData.error,
})
)
console.error(validatedData.error.format())
metricsGeneratePreferencesLink.validationError(validatedData.error)
return null
}
const preferencesLink = new URL(env.SALESFORCE_PREFERENCE_BASE_URL)
preferencesLink.searchParams.set("subKey", validatedData.data.subscriberId)
generatePreferencesLinkSuccessCounter.add(1)
metricsGeneratePreferencesLink.success()
return preferencesLink.toString()
}),
signup: serviceProcedure.input(signupInput).mutation(async function ({
ctx,
input,
}) {
signupCounter.add(1)
const signupCounter = createCounter("trpc.user", "signup")
const metricsSignup = signupCounter.init()
const apiResponse = await api.post(api.endpoints.v1.Profile.profile, {
body: input,
@@ -228,29 +195,13 @@ export const userMutationRouter = router({
})
if (!apiResponse.ok) {
await metricsSignup.httpError(apiResponse)
const text = await apiResponse.text()
signupFailCounter.add(1, {
error_type: "http_error",
error: JSON.stringify({
status: apiResponse.status,
statusText: apiResponse.statusText,
error: text,
}),
})
console.error(
"api.user.signup api error",
JSON.stringify({
error: {
status: apiResponse.status,
statusText: apiResponse.statusText,
error: text,
},
})
)
throw serverErrorByStatus(apiResponse.status, text)
}
signupSuccessCounter.add(1)
console.info("api.user.signup success")
metricsSignup.success()
return {
success: true,
redirectUrl: signupVerify[input.language],

View File

@@ -1,8 +1,5 @@
import { metrics } from "@opentelemetry/api"
import { countries } from "@/constants/countries"
import * as api from "@/lib/api"
import { dt } from "@/lib/dt"
import { createCounter } from "@/server/telemetry"
import {
languageProtectedProcedure,
protectedProcedure,
@@ -10,8 +7,6 @@ import {
safeProtectedProcedure,
} from "@/server/trpc"
import { cache } from "@/utils/cache"
import * as maskValue from "@/utils/maskValue"
import { isValidSession } from "@/utils/session"
import { getFriendsMembership, getMembershipCards } from "@/utils/user"
@@ -20,277 +15,21 @@ import {
getSavedPaymentCardsInput,
staysInput,
} from "./input"
import { getFriendTransactionsSchema } from "./output"
import {
creditCardsSchema,
getFriendTransactionsSchema,
getStaysSchema,
getUserSchema,
} from "./output"
import { updateStaysBookingUrl } from "./utils"
import type { Session } from "next-auth"
getCreditCards,
getPreviousStays,
getUpcomingStays,
getVerifiedUser,
parsedUser,
updateStaysBookingUrl,
} from "./utils"
import type {
LoginType,
TrackingSDKUserData,
} from "@/types/components/tracking"
import { Transactions } from "@/types/enums/transactions"
import type { User } from "@/types/user"
// OpenTelemetry metrics: User
const meter = metrics.getMeter("trpc.user")
const getVerifiedUserCounter = meter.createCounter("trpc.user.get")
const getVerifiedUserSuccessCounter = meter.createCounter(
"trpc.user.get-success"
)
const getVerifiedUserFailCounter = meter.createCounter("trpc.user.get-fail")
// OpenTelemetry metrics: Stays
const getPreviousStaysCounter = meter.createCounter("trpc.user.stays.previous")
const getPreviousStaysSuccessCounter = meter.createCounter(
"trpc.user.stays.previous-success"
)
const getPreviousStaysFailCounter = meter.createCounter(
"trpc.user.stays.previous-fail"
)
const getUpcomingStaysCounter = meter.createCounter("trpc.user.stays.upcoming")
const getUpcomingStaysSuccessCounter = meter.createCounter(
"trpc.user.stays.upcoming-success"
)
const getUpcomingStaysFailCounter = meter.createCounter(
"trpc.user.stays.upcoming-fail"
)
// OpenTelemetry metrics: Transactions
const getFriendTransactionsCounter = meter.createCounter(
"trpc.user.transactions.friendTransactions"
)
const getFriendTransactionsSuccessCounter = meter.createCounter(
"trpc.user.transactions.friendTransactions-success"
)
const getFriendTransactionsFailCounter = meter.createCounter(
"trpc.user.transactions.friendTransactions-fail"
)
// OpenTelemetry metrics: Credit Cards
const getCreditCardsCounter = meter.createCounter("trpc.user.creditCards")
const getCreditCardsSuccessCounter = meter.createCounter(
"trpc.user.creditCards-success"
)
const getCreditCardsFailCounter = meter.createCounter(
"trpc.user.creditCards-fail"
)
export const getVerifiedUser = cache(
async ({
session,
includeExtendedPartnerData,
}: {
session: Session
includeExtendedPartnerData?: boolean
}) => {
const now = Date.now()
if (session.token.expires_at && session.token.expires_at < now) {
return { error: true, cause: "token_expired" } as const
}
getVerifiedUserCounter.add(1)
console.info("api.user.profile getVerifiedUser start", JSON.stringify({}))
const apiResponse = await api.get(
api.endpoints.v2.Profile.profile,
{
headers: {
Authorization: `Bearer ${session.token.access_token}`,
},
},
includeExtendedPartnerData
? { includes: "extendedPartnerInformation" }
: {}
)
if (!apiResponse.ok) {
const text = await apiResponse.text()
getVerifiedUserFailCounter.add(1, {
error_type: "http_error",
error: JSON.stringify({
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
}),
})
console.error(
"api.user.profile getVerifiedUser error",
JSON.stringify({
error: {
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
},
})
)
if (apiResponse.status === 401) {
return { error: true, cause: "unauthorized" } as const
} else if (apiResponse.status === 403) {
return { error: true, cause: "forbidden" } as const
} else if (apiResponse.status === 404) {
return { error: true, cause: "notfound" } as const
}
return {
error: true,
cause: "unknown",
status: apiResponse.status,
} as const
}
const apiJson = await apiResponse.json()
if (!apiJson.data?.attributes) {
getVerifiedUserFailCounter.add(1, {
error_type: "data_error",
})
console.error(
"api.user.profile getVerifiedUser data error",
JSON.stringify({
apiResponse: apiJson,
})
)
return null
}
const verifiedData = getUserSchema.safeParse(apiJson)
if (!verifiedData.success) {
getVerifiedUserFailCounter.add(1, {
error_type: "validation_error",
error: JSON.stringify(verifiedData.error),
})
console.error(
"api.user.profile validation error",
JSON.stringify({
errors: verifiedData.error,
apiResponse: apiJson,
})
)
return null
}
getVerifiedUserSuccessCounter.add(1)
console.info("api.user.profile getVerifiedUser success", JSON.stringify({}))
return verifiedData
}
)
export function parsedUser(data: User, isMFA: boolean) {
const country = countries.find((c) => c.code === data.address?.countryCode)
const user = {
address: {
city: data.address?.city,
country: country?.name ?? "",
countryCode: data.address?.countryCode,
streetAddress: data.address?.streetAddress,
zipCode: data.address?.zipCode,
},
dateOfBirth: data.dateOfBirth,
email: data.email,
firstName: data.firstName,
language: data.language,
lastName: data.lastName,
membershipNumber: data.membershipNumber,
membership: getFriendsMembership(data.loyalty),
loyalty: data.loyalty,
name: `${data.firstName} ${data.lastName}`,
phoneNumber: data.phoneNumber,
profileId: data.profileId,
}
if (!isMFA) {
if (user.address.city) {
user.address.city = maskValue.text(user.address.city)
}
if (user.address.streetAddress) {
user.address.streetAddress = maskValue.text(user.address.streetAddress)
}
user.address.zipCode = data.address?.zipCode
? maskValue.text(data.address.zipCode)
: ""
user.dateOfBirth = maskValue.all(user.dateOfBirth)
user.email = maskValue.email(user.email)
user.phoneNumber = user.phoneNumber ? maskValue.phone(user.phoneNumber) : ""
}
return user
}
const getCreditCards = cache(
async ({
session,
onlyNonExpired,
}: {
session: Session
onlyNonExpired?: boolean
}) => {
getCreditCardsCounter.add(1)
console.info("api.profile.creditCards start", JSON.stringify({}))
const apiResponse = await api.get(api.endpoints.v1.Profile.creditCards, {
headers: {
Authorization: `Bearer ${session.token.access_token}`,
},
})
if (!apiResponse.ok) {
const text = await apiResponse.text()
getCreditCardsFailCounter.add(1, {
error_type: "http_error",
error: JSON.stringify({
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
}),
})
console.error(
"api.profile.creditCards error ",
JSON.stringify({
error: {
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
},
})
)
return null
}
const apiJson = await apiResponse.json()
const verifiedData = creditCardsSchema.safeParse(apiJson)
if (!verifiedData.success) {
getCreditCardsFailCounter.add(1, {
error_type: "validation_error",
error: JSON.stringify(verifiedData.error),
})
console.error(
"api.profile.creditCards validation error ",
JSON.stringify({ error: verifiedData.error })
)
return null
}
getCreditCardsSuccessCounter.add(1)
console.info("api.profile.creditCards success", JSON.stringify({}))
return verifiedData.data.data.filter((card) => {
if (onlyNonExpired) {
try {
const expirationDate = dt(card.expirationDate).startOf("day")
const currentDate = dt().startOf("day")
return expirationDate > currentDate
} catch (_) {
return false
}
}
return true
})
}
)
export const userQueryRouter = router({
get: protectedProcedure
@@ -385,11 +124,20 @@ export const userQueryRouter = router({
return membershipLevel
}),
userTrackingInfo: safeProtectedProcedure.query(async function ({ ctx }) {
const userTrackingInfoCounter = createCounter("user", "userTrackingInfo")
const metricsUserTrackingInfo = userTrackingInfoCounter.init()
metricsUserTrackingInfo.start()
const notLoggedInUserTrackingData: TrackingSDKUserData = {
loginStatus: "Non-logged in",
}
if (!isValidSession(ctx.session)) {
metricsUserTrackingInfo.success({
reason: "invalid session",
data: notLoggedInUserTrackingData,
})
return notLoggedInUserTrackingData
}
@@ -397,63 +145,25 @@ export const userQueryRouter = router({
const verifiedUserData = await getVerifiedUser({ session: ctx.session })
if (!verifiedUserData || "error" in verifiedUserData) {
metricsUserTrackingInfo.success({
reason: "invalid user data",
data: notLoggedInUserTrackingData,
})
return notLoggedInUserTrackingData
}
const params = new URLSearchParams()
params.set("limit", "1")
getPreviousStaysCounter.add(1, { query: JSON.stringify({ params }) })
console.info(
"api.booking.stays.past start",
JSON.stringify({ query: { params } })
const previousStaysData = await getPreviousStays(
ctx.session.token.access_token,
1
)
const previousStaysResponse = await api.get(
api.endpoints.v1.Booking.Stays.past,
{
headers: {
Authorization: `Bearer ${ctx.session.token.access_token}`,
},
},
params
)
if (!previousStaysResponse.ok) {
getPreviousStaysFailCounter.add(1, {
error_type: "http_error",
error: JSON.stringify({
status: previousStaysResponse.status,
statusText: previousStaysResponse.statusText,
}),
if (!previousStaysData) {
metricsUserTrackingInfo.success({
reason: "no previous stays data",
data: notLoggedInUserTrackingData,
})
console.error(
"api.booking.stays.past error",
JSON.stringify({
error: {
status: previousStaysResponse.status,
statusText: previousStaysResponse.statusText,
},
})
)
return notLoggedInUserTrackingData
}
const previousStaysApiJson = await previousStaysResponse.json()
const verifiedPreviousStaysData =
getStaysSchema.safeParse(previousStaysApiJson)
if (!verifiedPreviousStaysData.success) {
getPreviousStaysFailCounter.add(1, {
error_type: "validation_error",
error: JSON.stringify(verifiedPreviousStaysData.error),
})
console.error(
"api.booking.stays.past validation error, ",
JSON.stringify({ error: verifiedPreviousStaysData.error })
)
return notLoggedInUserTrackingData
}
getPreviousStaysSuccessCounter.add(1)
console.info("api.booking.stays.past success", JSON.stringify({}))
const membership = getFriendsMembership(verifiedUserData.data.loyalty)
const loggedInUserTrackingData: TrackingSDKUserData = {
@@ -462,13 +172,19 @@ export const userQueryRouter = router({
memberId: verifiedUserData.data.profileId,
membershipNumber: membership?.membershipNumber,
memberLevel: membership?.membershipLevel,
noOfNightsStayed: verifiedPreviousStaysData.data.links?.totalCount ?? 0,
noOfNightsStayed: previousStaysData.links?.totalCount ?? 0,
totalPointsAvailableToSpend: membership?.currentPoints,
loginAction: "login success",
}
metricsUserTrackingInfo.success({
reason: "valid logged in",
data: loggedInUserTrackingData,
})
return loggedInUserTrackingData
} catch (error) {
console.error("Error in userTrackingInfo:", error)
metricsUserTrackingInfo.fail(error)
return notLoggedInUserTrackingData
}
}),
@@ -479,86 +195,22 @@ export const userQueryRouter = router({
.query(async ({ ctx, input }) => {
const { limit, cursor, lang } = input
const language = lang || ctx.lang
const params: Record<string, string> = { limit }
if (cursor) {
params.offset = cursor
}
getPreviousStaysCounter.add(1, { query: JSON.stringify({ params }) })
console.info(
"api.booking.stays.past start",
JSON.stringify({ query: { params } })
const data = await getPreviousStays(
ctx.session.token.access_token,
limit,
cursor
)
const apiResponse = await api.get(
api.endpoints.v1.Booking.Stays.past,
{
headers: {
Authorization: `Bearer ${ctx.session.token.access_token}`,
},
},
params
)
if (!apiResponse.ok) {
const text = await apiResponse.text()
getPreviousStaysFailCounter.add(1, {
query: JSON.stringify({ params }),
error_type: "http_error",
error: JSON.stringify({
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
}),
})
console.error(
"api.booking.stays.past error ",
JSON.stringify({
query: { params },
error: {
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
},
})
)
return null
}
const apiJson = await apiResponse.json()
const verifiedData = getStaysSchema.safeParse(apiJson)
if (!verifiedData.success) {
getPreviousStaysFailCounter.add(1, {
query: JSON.stringify({ params }),
error_type: "validation_error",
error: JSON.stringify(verifiedData.error),
})
console.error(
"api.booking.stays.past validation error ",
JSON.stringify({
query: { params },
error: verifiedData.error,
})
)
return null
}
getPreviousStaysSuccessCounter.add(1, {
query: JSON.stringify({ params }),
})
console.info(
"api.booking.stays.past success",
JSON.stringify({ query: { params } })
)
if (data) {
const nextCursor =
verifiedData.data.links &&
verifiedData.data.links.offset < verifiedData.data.links.totalCount
? verifiedData.data.links.offset
data.links && data.links.offset < data.links.totalCount
? data.links.offset
: undefined
const updatedData = await updateStaysBookingUrl(
verifiedData.data.data,
ctx.session.token.access_token,
data.data,
ctx.session,
language
)
@@ -566,6 +218,8 @@ export const userQueryRouter = router({
data: updatedData,
nextCursor,
}
}
return null
}),
upcoming: languageProtectedProcedure
@@ -573,85 +227,22 @@ export const userQueryRouter = router({
.query(async ({ ctx, input }) => {
const { limit, cursor, lang } = input
const language = lang || ctx.lang
const params: Record<string, string> = { limit }
if (cursor) {
params.offset = cursor
}
getUpcomingStaysCounter.add(1, {
query: JSON.stringify({ params }),
})
console.info(
"api.booking.stays.future start",
JSON.stringify({ query: { params } })
)
const apiResponse = await api.get(
api.endpoints.v1.Booking.Stays.future,
{
headers: {
Authorization: `Bearer ${ctx.session.token.access_token}`,
},
},
params
const data = await getUpcomingStays(
ctx.session.token.access_token,
limit,
cursor
)
if (!apiResponse.ok) {
const text = await apiResponse.text()
getUpcomingStaysFailCounter.add(1, {
query: JSON.stringify({ params }),
error_type: "http_error",
error: JSON.stringify({
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
}),
})
console.error(
"api.booking.stays.future error ",
JSON.stringify({
query: { params },
error_type: "http_error",
error: {
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
},
})
)
return null
}
const apiJson = await apiResponse.json()
const verifiedData = getStaysSchema.safeParse(apiJson)
if (!verifiedData.success) {
getUpcomingStaysFailCounter.add(1, {
query: JSON.stringify({ params }),
error_type: "validation_error",
error: JSON.stringify(verifiedData.error),
})
console.error(
"api.booking.stays.future validation error ",
JSON.stringify({
query: { params },
error: verifiedData.error,
})
)
return null
}
getUpcomingStaysSuccessCounter.add(1, {
query: JSON.stringify({ params }),
})
console.info("api.booking.stays.future success", {
query: JSON.stringify({ params }),
})
if (data) {
const nextCursor =
verifiedData.data.links &&
verifiedData.data.links.offset < verifiedData.data.links.totalCount
? verifiedData.data.links.offset
data.links && data.links.offset < data.links.totalCount
? data.links.offset
: undefined
const updatedData = await updateStaysBookingUrl(
verifiedData.data.data,
ctx.session.token.access_token,
data.data,
ctx.session,
language
)
@@ -659,6 +250,8 @@ export const userQueryRouter = router({
data: updatedData,
nextCursor,
}
}
return null
}),
}),
transaction: router({
@@ -667,11 +260,18 @@ export const userQueryRouter = router({
.query(async ({ ctx, input }) => {
const { limit, page } = input
getFriendTransactionsCounter.add(1)
console.info(
"api.transaction.friendTransactions start",
JSON.stringify({})
const friendTransactionsCounter = createCounter(
"trpc.user.transactions",
"friendTransactions"
)
const metricsFriendTransactions = friendTransactionsCounter.init({
limit,
page,
})
metricsFriendTransactions.start()
const apiResponse = await api.get(
api.endpoints.v1.Profile.Transaction.friendTransactions,
{
@@ -682,61 +282,20 @@ export const userQueryRouter = router({
)
if (!apiResponse.ok) {
// switch (apiResponse.status) {
// case 400:
// throw badRequestError()
// case 401:
// throw unauthorizedError()
// case 403:
// throw forbiddenError()
// default:
// throw internalServerError()
// }
const text = await apiResponse.text()
getFriendTransactionsFailCounter.add(1, {
error_type: "http_error",
error: JSON.stringify({
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
}),
})
console.error(
"api.transaction.friendTransactions error ",
JSON.stringify({
error: {
status: apiResponse.status,
statusText: apiResponse.statusText,
text,
},
})
)
await metricsFriendTransactions.httpError(apiResponse)
return null
}
const apiJson = await apiResponse.json()
const verifiedData = getFriendTransactionsSchema.safeParse(apiJson)
if (!verifiedData.success) {
getFriendTransactionsFailCounter.add(1, {
error_type: "validation_error",
error: JSON.stringify(verifiedData.error),
})
console.error(
"api.transaction.friendTransactions validation error ",
JSON.stringify({ error: verifiedData.error })
)
metricsFriendTransactions.validationError(verifiedData.error)
return null
}
getFriendTransactionsSuccessCounter.add(1)
console.info(
"api.transaction.friendTransactions success",
JSON.stringify({})
)
const updatedData = await updateStaysBookingUrl(
verifiedData.data.data,
ctx.session.token.access_token,
ctx.session,
ctx.lang
)
@@ -763,7 +322,7 @@ export const userQueryRouter = router({
const slicedData = pageData.slice(limit * (page - 1), limit * page)
return {
const result = {
data: {
transactions: slicedData.map(({ type, attributes }) => {
return {
@@ -786,6 +345,10 @@ export const userQueryRouter = router({
totalPages: Math.ceil(pageData.length / limit),
},
}
metricsFriendTransactions.success()
return result
}),
}),

View File

@@ -1,73 +1,327 @@
import { metrics } from "@opentelemetry/api"
import { countries } from "@/constants/countries"
import { myBookingPath } from "@/constants/myBooking"
import { myStay } from "@/constants/routes/myStay"
import { env } from "@/env/server"
import * as api from "@/lib/api"
import { dt } from "@/lib/dt"
import { encrypt } from "@/server/routers/utils/encryption"
import { createCounter } from "@/server/telemetry"
import { cache } from "@/utils/cache"
import * as maskValue from "@/utils/maskValue"
import { isValidSession } from "@/utils/session"
import { getCurrentWebUrl } from "@/utils/url"
import { getFriendsMembership } from "@/utils/user"
import { encrypt } from "../utils/encryption"
import {
creditCardsSchema,
type FriendTransaction,
getStaysSchema,
getUserSchema,
type Stay,
} from "./output"
import type { Session } from "next-auth"
import type { User } from "@/types/user"
import type { Lang } from "@/constants/languages"
import type { FriendTransaction, Stay } from "./output"
const meter = metrics.getMeter("trpc.user")
const getProfileCounter = meter.createCounter("trpc.user.profile")
const getProfileSuccessCounter = meter.createCounter(
"trpc.user.profile-success"
)
const getProfileFailCounter = meter.createCounter("trpc.user.profile-fail")
export const getVerifiedUser = cache(
async ({
session,
includeExtendedPartnerData,
}: {
session: Session
includeExtendedPartnerData?: boolean
}) => {
const getVerifiedUserCounter = createCounter("user", "getVerifiedUser")
const metricsGetVerifiedUser = getVerifiedUserCounter.init()
async function updateStaysBookingUrl(
data: Stay[],
token: string,
lang: Lang
): Promise<Stay[]>
metricsGetVerifiedUser.start()
async function updateStaysBookingUrl(
data: FriendTransaction[],
token: string,
lang: Lang
): Promise<FriendTransaction[]>
const now = Date.now()
if (session.token.expires_at && session.token.expires_at < now) {
metricsGetVerifiedUser.dataError(`Token expired`)
return { error: true, cause: "token_expired" } as const
}
async function updateStaysBookingUrl(
data: Stay[] | FriendTransaction[],
token: string,
lang: Lang
) {
// Temporary API call needed till we have user name in ctx session data
getProfileCounter.add(1)
console.info("api.user.profile updatebookingurl start", JSON.stringify({}))
const apiResponse = await api.get(api.endpoints.v1.Profile.profile, {
cache: "no-store",
const apiResponse = await api.get(
api.endpoints.v2.Profile.profile,
{
headers: {
Authorization: `Bearer ${token}`,
Authorization: `Bearer ${session.token.access_token}`,
},
})
},
includeExtendedPartnerData
? { includes: "extendedPartnerInformation" }
: {}
)
if (!apiResponse.ok) {
getProfileFailCounter.add(1, { error: JSON.stringify(apiResponse) })
console.info(
"api.user.profile updatebookingurl error",
JSON.stringify({ error: apiResponse })
)
return data
await metricsGetVerifiedUser.httpError(apiResponse)
if (apiResponse.status === 401) {
return { error: true, cause: "unauthorized" } as const
} else if (apiResponse.status === 403) {
return { error: true, cause: "forbidden" } as const
} else if (apiResponse.status === 404) {
return { error: true, cause: "notfound" } as const
}
return {
error: true,
cause: "unknown",
status: apiResponse.status,
} as const
}
const apiJson = await apiResponse.json()
if (!apiJson.data?.attributes) {
return data
metricsGetVerifiedUser.dataError(
`Missing data attributes in API response`,
{
data: apiJson,
}
)
return null
}
getProfileSuccessCounter.add(1)
console.info("api.user.profile updatebookingurl success", JSON.stringify({}))
const verifiedData = getUserSchema.safeParse(apiJson)
if (!verifiedData.success) {
metricsGetVerifiedUser.validationError(verifiedData.error)
return null
}
metricsGetVerifiedUser.success()
return verifiedData
}
)
export async function getMembershipNumber(
session: Session | null
): Promise<string | undefined> {
if (!isValidSession(session)) return undefined
const verifiedUser = await getVerifiedUser({ session })
if (!verifiedUser || "error" in verifiedUser) {
return undefined
}
return verifiedUser.data.membershipNumber
}
export async function getPreviousStays(
accessToken: string,
limit: number = 10,
cursor?: string
) {
const getPreviousStaysCounter = createCounter("user", "getPreviousStays")
const metricsGetPreviousStays = getPreviousStaysCounter.init({
limit,
cursor,
})
metricsGetPreviousStays.start()
const apiResponse = await api.get(
api.endpoints.v1.Booking.Stays.past,
{
headers: {
Authorization: `Bearer ${accessToken}`,
},
},
{
limit,
cursor,
}
)
if (!apiResponse.ok) {
await metricsGetPreviousStays.httpError(apiResponse)
return null
}
const apiJson = await apiResponse.json()
const verifiedData = getStaysSchema.safeParse(apiJson)
if (!verifiedData.success) {
metricsGetPreviousStays.validationError(verifiedData.error)
return null
}
metricsGetPreviousStays.success()
return verifiedData.data
}
export async function getUpcomingStays(
accessToken: string,
limit: number = 10,
cursor?: string
) {
const getUpcomingStaysCounter = createCounter("user", "getUpcomingStays")
const metricsGetUpcomingStays = getUpcomingStaysCounter.init({
limit,
cursor,
})
metricsGetUpcomingStays.start()
const apiResponse = await api.get(
api.endpoints.v1.Booking.Stays.future,
{
headers: {
Authorization: `Bearer ${accessToken}`,
},
},
{
limit,
cursor,
}
)
if (!apiResponse.ok) {
await metricsGetUpcomingStays.httpError(apiResponse)
return null
}
const apiJson = await apiResponse.json()
const verifiedData = getStaysSchema.safeParse(apiJson)
if (!verifiedData.success) {
metricsGetUpcomingStays.validationError(verifiedData.error)
return null
}
metricsGetUpcomingStays.success()
return verifiedData.data
}
export function parsedUser(data: User, isMFA: boolean) {
const country = countries.find((c) => c.code === data.address?.countryCode)
const user = {
address: {
city: data.address?.city,
country: country?.name ?? "",
countryCode: data.address?.countryCode,
streetAddress: data.address?.streetAddress,
zipCode: data.address?.zipCode,
},
dateOfBirth: data.dateOfBirth,
email: data.email,
firstName: data.firstName,
language: data.language,
lastName: data.lastName,
membershipNumber: data.membershipNumber,
membership: getFriendsMembership(data.loyalty),
loyalty: data.loyalty,
name: `${data.firstName} ${data.lastName}`,
phoneNumber: data.phoneNumber,
profileId: data.profileId,
}
if (!isMFA) {
if (user.address.city) {
user.address.city = maskValue.text(user.address.city)
}
if (user.address.streetAddress) {
user.address.streetAddress = maskValue.text(user.address.streetAddress)
}
user.address.zipCode = data.address?.zipCode
? maskValue.text(data.address.zipCode)
: ""
user.dateOfBirth = maskValue.all(user.dateOfBirth)
user.email = maskValue.email(user.email)
user.phoneNumber = user.phoneNumber ? maskValue.phone(user.phoneNumber) : ""
}
return user
}
export const getCreditCards = cache(
async ({
session,
onlyNonExpired,
}: {
session: Session
onlyNonExpired?: boolean
}) => {
const getCreditCardsCounter = createCounter("user", "getCreditCards")
const metricsGetCreditCards = getCreditCardsCounter.init({
onlyNonExpired,
})
metricsGetCreditCards.start()
const apiResponse = await api.get(api.endpoints.v1.Profile.creditCards, {
headers: {
Authorization: `Bearer ${session.token.access_token}`,
},
})
if (!apiResponse.ok) {
await metricsGetCreditCards.httpError(apiResponse)
return null
}
const apiJson = await apiResponse.json()
const verifiedData = creditCardsSchema.safeParse(apiJson)
if (!verifiedData.success) {
metricsGetCreditCards.validationError(verifiedData.error)
return null
}
const result = verifiedData.data.data.filter((card) => {
if (onlyNonExpired) {
try {
const expirationDate = dt(card.expirationDate).startOf("day")
const currentDate = dt().startOf("day")
return expirationDate > currentDate
} catch (_) {
return false
}
}
return true
})
metricsGetCreditCards.success()
return result
}
)
export async function updateStaysBookingUrl(
data: Stay[],
session: Session,
lang: Lang
): Promise<Stay[]>
export async function updateStaysBookingUrl(
data: FriendTransaction[],
session: Session,
lang: Lang
): Promise<FriendTransaction[]>
export async function updateStaysBookingUrl(
data: Stay[] | FriendTransaction[],
session: Session,
lang: Lang
) {
const user = await getVerifiedUser({
session,
})
if (user && !("error" in user)) {
return data.map((d) => {
const originalString =
d.attributes.confirmationNumber.toString() +
"," +
apiJson.data.attributes.lastName
d.attributes.confirmationNumber.toString() + "," + user.data.lastName
const encryptedBookingValue = encrypt(originalString)
// Get base URL with fallback for ephemeral environments (like deploy previews).
@@ -88,7 +342,7 @@ async function updateStaysBookingUrl(
if (encryptedBookingValue) {
bookingUrl.searchParams.set("RefId", encryptedBookingValue)
} else {
bookingUrl.searchParams.set("lastName", apiJson.data.attributes.lastName)
bookingUrl.searchParams.set("lastName", user.data.lastName)
bookingUrl.searchParams.set(
"bookingId",
d.attributes.confirmationNumber.toString()
@@ -103,6 +357,7 @@ async function updateStaysBookingUrl(
},
}
})
}
}
export { updateStaysBookingUrl }
return data
}

View File

@@ -0,0 +1,135 @@
import { describe, expect, test } from "@jest/globals"
import { sanitize } from "./"
describe("sanitize", () => {
test("should handle valid primitive attributes", () => {
const input = {
key1: "value1",
key2: 10,
key3: true,
}
const expected = {
key1: "value1",
key2: 10,
key3: true,
}
expect(sanitize(input)).toEqual(expected)
})
test("should handle valid array attributes", () => {
const input = {
key1: ["value1", "value2"],
key2: [1, 2, 3],
key3: [true, false, true],
key4: [null, undefined, "a", 1, true],
}
const expected = {
"key1.0": "value1",
"key1.1": "value2",
"key2.0": 1,
"key2.1": 2,
"key2.2": 3,
"key3.0": true,
"key3.1": false,
"key3.2": true,
"key4.0": null,
"key4.1": undefined,
"key4.2": "a",
"key4.3": 1,
"key4.4": true,
}
expect(sanitize(input)).toEqual(expected)
})
test("should stringify non-valid attributes", () => {
const input = {
key1: new Date("2024-08-08T12:00:00Z"),
key2: { nested: "object" },
}
const expected = {
key1: '"2024-08-08T12:00:00.000Z"',
"key2.nested": "object",
}
expect(sanitize(input)).toEqual(expected)
})
test("should handle nested valid attributes", () => {
const input = {
key1: "Example",
key2: 10,
nested: {
nestedKey1: "Value",
nestedKey2: {
nestedKey2Key1: true,
},
},
}
const expected = {
key1: "Example",
key2: 10,
"nested.nestedKey1": "Value",
"nested.nestedKey2.nestedKey2Key1": true,
}
expect(sanitize(input)).toEqual(expected)
})
test("should handle a mix of valid and non-valid nested attributes", () => {
const input = {
key1: "Example",
key2: 10,
nested: {
nestedKey1: "Value",
nestedKey2: {
nestedKey2Key1: true,
nestedKey2Key2: new Date("2024-08-08T12:00:00Z"),
},
nestedKey3: {
reallyNested: "hello",
},
},
nonPrimitive: new Date("2024-08-08T13:00:00Z"),
}
const expected = {
key1: "Example",
key2: 10,
"nested.nestedKey1": "Value",
"nested.nestedKey2.nestedKey2Key1": true,
"nested.nestedKey2.nestedKey2Key2": '"2024-08-08T12:00:00.000Z"',
"nested.nestedKey3.reallyNested": "hello",
nonPrimitive: '"2024-08-08T13:00:00.000Z"',
}
expect(sanitize(input)).toEqual(expected)
})
test("should throw an error when a function is passed", () => {
const input = {
key1: () => {},
}
expect(() => sanitize(input)).toThrowError("Cannot sanitize function")
})
test("should throw an error when input not an object", () => {
// @ts-expect-error: array not allowed. We do this here to make sure the
// function not only relies on TS but actively blocks arrays as input.
expect(() => sanitize(null)).toThrowError()
// @ts-expect-error: array not allowed. We do this here to make sure the
// function not only relies on TS but actively blocks arrays as input.
expect(() => sanitize(undefined)).toThrowError()
// @ts-expect-error: array not allowed. We do this here to make sure the
// function not only relies on TS but actively blocks arrays as input.
expect(() => sanitize("")).toThrowError()
// @ts-expect-error: array not allowed. We do this here to make sure the
// function not only relies on TS but actively blocks arrays as input.
expect(() => sanitize([1, 2, 3])).toThrowError()
})
test("should handle empty input", () => {
const input = {}
const expected = {}
expect(sanitize(input)).toEqual(expected)
})
})

View File

@@ -0,0 +1,309 @@
// Central place for telemetry
// TODO: Replace all of this with proper tracers and events
import {
type Attributes,
type AttributeValue,
metrics,
} from "@opentelemetry/api"
import deepmerge from "deepmerge"
import { flatten } from "flat"
import {
every,
isArray,
isBoolean,
isFunction,
isNull,
isNumber,
isObject,
isPlainObject,
isString,
isUndefined,
keys,
mapValues,
} from "lodash-es"
import type { ZodError } from "zod"
type AttributesInput = Record<string, unknown>
function isAttributesInput(value: unknown): value is AttributesInput {
return (
isObject(value) &&
!isArray(value) &&
!isNull(value) &&
keys(value).length > 0 &&
every(keys(value), isString)
)
}
/**
* Checks if a given value is a valid OpenTelemetry `AttributeValue`.
* An `AttributeValue` can be a `string`, `number`, `boolean`, or a homogenous
* array containing only `null`, `undefined`, `string`, `number`, or `boolean`.
*
* @param value The value to check.
* @returns `true` if the value is a valid `AttributeValue`, `false` otherwise.
*/
export function isValidAttributeValue(value: unknown): value is AttributeValue {
if (isString(value) || isNumber(value) || isBoolean(value)) {
return true
}
if (isArray(value)) {
return every(
value,
(item) =>
isNull(item) ||
isUndefined(item) ||
isString(item) ||
isNumber(item) ||
isBoolean(item)
)
}
return false
}
/**
* Sanitizes an input object, ensuring its values are valid OpenTelemetry
* `AttributeValue` or `JSON.stringify()` representations as a fallback.
* It recursively processes nested objects and flattens the final object to one
* level deep with dot delimited keys for nested values.
*
* @param data The input object to sanitize.
* @returns The resulting object.
*
* @example
* ```typescript
* import { sanitize } from '@/server/telemetry';
*
* const input = {
* key1: "Example",
* key2: 10,
* nested: {
* nestedKey1: "Value",
* nestedKey2: {
* nestedKey2Key1: true,
* },
* },
* };
*
* const sanitized = sanitize(input);
* console.log(sanitized);
* // {
* // key1: "Example",
* // key2: 10,
* // "nested.nestedKey1": "Value",
* // "nested.nestedKey2.nestedKey2Key1": true,
* // }
* ```
*/
export function sanitize(data: AttributesInput): Attributes {
if (!isPlainObject(data)) {
throw new Error(`Input must be an object, got ${JSON.stringify(data)}`)
}
return flatten(
mapValues(data, (value) => {
if (isFunction(value)) {
throw new Error("Cannot sanitize function")
} else if (isValidAttributeValue(value)) {
return value
} else if (isAttributesInput(value)) {
return sanitize(value)
}
return JSON.stringify(value)
})
)
}
/**
* Creates an object that holds three OpenTelemetry counter instruments. One
* that represents the counter itself, one for the success and one for any fail.
* The object contains an `init method that acts as a factory to create the a
* final object that holds methods to record different types of events one the
* appropriate counter.
*
* @param meterName The name of the OpenTelemetry meter to create.
* @param counterName The name of the counter instrument to create.
* @returns An object with an `init` method that returns an object
* with methods for recording counter events.
*
* @example
*
* See the codebase for reference usage.
*/
export function createCounter(meterName: string, counterName: string) {
const meter = metrics.getMeter(meterName)
const fullName = `${meterName}.${counterName}`
const counter = meter.createCounter(fullName)
const success = meter.createCounter(`${fullName}-success`)
const fail = meter.createCounter(`${fullName}-fail`)
return {
/**
* Initializes the counter event handlers with a set of base attributes.
* These attributes will be included in all recorded events.
*
* @param baseAttrs - The base attributes to associate with the counter. Defaults to an empty object.
* @returns An object with methods to record specific counter events.
*/
init(baseAttrs: AttributesInput = {}) {
return {
/**
* Records an event for the main counter.
*
* @param attrs - Additional attributes specific to this 'start' event. Defaults to an empty object.
*/
start(attrs: AttributesInput = {}) {
const mergedAttrs = deepmerge.all<AttributesInput>([baseAttrs, attrs])
const finalAttrs = sanitize(mergedAttrs)
counter.add(1, finalAttrs)
console.info(`[${fullName}] start:`, finalAttrs)
},
/**
* Records an event for the success counter.
*
* @param attrs - Additional attributes specific to this 'success' event. Defaults to an empty object.
*/
success(attrs: AttributesInput = {}) {
const mergedAttrs = deepmerge.all<AttributesInput>([baseAttrs, attrs])
const finalAttrs = sanitize(mergedAttrs)
success.add(1, finalAttrs)
console.info(`[${fullName}] success:`, finalAttrs)
},
/**
* Records an event of type `data_error` for the fail counter.
* Used when some dependent data could not be resolved during the
* operation. Note that "no data" also exists and might be more
* appropriate in certain situations.
*
* @param errorMsg - A message describing the data error.
* @param attrs - Additional attributes specific to this 'dataError' event. Defaults to an empty object.
*/
dataError(errorMsg: string, attrs: AttributesInput = {}) {
const mergedAttrs = deepmerge.all<AttributesInput>([
baseAttrs,
attrs,
{
error_type: "data_error",
error: errorMsg,
},
])
const finalAttrs = sanitize(mergedAttrs)
fail.add(1, finalAttrs)
console.error(`[${fullName}] dataError:`, finalAttrs)
},
/**
* Records an event of type `not_found` for the fail counter.
* Used when some dependent data could not be found during the operation.
* Note that when there is an error resolving the data, the
* `dataError` method might be more appropriate.
*
* @param attrs - Additional attributes specific to this 'noDataError' event. Defaults to an empty object.
*/
noDataError(attrs: AttributesInput = {}) {
const mergedAttrs = deepmerge.all<AttributesInput>([
baseAttrs,
attrs,
{
error_type: "not_found",
},
])
const finalAttrs = sanitize(mergedAttrs)
fail.add(1, finalAttrs)
console.error(`[${fullName}] noDataError:`, finalAttrs)
},
/**
* Records an event of type `validation_error` for the fail counter.
* Used when a Zod schema fails validation.
*
* @param zodError - The {@link ZodError} object representing the validation error.
*/
validationError(zodError: ZodError) {
const mergedAttrs = deepmerge.all<AttributesInput>([
baseAttrs,
{
error_type: "validation_error",
error: zodError,
},
])
const finalAttrs = sanitize(mergedAttrs)
fail.add(1, finalAttrs)
console.error(`[${fullName}] validationError:`, finalAttrs)
},
/**
* Records an event of type `http_error` for the fail counter.
* Used when a `fetch(...)` call fails. **Note**: This method must be
* `await`ed as it is asynchronous!
* The given {@link Response} must be unprocessed and will be cloned
* to avoid interfering with its consumption outside this function.
*
* @param response - The HTTP {@link Response} object.
*/
async httpError(response: Response) {
const res = response.clone()
const text = await res.text()
const mergedAttrs = deepmerge.all<AttributesInput>([
baseAttrs,
{
error_type: "http_error",
error: {
status: res.status,
statusText: res.statusText,
text,
},
},
])
const finalAttrs = sanitize(mergedAttrs)
fail.add(1, finalAttrs)
console.error(`[${fullName}] httpError:`, finalAttrs)
},
/**
* Records an event of type `error` for the fail counter.
* Used when an error is thrown or an exception is caught, or as a
* general-purpose way to record an 'error' on the fail counter.
*
* @param err - An optional error object or message associated with the
* failure. Usually an instance of {@link Error} or a string.
*/
fail(err?: unknown) {
let msg = "unknown"
if (err && err instanceof Error) {
msg = err.message
} else if (typeof err === "string") {
msg = err
}
const mergedAttrs = deepmerge.all<AttributesInput>([
baseAttrs,
{
error_type: "error",
error: msg,
},
])
const finalAttrs = sanitize(mergedAttrs)
fail.add(1, finalAttrs)
console.error(`[${fullName}] fail:`, finalAttrs)
},
}
},
}
}

View File

@@ -1,21 +1,12 @@
import { metrics, trace, type Tracer } from "@opentelemetry/api"
import { trace, type Tracer } from "@opentelemetry/api"
import { env } from "@/env/server"
import { createCounter } from "@/server/telemetry"
import { getCacheClient } from "@/services/dataCache"
import type { ServiceTokenResponse } from "@/types/tokens"
// OpenTelemetry metrics: Service token
const meter = metrics.getMeter("trpc.context.serviceToken")
const fetchServiceTokenCounter = meter.createCounter(
"trpc.context.serviceToken.fetch-new-token"
)
const fetchServiceTokenFailCounter = meter.createCounter(
"trpc.context.serviceToken.fetch-fail"
)
export async function getServiceToken() {
const tracer = trace.getTracer("getServiceToken")
@@ -62,15 +53,32 @@ async function getOrSetServiceTokenFromCache(
}
async function getJwt(scopes: string[]) {
fetchServiceTokenCounter.add(1)
const getJwtCounter = createCounter("tokenManager", "getJwt")
const metricsGetJwt = getJwtCounter.init({
scopes,
})
metricsGetJwt.start()
const jwt = await fetchServiceToken(scopes)
const expiresAt = Date.now() + jwt.expires_in * 1000
metricsGetJwt.success()
return { expiresAt, jwt }
}
async function fetchServiceToken(scopes: string[]) {
fetchServiceTokenCounter.add(1)
const fetchServiceTokenCounter = createCounter(
"tokenManager",
"fetchServiceToken"
)
const metricsFetchServiceToken = fetchServiceTokenCounter.init({
scopes,
})
metricsFetchServiceToken.start()
const response = await fetch(`${env.CURITY_ISSUER_USER}/oauth/v2/token`, {
method: "POST",
@@ -87,36 +95,23 @@ async function fetchServiceToken(scopes: string[]) {
})
if (!response.ok) {
await metricsFetchServiceToken.httpError(response)
const text = await response.text()
const error = {
throw new Error(
`[fetchServiceToken] Failed to obtain service token: ${JSON.stringify({
status: response.status,
statusText: response.statusText,
text,
}
fetchServiceTokenFailCounter.add(1, {
error_type: "http_error",
error: JSON.stringify(error),
})
console.error(
"fetchServiceToken error",
JSON.stringify({
query: {
grant_type: "client_credentials",
client_id: env.CURITY_CLIENT_ID_SERVICE,
scope: scopes.join(" "),
},
error,
})
)
throw new Error(
`[fetchServiceToken] Failed to obtain service token: ${JSON.stringify(error)}`
})}`
)
}
return response.json() as Promise<ServiceTokenResponse>
const result = response.json() as Promise<ServiceTokenResponse>
metricsFetchServiceToken.success()
return result
}
function getServiceTokenCacheKey(scopes: string[]): string {

View File

@@ -0,0 +1,7 @@
{
"compilerOptions": {
"module": "ESNext",
"target": "ESNext",
"esModuleInterop": true
}
}

View File

@@ -18,6 +18,9 @@ export interface AncillaryPackagesInput
export interface PackagesInput
extends z.input<typeof roomPackagesInputSchema> {}
export interface PackagesOutput
extends z.output<typeof roomPackagesInputSchema> {}
export type Packages = z.output<typeof packagesSchema>
export type Package = NonNullable<Packages>[number]

View File

@@ -25,6 +25,9 @@ export type HotelsByHotelIdsAvailabilityInputSchema = z.output<
export type RoomsAvailabilityInputSchema = z.input<
typeof selectRateRoomsAvailabilityInputSchema
>
export type RoomsAvailabilityOutputSchema = z.output<
typeof selectRateRoomsAvailabilityInputSchema
>
export type RoomsAvailabilityInputRoom =
RoomsAvailabilityInputSchema["booking"]["rooms"][number]
export type RoomsAvailabilityExtendedInputSchema = z.input<

153
yarn.lock
View File

@@ -6402,6 +6402,7 @@ __metadata:
"@types/geojson": "npm:^7946.0.16"
"@types/jest": "npm:^29.5.12"
"@types/json-stable-stringify-without-jsonify": "npm:^1.0.2"
"@types/lodash-es": "npm:^4"
"@types/node": "npm:^20"
"@types/react": "npm:^18"
"@types/react-dom": "npm:^18"
@@ -6429,6 +6430,7 @@ __metadata:
eslint-plugin-simple-import-sort: "npm:^12.1.0"
fast-deep-equal: "npm:^3.1.3"
fetch-retry: "npm:^6.0.0"
flat: "npm:^6.0.1"
framer-motion: "npm:^11.3.28"
fuse.js: "npm:^7.1.0"
graphql: "npm:^16.8.1"
@@ -6446,6 +6448,7 @@ __metadata:
json-stable-stringify-without-jsonify: "npm:^1.0.1"
libphonenumber-js: "npm:^1.10.60"
lint-staged: "npm:^15.2.2"
lodash-es: "npm:^4.17.21"
material-symbols: "npm:^0.29.0"
nanoid: "npm:^5.0.9"
netlify-plugin-cypress: "npm:^2.2.1"
@@ -6464,13 +6467,13 @@ __metadata:
react-material-symbols: "npm:^4.4.0"
react-to-print: "npm:^3.0.2"
schema-dts: "npm:^1.1.2"
secure-json-parse: "npm:^4.0.0"
server-only: "npm:^0.0.1"
slugify: "npm:^1.6.6"
sonner: "npm:^1.7.0"
start-server-and-test: "npm:^2.0.3"
supercluster: "npm:^8.0.1"
superjson: "npm:^2.2.1"
ts-jest: "npm:^29.3.2"
ts-morph: "npm:^25.0.1"
ts-node: "npm:^10.9.2"
typescript: "npm:5.4.5"
@@ -8227,6 +8230,22 @@ __metadata:
languageName: node
linkType: hard
"@types/lodash-es@npm:^4":
version: 4.17.12
resolution: "@types/lodash-es@npm:4.17.12"
dependencies:
"@types/lodash": "npm:*"
checksum: 10c0/5d12d2cede07f07ab067541371ed1b838a33edb3c35cb81b73284e93c6fd0c4bbeaefee984e69294bffb53f62d7272c5d679fdba8e595ff71e11d00f2601dde0
languageName: node
linkType: hard
"@types/lodash@npm:*":
version: 4.17.16
resolution: "@types/lodash@npm:4.17.16"
checksum: 10c0/cf017901b8ab1d7aabc86d5189d9288f4f99f19a75caf020c0e2c77b8d4cead4db0d0b842d009b029339f92399f49f34377dd7c2721053388f251778b4c23534
languageName: node
linkType: hard
"@types/mdx@npm:^2.0.0":
version: 2.0.13
resolution: "@types/mdx@npm:2.0.13"
@@ -9608,7 +9627,7 @@ __metadata:
languageName: node
linkType: hard
"async@npm:^3.2.0":
"async@npm:^3.2.0, async@npm:^3.2.3":
version: 3.2.6
resolution: "async@npm:3.2.6"
checksum: 10c0/36484bb15ceddf07078688d95e27076379cc2f87b10c03b6dd8a83e89475a3c8df5848859dd06a4c95af1e4c16fc973de0171a77f18ea00be899aca2a4f85e70
@@ -9961,6 +9980,15 @@ __metadata:
languageName: node
linkType: hard
"bs-logger@npm:^0.2.6":
version: 0.2.6
resolution: "bs-logger@npm:0.2.6"
dependencies:
fast-json-stable-stringify: "npm:2.x"
checksum: 10c0/80e89aaaed4b68e3374ce936f2eb097456a0dddbf11f75238dbd53140b1e39259f0d248a5089ed456f1158984f22191c3658d54a713982f676709fbe1a6fa5a0
languageName: node
linkType: hard
"bser@npm:2.1.1":
version: 2.1.1
resolution: "bser@npm:2.1.1"
@@ -10193,7 +10221,7 @@ __metadata:
languageName: node
linkType: hard
"chalk@npm:4, chalk@npm:^4.0.0, chalk@npm:^4.1.0":
"chalk@npm:4, chalk@npm:^4.0.0, chalk@npm:^4.0.2, chalk@npm:^4.1.0":
version: 4.1.2
resolution: "chalk@npm:4.1.2"
dependencies:
@@ -11588,6 +11616,17 @@ __metadata:
languageName: node
linkType: hard
"ejs@npm:^3.1.10":
version: 3.1.10
resolution: "ejs@npm:3.1.10"
dependencies:
jake: "npm:^10.8.5"
bin:
ejs: bin/cli.js
checksum: 10c0/52eade9e68416ed04f7f92c492183340582a36482836b11eab97b159fcdcfdedc62233a1bf0bf5e5e1851c501f2dca0e2e9afd111db2599e4e7f53ee29429ae1
languageName: node
linkType: hard
"electron-to-chromium@npm:^1.5.73":
version: 1.5.103
resolution: "electron-to-chromium@npm:1.5.103"
@@ -12646,7 +12685,7 @@ __metadata:
languageName: node
linkType: hard
"fast-json-stable-stringify@npm:^2.0.0, fast-json-stable-stringify@npm:^2.1.0":
"fast-json-stable-stringify@npm:2.x, fast-json-stable-stringify@npm:^2.0.0, fast-json-stable-stringify@npm:^2.1.0":
version: 2.1.0
resolution: "fast-json-stable-stringify@npm:2.1.0"
checksum: 10c0/7f081eb0b8a64e0057b3bb03f974b3ef00135fbf36c1c710895cd9300f13c94ba809bb3a81cf4e1b03f6e5285610a61abbd7602d0652de423144dfee5a389c9b
@@ -12758,6 +12797,15 @@ __metadata:
languageName: node
linkType: hard
"filelist@npm:^1.0.4":
version: 1.0.4
resolution: "filelist@npm:1.0.4"
dependencies:
minimatch: "npm:^5.0.1"
checksum: 10c0/426b1de3944a3d153b053f1c0ebfd02dccd0308a4f9e832ad220707a6d1f1b3c9784d6cadf6b2f68f09a57565f63ebc7bcdc913ccf8012d834f472c46e596f41
languageName: node
linkType: hard
"fill-range@npm:^7.1.1":
version: 7.1.1
resolution: "fill-range@npm:7.1.1"
@@ -12807,6 +12855,15 @@ __metadata:
languageName: node
linkType: hard
"flat@npm:^6.0.1":
version: 6.0.1
resolution: "flat@npm:6.0.1"
bin:
flat: cli.js
checksum: 10c0/9dc0dbe6e2acc012512a53130d9ba1c82c1a596cdca91b23d11716348361c4a68928409bb4433c4493a17595c3efd0cab9f09e23dd3f9962a58af225c3efc23a
languageName: node
linkType: hard
"flatted@npm:^3.2.9":
version: 3.3.3
resolution: "flatted@npm:3.3.3"
@@ -14587,6 +14644,20 @@ __metadata:
languageName: node
linkType: hard
"jake@npm:^10.8.5":
version: 10.9.2
resolution: "jake@npm:10.9.2"
dependencies:
async: "npm:^3.2.3"
chalk: "npm:^4.0.2"
filelist: "npm:^1.0.4"
minimatch: "npm:^3.1.2"
bin:
jake: bin/cli.js
checksum: 10c0/c4597b5ed9b6a908252feab296485a4f87cba9e26d6c20e0ca144fb69e0c40203d34a2efddb33b3d297b8bd59605e6c1f44f6221ca1e10e69175ecbf3ff5fe31
languageName: node
linkType: hard
"jest-changed-files@npm:^29.7.0":
version: 29.7.0
resolution: "jest-changed-files@npm:29.7.0"
@@ -14972,7 +15043,7 @@ __metadata:
languageName: node
linkType: hard
"jest-util@npm:^29.7.0":
"jest-util@npm:^29.0.0, jest-util@npm:^29.7.0":
version: 29.7.0
resolution: "jest-util@npm:29.7.0"
dependencies:
@@ -15849,6 +15920,13 @@ __metadata:
languageName: node
linkType: hard
"lodash.memoize@npm:^4.1.2":
version: 4.1.2
resolution: "lodash.memoize@npm:4.1.2"
checksum: 10c0/c8713e51eccc650422716a14cece1809cfe34bc5ab5e242b7f8b4e2241c2483697b971a604252807689b9dd69bfe3a98852e19a5b89d506b000b4187a1285df8
languageName: node
linkType: hard
"lodash.merge@npm:^4.6.2":
version: 4.6.2
resolution: "lodash.merge@npm:4.6.2"
@@ -16207,7 +16285,7 @@ __metadata:
languageName: node
linkType: hard
"make-error@npm:^1.1.1":
"make-error@npm:^1.1.1, make-error@npm:^1.3.6":
version: 1.3.6
resolution: "make-error@npm:1.3.6"
checksum: 10c0/171e458d86854c6b3fc46610cfacf0b45149ba043782558c6875d9f42f222124384ad0b468c92e996d815a8a2003817a710c0a160e49c1c394626f76fa45396f
@@ -16437,6 +16515,15 @@ __metadata:
languageName: node
linkType: hard
"minimatch@npm:^5.0.1":
version: 5.1.6
resolution: "minimatch@npm:5.1.6"
dependencies:
brace-expansion: "npm:^2.0.1"
checksum: 10c0/3defdfd230914f22a8da203747c42ee3c405c39d4d37ffda284dac5e45b7e1f6c49aa8be606509002898e73091ff2a3bbfc59c2c6c71d4660609f63aa92f98e3
languageName: node
linkType: hard
"minimatch@npm:^8.0.2":
version: 8.0.4
resolution: "minimatch@npm:8.0.4"
@@ -19418,13 +19505,6 @@ __metadata:
languageName: node
linkType: hard
"secure-json-parse@npm:^4.0.0":
version: 4.0.0
resolution: "secure-json-parse@npm:4.0.0"
checksum: 10c0/1a298cf00e1de91e833cee5eb406d6e77fb2f7eca9bef3902047d49e7f5d3e6c21b5de61ff73466c831e716430bfe87d732a6e645a7dabb5f1e8a8e4d3e15eb4
languageName: node
linkType: hard
"semver@npm:^5.6.0":
version: 5.7.2
resolution: "semver@npm:5.7.2"
@@ -19443,7 +19523,7 @@ __metadata:
languageName: node
linkType: hard
"semver@npm:^7.3.5, semver@npm:^7.5.2, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.0, semver@npm:^7.6.2, semver@npm:^7.6.3":
"semver@npm:^7.3.5, semver@npm:^7.5.2, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.0, semver@npm:^7.6.2, semver@npm:^7.6.3, semver@npm:^7.7.1":
version: 7.7.1
resolution: "semver@npm:7.7.1"
bin:
@@ -20799,6 +20879,44 @@ __metadata:
languageName: node
linkType: hard
"ts-jest@npm:^29.3.2":
version: 29.3.2
resolution: "ts-jest@npm:29.3.2"
dependencies:
bs-logger: "npm:^0.2.6"
ejs: "npm:^3.1.10"
fast-json-stable-stringify: "npm:^2.1.0"
jest-util: "npm:^29.0.0"
json5: "npm:^2.2.3"
lodash.memoize: "npm:^4.1.2"
make-error: "npm:^1.3.6"
semver: "npm:^7.7.1"
type-fest: "npm:^4.39.1"
yargs-parser: "npm:^21.1.1"
peerDependencies:
"@babel/core": ">=7.0.0-beta.0 <8"
"@jest/transform": ^29.0.0
"@jest/types": ^29.0.0
babel-jest: ^29.0.0
jest: ^29.0.0
typescript: ">=4.3 <6"
peerDependenciesMeta:
"@babel/core":
optional: true
"@jest/transform":
optional: true
"@jest/types":
optional: true
babel-jest:
optional: true
esbuild:
optional: true
bin:
ts-jest: cli.js
checksum: 10c0/84762720dbef45c1644348d67d0dcb8b7ad6369a16628c4752aceeb47f0ccdad63ae14485048b641c20ce096337a160ab816881361ef5517325bac6a5b3756e0
languageName: node
linkType: hard
"ts-morph@npm:^25.0.1":
version: 25.0.1
resolution: "ts-morph@npm:25.0.1"
@@ -21029,6 +21147,13 @@ __metadata:
languageName: node
linkType: hard
"type-fest@npm:^4.39.1":
version: 4.40.0
resolution: "type-fest@npm:4.40.0"
checksum: 10c0/b39d4da6f9a154e3db7e714cd05ccf56b53f4f0bbf74dd294cb6be4921b16ecca5cb00cb81b53ab621a31c8e8509c74b5101895ada47af9de368a317d24538a3
languageName: node
linkType: hard
"type-is@npm:^1.6.16, type-is@npm:^1.6.18":
version: 1.6.18
resolution: "type-is@npm:1.6.18"