From 78569fcb21e515634c831a2210094679f6da6112 Mon Sep 17 00:00:00 2001 From: Erik Tiekstra Date: Mon, 30 Sep 2024 13:11:27 +0200 Subject: [PATCH] feat(SW-497): Added global alerts query and typings --- lib/graphql/Fragments/GlobalAlert.graphql | 10 + lib/graphql/Query/ContactConfig.graphql | 2 + lib/graphql/Query/SiteConfiguration.graphql | 18 ++ server/routers/contentstack/base/output.ts | 53 +++++ server/routers/contentstack/base/query.ts | 183 +++++++++--------- server/routers/contentstack/base/telemetry.ts | 103 ++++++++++ .../routers/contentstack/siteConfiguration.ts | 17 ++ 7 files changed, 297 insertions(+), 89 deletions(-) create mode 100644 lib/graphql/Fragments/GlobalAlert.graphql create mode 100644 lib/graphql/Query/SiteConfiguration.graphql create mode 100644 server/routers/contentstack/base/telemetry.ts create mode 100644 types/trpc/routers/contentstack/siteConfiguration.ts diff --git a/lib/graphql/Fragments/GlobalAlert.graphql b/lib/graphql/Fragments/GlobalAlert.graphql new file mode 100644 index 000000000..a9d5712f9 --- /dev/null +++ b/lib/graphql/Fragments/GlobalAlert.graphql @@ -0,0 +1,10 @@ +fragment GlobalAlert on GlobalAlert { + type + heading + text + phone_contact { + display_text + phone_number + footnote + } +} diff --git a/lib/graphql/Query/ContactConfig.graphql b/lib/graphql/Query/ContactConfig.graphql index 381d00941..0a17e9568 100644 --- a/lib/graphql/Query/ContactConfig.graphql +++ b/lib/graphql/Query/ContactConfig.graphql @@ -19,10 +19,12 @@ query GetContactConfig($locale: String!) { phone { number name + footnote } phone_loyalty { name number + footnote } title visiting_address { diff --git a/lib/graphql/Query/SiteConfiguration.graphql b/lib/graphql/Query/SiteConfiguration.graphql new file mode 100644 index 000000000..f4d8a09af --- /dev/null +++ b/lib/graphql/Query/SiteConfiguration.graphql @@ -0,0 +1,18 @@ +#import "../Fragments/GlobalAlert.graphql" + +query GetSiteConfiguration($locale: String!) { + all_site_configuration(limit: 1, locale: $locale) { + items { + sitewide_alert { + booking_widget_disabled + alertConnection { + edges { + node { + ...GlobalAlert + } + } + } + } + } + } +} diff --git a/server/routers/contentstack/base/output.ts b/server/routers/contentstack/base/output.ts index 50b739bdb..9379fb370 100644 --- a/server/routers/contentstack/base/output.ts +++ b/server/routers/contentstack/base/output.ts @@ -15,6 +15,7 @@ import { removeMultipleSlashes } from "@/utils/url" import { systemSchema } from "../schemas/system" import { Image } from "@/types/image" +import { GlobalAlertType } from "@/types/trpc/routers/contentstack/siteConfiguration" // Help me write this zod schema based on the type ContactConfig export const validateContactConfigSchema = z.object({ @@ -39,10 +40,12 @@ export const validateContactConfigSchema = z.object({ phone: z.object({ number: z.string().nullable(), name: z.string().nullable(), + footnote: z.string().nullable(), }), phone_loyalty: z.object({ number: z.string().nullable(), name: z.string().nullable(), + footnote: z.string().nullable(), }), visiting_address: z.object({ zip: z.string().nullable(), @@ -660,3 +663,53 @@ export const headerSchema = z }, } }) + +export const globalAlertSchema = z.object({ + type: z.nativeEnum(GlobalAlertType), + text: z.string(), + heading: z.string(), + phone_contact: z.object({ + display_text: z.string(), + phone_number: z.string(), + footnote: z.string(), + }), +}) + +export const siteConfigurationSchema = z + .object({ + all_site_configuration: z.object({ + items: z + .array( + z.object({ + sitewide_alert: z.object({ + booking_widget_disabled: z.boolean(), + alertConnection: z.object({ + edges: z + .array( + z.object({ + node: globalAlertSchema, + }) + ) + .max(1), + }), + }), + }) + ) + .max(1), + }), + }) + .transform((data) => { + if (!data.all_site_configuration.items.length) { + return { + globalAlert: null, + bookingWidgetDisabled: false, + } + } + + const { sitewide_alert } = data.all_site_configuration.items[0] + + return { + globalAlert: sitewide_alert.alertConnection.edges[0]?.node || null, + bookingWidgetDisabled: sitewide_alert.booking_widget_disabled, + } + }) diff --git a/server/routers/contentstack/base/query.ts b/server/routers/contentstack/base/query.ts index 74b511123..f596d711a 100644 --- a/server/routers/contentstack/base/query.ts +++ b/server/routers/contentstack/base/query.ts @@ -1,5 +1,3 @@ -import { metrics } from "@opentelemetry/api" - import { GetContactConfig } from "@/lib/graphql/Query/ContactConfig.graphql" import { GetCurrentFooter, @@ -11,6 +9,7 @@ import { } from "@/lib/graphql/Query/Current/Header.graphql" import { GetFooter, GetFooterRef } from "@/lib/graphql/Query/Footer.graphql" import { GetHeader, GetHeaderRef } from "@/lib/graphql/Query/Header.graphql" +import { GetSiteConfiguration } from "@/lib/graphql/Query/SiteConfiguration.graphql" import { request } from "@/lib/graphql/request" import { notFound } from "@/server/errors/trpc" import { contentstackBaseProcedure, router } from "@/server/trpc" @@ -31,12 +30,41 @@ import { type GetCurrentHeaderData, headerRefsSchema, headerSchema, + siteConfigurationSchema, validateContactConfigSchema, validateCurrentFooterConfigSchema, validateCurrentHeaderConfigSchema, 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, + getSiteConfigurationCounter, + getSiteConfigurationFailCounter, + getSiteConfigurationSuccessCounter, +} from "./telemetry" import { getConnections, getFooterConnections } from "./utils" import type { @@ -47,93 +75,7 @@ import type { GetHeader as GetHeaderData, GetHeaderRefs, } from "@/types/trpc/routers/contentstack/header" - -const meter = metrics.getMeter("trpc.contentstack.base") -// OpenTelemetry metrics: ContactConfig -const getContactConfigCounter = meter.createCounter( - "trpc.contentstack.contactConfig.get" -) -const getContactConfigSuccessCounter = meter.createCounter( - "trpc.contentstack.contactConfig.get-success" -) -const getContactConfigFailCounter = meter.createCounter( - "trpc.contentstack.contactConfig.get-fail" -) -// OpenTelemetry metrics: CurrentHeader -const getCurrentHeaderRefCounter = meter.createCounter( - "trpc.contentstack.currentHeader.ref.get" -) -const getCurrentHeaderRefSuccessCounter = meter.createCounter( - "trpc.contentstack.currentHeader.ref.get-success" -) -const getCurrentHeaderRefFailCounter = meter.createCounter( - "trpc.contentstack.currentHeader.ref.get-fail" -) -const getCurrentHeaderCounter = meter.createCounter( - "trpc.contentstack.currentHeader.get" -) -const getCurrentHeaderSuccessCounter = meter.createCounter( - "trpc.contentstack.currentHeader.get-success" -) -const getCurrentHeaderFailCounter = meter.createCounter( - "trpc.contentstack.currentHeader.get-fail" -) - -// OpenTelemetry metrics: Header -const getHeaderRefsCounter = meter.createCounter( - "trpc.contentstack.header.ref.get" -) -const getHeaderRefsSuccessCounter = meter.createCounter( - "trpc.contentstack.header.ref.get-success" -) -const getHeaderRefsFailCounter = meter.createCounter( - "trpc.contentstack.header.ref.get-fail" -) -const getHeaderCounter = meter.createCounter("trpc.contentstack.header.get") -const getHeaderSuccessCounter = meter.createCounter( - "trpc.contentstack.header.get-success" -) -const getHeaderFailCounter = meter.createCounter( - "trpc.contentstack.header.get-fail" -) - -// OpenTelemetry metrics: CurrentHeader -const getCurrentFooterRefCounter = meter.createCounter( - "trpc.contentstack.currentFooter.ref.get" -) -const getCurrentFooterRefSuccessCounter = meter.createCounter( - "trpc.contentstack.currentFooter.ref.get-success" -) -const getCurrentFooterRefFailCounter = meter.createCounter( - "trpc.contentstack.currentFooter.ref.get-fail" -) -const getCurrentFooterCounter = meter.createCounter( - "trpc.contentstack.currentFooter.get" -) -const getCurrentFooterSuccessCounter = meter.createCounter( - "trpc.contentstack.currentFooter.get-success" -) -const getCurrentFooterFailCounter = meter.createCounter( - "trpc.contentstack.currentFooter.get-fail" -) - -// OpenTelemetry metrics: Footer -const getFooterRefCounter = meter.createCounter( - "trpc.contentstack.footer.ref.get" -) -const getFooterRefSuccessCounter = meter.createCounter( - "trpc.contentstack.footer.ref.get-success" -) -const getFooterRefFailCounter = meter.createCounter( - "trpc.contentstack.footer.ref.get-fail" -) -const getFooterCounter = meter.createCounter("trpc.contentstack.footer.get") -const getFooterSuccessCounter = meter.createCounter( - "trpc.contentstack.footer.get-success" -) -const getFooterFailCounter = meter.createCounter( - "trpc.contentstack.footer.get-fail" -) +import type { GetSiteConfigurationData } from "@/types/trpc/routers/contentstack/siteConfiguration" export const baseQueryRouter = router({ contact: contentstackBaseProcedure.query(async ({ ctx }) => { @@ -652,4 +594,67 @@ export const baseQueryRouter = router({ return validatedFooterConfig.data }), + siteConfiguration: contentstackBaseProcedure.query(async ({ ctx }) => { + const { lang } = ctx + getSiteConfigurationCounter.add(1, { lang }) + console.info( + "contentstack.siteConfiguration start", + JSON.stringify({ query: { lang } }) + ) + const response = await request( + GetSiteConfiguration, + { + locale: lang, + }, + { + cache: "force-cache", + next: { + tags: [`${lang}:siteConfiguration`], + }, + } + ) + + if (!response.data) { + const notFoundError = notFound(response) + + getSiteConfigurationFailCounter.add(1, { + lang, + error_type: "not_found", + error: JSON.stringify({ code: notFoundError.code }), + }) + + console.error( + "contentstack.siteConfiguration not found error", + JSON.stringify({ query: { lang }, error: { code: notFoundError.code } }) + ) + + throw notFoundError + } + + const validatedSiteConfiguration = siteConfigurationSchema.safeParse( + response.data + ) + + if (!validatedSiteConfiguration.success) { + getSiteConfigurationFailCounter.add(1, { + lang, + error_type: "validation_error", + error: JSON.stringify(validatedSiteConfiguration.error), + }) + console.error( + "contentstack.siteConfiguration validation error", + JSON.stringify({ + query: { lang }, + error: validatedSiteConfiguration.error, + }) + ) + return null + } + getSiteConfigurationSuccessCounter.add(1, { lang }) + console.info( + "contentstack.siteConfiguration success", + JSON.stringify({ query: { lang } }) + ) + return validatedSiteConfiguration.data + }), }) diff --git a/server/routers/contentstack/base/telemetry.ts b/server/routers/contentstack/base/telemetry.ts new file mode 100644 index 000000000..db7139ed4 --- /dev/null +++ b/server/routers/contentstack/base/telemetry.ts @@ -0,0 +1,103 @@ +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: SiteConfiguration +export const getSiteConfigurationCounter = meter.createCounter( + "trpc.contentstack.SiteConfiguration.get" +) +export const getSiteConfigurationSuccessCounter = meter.createCounter( + "trpc.contentstack.SiteConfiguration.get-success" +) +export const getSiteConfigurationFailCounter = meter.createCounter( + "trpc.contentstack.SiteConfiguration.get-fail" +) diff --git a/types/trpc/routers/contentstack/siteConfiguration.ts b/types/trpc/routers/contentstack/siteConfiguration.ts new file mode 100644 index 000000000..dcc86c4ec --- /dev/null +++ b/types/trpc/routers/contentstack/siteConfiguration.ts @@ -0,0 +1,17 @@ +import { z } from "zod" + +import { + globalAlertSchema, + siteConfigurationSchema, +} from "@/server/routers/contentstack/base/output" + +export enum GlobalAlertType { + Info = "info", + Alarm = "alarm", +} + +export type GetSiteConfigurationData = z.input +export type SiteConfiguration = z.output + +export type GetGlobalAlertData = z.input +export type GlobalAlert = z.output