Include more details when throwing errors for debugging in Sentry * WIP throw errors with more details for debugging in Sentry * Fix throwing response-data * Clearer message when a response fails * Add message to errors * better typings * . * Try to send profileID and membershipNumber to Sentry when we fail to parse the apiResponse * rename notFound -> notFoundError * Merge branch 'master' of bitbucket.org:scandic-swap/web into chore/add-error-details-for-sentry Approved-by: Linus Flood
215 lines
6.5 KiB
TypeScript
215 lines
6.5 KiB
TypeScript
import { createCounter } from "@scandic-hotels/common/telemetry"
|
|
import { safeTry } from "@scandic-hotels/common/utils/safeTry"
|
|
|
|
import { router } from "../../.."
|
|
import { notFoundError } from "../../../errors"
|
|
import {
|
|
GetDestinationOverviewPage,
|
|
GetDestinationOverviewPageRefs,
|
|
} from "../../../graphql/Query/DestinationOverviewPage/DestinationOverviewPage.graphql"
|
|
import { request } from "../../../graphql/request"
|
|
import {
|
|
contentstackExtendedProcedureUID,
|
|
serviceProcedure,
|
|
} from "../../../procedures"
|
|
import { ApiCountry } from "../../../types/country"
|
|
import {
|
|
generateRefsResponseTag,
|
|
generateTag,
|
|
} from "../../../utils/generateTag"
|
|
import { getCitiesByCountry } from "../../hotels/services/getCitiesByCountry"
|
|
import { getCountries } from "../../hotels/services/getCountries"
|
|
import { getHotelIdsByCityId } from "../../hotels/services/getHotelIdsByCityId"
|
|
import { getCityPageUrls } from "../destinationCityPage/utils"
|
|
import { getCountryPageUrls } from "../destinationCountryPage/utils"
|
|
import {
|
|
destinationOverviewPageRefsSchema,
|
|
destinationOverviewPageSchema,
|
|
} from "./output"
|
|
import { getSortedDestinationsByLanguage } from "./utils"
|
|
|
|
import type { Country } from "@scandic-hotels/common/constants/country"
|
|
|
|
import type {
|
|
GetDestinationOverviewPageData,
|
|
GetDestinationOverviewPageRefsSchema,
|
|
} from "../../../types/destinationOverviewPage"
|
|
import type { City, DestinationsData } from "../../../types/destinationsData"
|
|
import type { TrackingPageData } from "../../types"
|
|
|
|
export const destinationOverviewPageQueryRouter = router({
|
|
get: contentstackExtendedProcedureUID.query(async ({ ctx }) => {
|
|
const { lang, uid } = ctx
|
|
|
|
const getDestinationOverviewPageRefsCounter = createCounter(
|
|
"trpc.contentstack.destinationOverviewPage.get.refs"
|
|
)
|
|
const metricsGetDestinationOverviewPageRefs =
|
|
getDestinationOverviewPageRefsCounter.init({ lang, uid })
|
|
|
|
metricsGetDestinationOverviewPageRefs.start()
|
|
|
|
const variables = { locale: lang, uid }
|
|
const refsResponse = await request<GetDestinationOverviewPageRefsSchema>(
|
|
GetDestinationOverviewPageRefs,
|
|
variables,
|
|
{
|
|
key: generateRefsResponseTag(lang, uid),
|
|
ttl: "max",
|
|
}
|
|
)
|
|
if (!refsResponse.data) {
|
|
metricsGetDestinationOverviewPageRefs.noDataError()
|
|
throw notFoundError({
|
|
message: "GetDestinationOverviewPageRefs returned no data",
|
|
errorDetails: variables,
|
|
})
|
|
}
|
|
|
|
const validatedRefsData = destinationOverviewPageRefsSchema.safeParse(
|
|
refsResponse.data
|
|
)
|
|
|
|
if (!validatedRefsData.success) {
|
|
metricsGetDestinationOverviewPageRefs.validationError(
|
|
validatedRefsData.error
|
|
)
|
|
return null
|
|
}
|
|
|
|
metricsGetDestinationOverviewPageRefs.success()
|
|
|
|
const getDestinationOverviewPageCounter = createCounter(
|
|
"trpc.contentstack.destinationOverviewPage.get"
|
|
)
|
|
const metricsGetDestinationOverviewPage =
|
|
getDestinationOverviewPageCounter.init({ lang, uid })
|
|
|
|
metricsGetDestinationOverviewPage.start()
|
|
|
|
const response = await request<GetDestinationOverviewPageData>(
|
|
GetDestinationOverviewPage,
|
|
variables,
|
|
{
|
|
key: generateTag(lang, uid),
|
|
ttl: "max",
|
|
}
|
|
)
|
|
if (!response.data) {
|
|
metricsGetDestinationOverviewPage.noDataError()
|
|
throw notFoundError({
|
|
message: "GetDestinationOverviewPage returned no data",
|
|
errorDetails: variables,
|
|
})
|
|
}
|
|
|
|
const destinationOverviewPage = destinationOverviewPageSchema.safeParse(
|
|
response.data
|
|
)
|
|
|
|
if (!destinationOverviewPage.success) {
|
|
metricsGetDestinationOverviewPage.validationError(
|
|
destinationOverviewPage.error
|
|
)
|
|
return null
|
|
}
|
|
|
|
metricsGetDestinationOverviewPage.success()
|
|
|
|
const system = destinationOverviewPage.data.destination_overview_page.system
|
|
const tracking: TrackingPageData = {
|
|
pageId: system.uid,
|
|
domainLanguage: lang,
|
|
publishDate: system.updated_at,
|
|
createDate: system.created_at,
|
|
channel: "hotels",
|
|
pageType: "destinationoverviewpage",
|
|
pageName: "destinations|overview",
|
|
siteSections: "destinations|overview",
|
|
siteVersion: "new-web",
|
|
}
|
|
|
|
return {
|
|
destinationOverviewPage:
|
|
destinationOverviewPage.data.destination_overview_page,
|
|
tracking,
|
|
}
|
|
}),
|
|
destinations: router({
|
|
get: serviceProcedure.query(async function ({
|
|
ctx,
|
|
}): Promise<DestinationsData> {
|
|
const { lang } = ctx
|
|
|
|
const countries = await getCountries({
|
|
lang,
|
|
serviceToken: ctx.serviceToken,
|
|
})
|
|
|
|
if (!countries) {
|
|
return []
|
|
}
|
|
|
|
const countryNames = countries.data.map((country) => country.name)
|
|
|
|
const citiesByCountry = await getCitiesByCountry({
|
|
lang,
|
|
countries: countryNames,
|
|
serviceToken: ctx.serviceToken,
|
|
})
|
|
|
|
const cityPages = await getCityPageUrls(lang)
|
|
|
|
const destinations = await Promise.all(
|
|
Object.entries(citiesByCountry).map(async ([country, cities]) => {
|
|
const activeCitiesWithHotelCount: (City | null)[] = await Promise.all(
|
|
cities.map(async (city) => {
|
|
const [hotels] = await safeTry(
|
|
getHotelIdsByCityId({
|
|
cityId: city.id,
|
|
serviceToken: ctx.serviceToken,
|
|
})
|
|
)
|
|
|
|
const cityPage = cityPages.find(
|
|
(cityPage) => cityPage.city === city.cityIdentifier
|
|
)
|
|
|
|
return cityPage?.url
|
|
? {
|
|
id: city.id,
|
|
name: city.name,
|
|
hotelIds: hotels || [],
|
|
hotelCount: hotels ? hotels.length : 0,
|
|
url: cityPage.url,
|
|
}
|
|
: null
|
|
})
|
|
)
|
|
const filteredActiveCitiesWithHotelCount: City[] =
|
|
activeCitiesWithHotelCount.filter((c): c is City => !!c)
|
|
|
|
const countryPages = await getCountryPageUrls(lang)
|
|
const countryPage = countryPages.find(
|
|
(countryPage) =>
|
|
ApiCountry[lang][countryPage.country as Country] === country
|
|
)
|
|
|
|
return {
|
|
country,
|
|
countryUrl: countryPage?.url,
|
|
numberOfHotels: filteredActiveCitiesWithHotelCount.reduce(
|
|
(acc, city) => acc + city.hotelCount,
|
|
0
|
|
),
|
|
cities: filteredActiveCitiesWithHotelCount,
|
|
}
|
|
})
|
|
)
|
|
const data = getSortedDestinationsByLanguage(destinations, lang)
|
|
|
|
return data
|
|
}),
|
|
}),
|
|
})
|