diff --git a/dotcom-rendering/package.json b/dotcom-rendering/package.json index 8b2aa70839d..8cbaf77360b 100644 --- a/dotcom-rendering/package.json +++ b/dotcom-rendering/package.json @@ -228,7 +228,7 @@ "typescript": "5.3.3", "typescript-json-schema": "0.58.1", "unified": "10.1.2", - "valibot": "0.26.0", + "valibot": "0.28.1", "web-vitals": "3.5.1", "webpack": "5.89.0", "webpack-assets-manifest": "5.1.0", diff --git a/dotcom-rendering/src/components/Discussion.tsx b/dotcom-rendering/src/components/Discussion.tsx index 2aa1dc3a03e..e1f53318a81 100644 --- a/dotcom-rendering/src/components/Discussion.tsx +++ b/dotcom-rendering/src/components/Discussion.tsx @@ -10,11 +10,12 @@ import type { FilterOptions, SignedInUser, } from '../lib/discussion'; -import { getDiscussion, type reportAbuse } from '../lib/discussionApi'; import { getCommentContext, - initFiltersFromLocalStorage, -} from '../lib/getCommentContext'; + getDiscussion, + type reportAbuse, +} from '../lib/discussionApi'; +import { initFiltersFromLocalStorage } from '../lib/discussionFilters'; import { palette as themePalette } from '../palette'; import { Comments } from './Discussion/Comments'; import { Hide } from './Hide'; @@ -536,10 +537,16 @@ export const Discussion = ({ remapToValidFilters(filters, hashCommentId), ) .then((context) => { - dispatch({ - type: 'updateCommentPage', - commentPage: context.page, - }); + if (context.kind === 'ok') { + dispatch({ + type: 'updateCommentPage', + commentPage: context.value.page, + }); + } else { + console.error( + `getCommentContext - error: ${context.error}`, + ); + } }) .catch((e) => console.error(`getCommentContext - error: ${String(e)}`), diff --git a/dotcom-rendering/src/lib/discussion.ts b/dotcom-rendering/src/lib/discussion.ts index cea2cb71eb5..1e528de2ffd 100644 --- a/dotcom-rendering/src/lib/discussion.ts +++ b/dotcom-rendering/src/lib/discussion.ts @@ -389,3 +389,15 @@ export type CommentForm = { previewBody: string; body: string; }; + +export const getCommentContextResponseSchema = object({ + status: literal('ok'), + commentId: number(), + commentAncestorId: number(), + discussionKey: string(), + discussionWebUrl: string(), + discussionApiUrl: string(), + orderBy: picklist(orderBy), + pageSize: picklist(pageSize), + page: number(), +}); diff --git a/dotcom-rendering/src/lib/discussionApi.tsx b/dotcom-rendering/src/lib/discussionApi.tsx index cb0029d47d9..4aba8eb3284 100644 --- a/dotcom-rendering/src/lib/discussionApi.tsx +++ b/dotcom-rendering/src/lib/discussionApi.tsx @@ -9,12 +9,14 @@ import type { } from './discussion'; import { discussionApiResponseSchema, + getCommentContextResponseSchema, parseAbuseResponse, parseCommentRepliesResponse, parseCommentResponse, pickResponseSchema, postUsernameResponseSchema, } from './discussion'; +import type { CommentContextType } from './discussionFilters'; import type { SignedInWithCookies, SignedInWithOkta } from './identity'; import { getOptionsHeadersWithOkta } from './identity'; import { fetchJSON } from './json'; @@ -436,3 +438,39 @@ export const getMoreResponses = async ( return parseCommentRepliesResponse(jsonResult.value); }; + +const buildParams = (filters: FilterOptions): URLSearchParams => { + return new URLSearchParams({ + // Frontend uses the 'recommendations' key to store this options but the api expects + // 'mostRecommended' so we have to map here to support both + orderBy: + filters.orderBy === 'recommendations' + ? 'mostRecommended' + : filters.orderBy, + pageSize: String(filters.pageSize), + displayThreaded: String( + filters.threads === 'collapsed' || filters.threads === 'expanded', + ), + }); +}; + +export const getCommentContext = async ( + ajaxUrl: string, + commentId: number, + filters: FilterOptions, +): Promise> => { + const url = joinUrl(ajaxUrl, 'comment', commentId.toString(), 'context'); + const params = buildParams(filters); + + const jsonResult = await fetchJSON(url + '?' + params.toString()); + + if (jsonResult.kind === 'error') return jsonResult; + + const result = safeParse(getCommentContextResponseSchema, jsonResult.value); + + if (!result.success) { + return error('ParsingError'); + } + + return ok(result.output); +}; diff --git a/dotcom-rendering/src/lib/getCommentContext.ts b/dotcom-rendering/src/lib/discussionFilters.ts similarity index 56% rename from dotcom-rendering/src/lib/getCommentContext.ts rename to dotcom-rendering/src/lib/discussionFilters.ts index 232cac29825..25fd5920fb4 100644 --- a/dotcom-rendering/src/lib/getCommentContext.ts +++ b/dotcom-rendering/src/lib/discussionFilters.ts @@ -1,4 +1,4 @@ -import { isOneOf, isString, joinUrl, storage } from '@guardian/libs'; +import { isOneOf, isString, storage } from '@guardian/libs'; const orderByTypes = ['newest', 'oldest', 'recommendations'] as const; const threadTypes = ['collapsed', 'expanded', 'unthreaded'] as const; @@ -11,8 +11,8 @@ type PageSizeType = (typeof pageSizeTypes)[number]; /** * @see http://discussion.guardianapis.com/discussion-api/comment/3519111/context */ -type CommentContextType = { - status: 'ok' | 'error'; +export type CommentContextType = { + status: 'ok'; commentId: number; commentAncestorId: number; discussionKey: string; @@ -54,46 +54,3 @@ export const initFiltersFromLocalStorage = (): FilterOptions => { pageSize, }; }; - -const buildParams = (filters: FilterOptions) => { - return new URLSearchParams({ - // Frontend uses the 'recommendations' key to store this options but the api expects - // 'mostRecommended' so we have to map here to support both - orderBy: - filters.orderBy === 'recommendations' - ? 'mostRecommended' - : filters.orderBy, - pageSize: String(filters.pageSize), - displayThreaded: String( - filters.threads === 'collapsed' || filters.threads === 'expanded', - ), - }); -}; - -export const getCommentContext = async ( - ajaxUrl: string, - commentId: number, - filters: FilterOptions, -): Promise => { - const url = joinUrl(ajaxUrl, 'comment', commentId.toString(), 'context'); - - const params = buildParams(filters); - - return fetch(url + '?' + params.toString()) - .then((response) => { - if (!response.ok) { - throw Error( - response.statusText || - `getCommentContext | An api call returned HTTP status ${response.status}`, - ); - } - return response; - }) - .then((response) => response.json()) - .catch((error) => { - window.guardian.modules.sentry.reportError( - error, - 'get-comment-page', - ); - }); -}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 40f25595783..1ddccc7d96c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -897,8 +897,8 @@ importers: specifier: 10.1.2 version: 10.1.2 valibot: - specifier: 0.26.0 - version: 0.26.0 + specifier: 0.28.1 + version: 0.28.1 web-vitals: specifier: 3.5.1 version: 3.5.1 @@ -19955,8 +19955,8 @@ packages: convert-source-map: 2.0.0 dev: false - /valibot@0.26.0: - resolution: {integrity: sha512-cM8VM9jsJGx9P4qarZUBXd0I88OVI4ddtyZ1sAMiofC3Usj7Jxm2c8Xx5accdTrKc3CblO8yyytRaJ6lrsuA+Q==} + /valibot@0.28.1: + resolution: {integrity: sha512-zQnjwNJuXk6362Leu0+4eFa/SMwRom3/hEvH6s1EGf3oXIPbo2WFKDra9ymnbVh3clLRvd8hw4sKF5ruI2Lyvw==} dev: false /validate-npm-package-license@3.0.4: