diff --git a/src/pages/Howto/Content/Howto/Howto.test.tsx b/src/pages/Howto/Content/Howto/Howto.test.tsx index faee05c2be..d5bd39ac09 100644 --- a/src/pages/Howto/Content/Howto/Howto.test.tsx +++ b/src/pages/Howto/Content/Howto/Howto.test.tsx @@ -25,7 +25,6 @@ const mockHowtoStore = () => ({ setActiveHowtoBySlug: vi.fn(), activeHowto: howto, needsModeration: vi.fn().mockReturnValue(false), - incrementViewCount: vi.fn(), removeActiveHowto: vi.fn(), }) @@ -151,7 +150,7 @@ describe('Howto', () => { act(() => { wrapper = factory( { - ...mockHowtoStore(), + ...(mockHowtoStore() as any), }, FactoryHowto({ _createdBy: 'HowtoAuthor', diff --git a/src/pages/Howto/Content/Howto/HowtoDescription/HowtoDescription.tsx b/src/pages/Howto/Content/Howto/HowtoDescription/HowtoDescription.tsx index 68866408a3..9988a01011 100644 --- a/src/pages/Howto/Content/Howto/HowtoDescription/HowtoDescription.tsx +++ b/src/pages/Howto/Content/Howto/HowtoDescription/HowtoDescription.tsx @@ -1,4 +1,4 @@ -import { Fragment, useEffect, useState } from 'react' +import { Fragment, useState } from 'react' import { Link, useNavigate } from '@remix-run/react' import { Button, @@ -25,7 +25,6 @@ import { isAllowedToDeleteContent, isAllowedToEditContent, } from 'src/utils/helpers' -import { incrementViewCount } from 'src/utils/incrementViewCount' import { Alert, Box, Card, Divider, Flex, Heading, Image, Text } from 'theme-ui' import { ContentAuthorTimestamp } from '../../../../common/ContentAuthorTimestamp/ContentAuthorTimestamp' @@ -73,14 +72,6 @@ const HowtoDescription = ({ howto, loggedInUser, ...props }: IProps) => { } } - useEffect(() => { - incrementViewCount({ - document: howto, - documentType: 'howto', - store: stores.howtoStore, - }) - }, [howto._id]) - return ( { const mockResearchStore = { addSubscriberToResearchArticle: vi.fn(), formatResearchCommentList: vi.fn(), - incrementViewCount: vi.fn(), } it('displays content statistics', async () => { diff --git a/src/pages/Research/Content/ResearchDescription.tsx b/src/pages/Research/Content/ResearchDescription.tsx index 836c6a5f04..a528cf0a29 100644 --- a/src/pages/Research/Content/ResearchDescription.tsx +++ b/src/pages/Research/Content/ResearchDescription.tsx @@ -1,4 +1,4 @@ -import { Fragment, useEffect, useState } from 'react' +import { Fragment, useState } from 'react' import { Link, useNavigate } from '@remix-run/react' import { Button, @@ -19,7 +19,6 @@ import { TagList } from 'src/common/Tags/TagsList' import { logger } from 'src/logger' import { useResearchStore } from 'src/stores/Research/research.store' import { buildStatisticsLabel } from 'src/utils/helpers' -import { incrementViewCount } from 'src/utils/incrementViewCount' import { Box, Card, Divider, Flex, Heading, Text } from 'theme-ui' import { ContentAuthorTimestamp } from '../../common/ContentAuthorTimestamp/ContentAuthorTimestamp' @@ -82,14 +81,6 @@ const ResearchDescription = ({ } } - useEffect(() => { - incrementViewCount({ - store, - document: research, - documentType: 'research', - }) - }, [research._id]) - return ( ({ stores: { userStore: { updateUserBadge: mockUpdateUserBadge, - incrementViewCount: vi.fn(), }, aggregationsStore: { updateVerifiedUsers: vi.fn(), diff --git a/src/routes/_.how-to.$slug._index.tsx b/src/routes/_.how-to.$slug._index.tsx index 7e2d3f6959..df625b2a2c 100644 --- a/src/routes/_.how-to.$slug._index.tsx +++ b/src/routes/_.how-to.$slug._index.tsx @@ -3,6 +3,7 @@ import { useLoaderData } from '@remix-run/react' import { Howto } from 'src/pages/Howto/Content/Howto/Howto' import { howtoService } from 'src/pages/Howto/howto.service' import { NotFoundPage } from 'src/pages/NotFound/NotFound' +import { pageViewService } from 'src/services/pageView.service' import { generateTags, mergeMeta } from 'src/utils/seo.utils' import type { LoaderFunctionArgs } from '@remix-run/node' @@ -11,6 +12,11 @@ import type { IHowtoDB } from 'oa-shared' export async function loader({ params }: LoaderFunctionArgs) { const howto = await howtoService.getBySlug(params.slug as string) + if (howto?._id) { + // not awaited to not block the render + pageViewService.incrementViewCount('howtos', howto._id) + } + return json({ howto }) } diff --git a/src/routes/_.questions.$slug._index.tsx b/src/routes/_.questions.$slug._index.tsx index d3b1d8b5be..be4423f6b5 100644 --- a/src/routes/_.questions.$slug._index.tsx +++ b/src/routes/_.questions.$slug._index.tsx @@ -3,6 +3,7 @@ import { useLoaderData } from '@remix-run/react' import { NotFoundPage } from 'src/pages/NotFound/NotFound' import { questionService } from 'src/pages/Question/question.service' import { QuestionPage } from 'src/pages/Question/QuestionPage' +import { pageViewService } from 'src/services/pageView.service' import { generateTags, mergeMeta } from 'src/utils/seo.utils' import type { LoaderFunctionArgs } from '@remix-run/node' @@ -11,6 +12,11 @@ import type { IQuestionDB, IUploadedFileMeta } from 'oa-shared' export async function loader({ params }: LoaderFunctionArgs) { const question = await questionService.getBySlug(params.slug as string) + if (question?._id) { + // not awaited to not block the render + pageViewService.incrementViewCount('questions', question._id) + } + return json({ question }) } diff --git a/src/routes/_.research.$slug._index.tsx b/src/routes/_.research.$slug._index.tsx index 5895c1f866..94e54a6236 100644 --- a/src/routes/_.research.$slug._index.tsx +++ b/src/routes/_.research.$slug._index.tsx @@ -4,6 +4,7 @@ import { ResearchUpdateStatus } from 'oa-shared' import { NotFoundPage } from 'src/pages/NotFound/NotFound' import ResearchArticle from 'src/pages/Research/Content/ResearchArticle' import { researchService } from 'src/pages/Research/research.service' +import { pageViewService } from 'src/services/pageView.service' import { generateTags, mergeMeta } from 'src/utils/seo.utils' import type { LoaderFunctionArgs } from '@remix-run/node' @@ -16,6 +17,11 @@ export async function loader({ params }: LoaderFunctionArgs) { (x) => x.status !== ResearchUpdateStatus.DRAFT && x._deleted !== true, ) || [] + if (research?._id) { + // not awaited to not block the render + pageViewService.incrementViewCount('research', research._id) + } + return json({ research, publicUpdates }) } diff --git a/src/routes/_.u.$id._index.tsx b/src/routes/_.u.$id._index.tsx index fb1cae7a75..c27af90c12 100644 --- a/src/routes/_.u.$id._index.tsx +++ b/src/routes/_.u.$id._index.tsx @@ -1,6 +1,7 @@ import { json } from '@remix-run/node' import { useLoaderData } from '@remix-run/react' import { UserProfile } from 'src/pages/User/content/UserProfile' +import { pageViewService } from 'src/services/pageView.service' import { userService } from 'src/services/user.service' import { generateTags, mergeMeta } from 'src/utils/seo.utils' import { Text } from 'theme-ui' @@ -16,6 +17,11 @@ export async function loader({ params }: LoaderFunctionArgs) { userService.getUserCreatedDocs(userId), ]) + if (profile?._id) { + // not awaited to not block the render + pageViewService.incrementViewCount('users', profile._id) + } + return json({ profile, userCreatedDocs: userCreatedDocs || [] }) } diff --git a/src/services/pageView.service.ts b/src/services/pageView.service.ts new file mode 100644 index 0000000000..7b042e138a --- /dev/null +++ b/src/services/pageView.service.ts @@ -0,0 +1,23 @@ +import { doc, increment, updateDoc } from 'firebase/firestore' +import { DB_ENDPOINTS } from 'oa-shared' +import { firestore } from 'src/utils/firebase' + +type PageViewCollections = Pick< + typeof DB_ENDPOINTS, + 'users' | 'questions' | 'research' | 'howtos' +> + +const incrementViewCount = async ( + collectionName: keyof PageViewCollections, + id: string, +) => { + const docRef = doc(firestore, DB_ENDPOINTS[collectionName], id) + + return await updateDoc(docRef, { + total_views: increment(1), + }) +} + +export const pageViewService = { + incrementViewCount, +} diff --git a/src/stores/Howto/howto.store.test.ts b/src/stores/Howto/howto.store.test.ts index fe62774594..b0a5c48648 100644 --- a/src/stores/Howto/howto.store.test.ts +++ b/src/stores/Howto/howto.store.test.ts @@ -227,20 +227,4 @@ describe('howto.store', () => { expect(updatedDownloads).toBe(downloads + 1) }) }) - - describe('incrementViews', () => { - it('increments views by one', async () => { - const howtoDoc = FactoryHowto({ total_views: 18 }) - const { store, howToItem, updateFn } = await factory([howtoDoc]) - - // Act - await store.incrementViewCount(howToItem) - const updatedTotalViews = 19 - - expect(updateFn).toHaveBeenCalledWith( - expect.objectContaining({ total_views: updatedTotalViews }), - expect.anything(), - ) - }) - }) }) diff --git a/src/stores/Howto/howto.store.tsx b/src/stores/Howto/howto.store.tsx index 8d90e20717..3e9c7ac15e 100644 --- a/src/stores/Howto/howto.store.tsx +++ b/src/stores/Howto/howto.store.tsx @@ -3,7 +3,6 @@ import { logger } from 'src/logger' import { getUserCountry } from 'src/utils/getUserCountry' import { getKeywords } from 'src/utils/searchHelper' -import { incrementDocViewCount } from '../common/incrementDocViewCount' import { changeMentionToUserReference } from '../common/mentions' import { ModuleStore } from '../common/module.store' import { toggleDocUsefulByUser } from '../common/toggleDocUsefulByUser' @@ -91,14 +90,6 @@ export class HowtoStore extends ModuleStore { } } - public async incrementViewCount(howTo: Partial) { - return await incrementDocViewCount({ - collection: COLLECTION_NAME, - db: this.db, - doc: howTo, - }) - } - private async addUserReference(msg: string): Promise<{ text: string users: string[] diff --git a/src/stores/Question/question.store.test.tsx b/src/stores/Question/question.store.test.tsx index 2bf5cd0ecb..4e19c1cc77 100644 --- a/src/stores/Question/question.store.test.tsx +++ b/src/stores/Question/question.store.test.tsx @@ -81,30 +81,6 @@ describe('question.store', () => { }) }) - describe('incrementViews', () => { - it('increments views by one', async () => { - const { store, updateFn } = factory() - - const question = FactoryQuestionItem({ - title: 'which trees to cut down', - _createdBy: undefined, - total_views: 56, - }) - - // Act - await store.upsertQuestion(question) - - // Act - await store.incrementViewCount(question) - const updatedTotalViews = 57 - - expect(updateFn).toHaveBeenCalledWith( - expect.objectContaining({ total_views: updatedTotalViews }), - expect.anything(), - ) - }) - }) - describe('toggleSubscriberStatusByUserName', () => { it('calls the toggle subscriber function', async () => { const { store } = factory() diff --git a/src/stores/Question/question.store.tsx b/src/stores/Question/question.store.tsx index b2b89268c3..b71a42508d 100644 --- a/src/stores/Question/question.store.tsx +++ b/src/stores/Question/question.store.tsx @@ -3,7 +3,6 @@ import { logger } from 'src/logger' import { getUserCountry } from 'src/utils/getUserCountry' import { getKeywords } from 'src/utils/searchHelper' -import { incrementDocViewCount } from '../common/incrementDocViewCount' import { ModuleStore } from '../common/module.store' import { toggleDocSubscriberStatusByUserName } from '../common/toggleDocSubscriberStatusByUserName' import { toggleDocUsefulByUser } from '../common/toggleDocUsefulByUser' @@ -12,7 +11,6 @@ import type { IConvertedFileMeta, IModerationStatus, IQuestion, - IQuestionDB, IUploadedFileMeta, } from 'oa-shared' import type { DBEndpoint } from '../databaseV2/endpoints' @@ -25,14 +23,6 @@ export class QuestionStore extends ModuleStore { super(rootStore, COLLECTION_NAME) } - public async incrementViewCount(question: Partial) { - await incrementDocViewCount({ - collection: COLLECTION_NAME, - db: this.db, - doc: question, - }) - } - public async upsertQuestion(values: IQuestion.FormInput) { logger.info(`upsertQuestion:`, { values, activeUser: this.activeUser }) diff --git a/src/stores/Research/research.store.test.ts b/src/stores/Research/research.store.test.ts index d175f4ee6a..137109a60c 100644 --- a/src/stores/Research/research.store.test.ts +++ b/src/stores/Research/research.store.test.ts @@ -412,21 +412,6 @@ describe('research.store', () => { }) }) - describe('incrementViews', () => { - it('increments views by one', async () => { - const { store, researchItem, updateFn } = await factoryResearchItem() - - // Act - await store.incrementViewCount(researchItem) - const updatedTotalViews = researchItem.total_views + 1 - - expect(updateFn).toHaveBeenCalledWith( - expect.objectContaining({ total_views: updatedTotalViews }), - expect.anything(), - ) - }) - }) - describe('Subscribe', () => { it('adds subscriber to the research article', async () => { const { store, researchItem, updateFn } = diff --git a/src/stores/Research/research.store.tsx b/src/stores/Research/research.store.tsx index 52d08c5065..512a573530 100644 --- a/src/stores/Research/research.store.tsx +++ b/src/stores/Research/research.store.tsx @@ -7,7 +7,6 @@ import { getUserCountry } from 'src/utils/getUserCountry' import { hasAdminRights, randomID } from 'src/utils/helpers' import { getKeywords } from 'src/utils/searchHelper' -import { incrementDocViewCount } from '../common/incrementDocViewCount' import { changeMentionToUserReference } from '../common/mentions' import { ModuleStore } from '../common/module.store' import { toggleDocSubscriberStatusByUserName } from '../common/toggleDocSubscriberStatusByUserName' @@ -98,14 +97,6 @@ export class ResearchStore extends ModuleStore { return } - public async incrementViewCount(researchItem: Partial) { - return await incrementDocViewCount({ - collection: COLLECTION_NAME, - db: this.db, - doc: researchItem, - }) - } - public async deleteResearch(id: string) { try { const dbRef = this.db.collection(COLLECTION_NAME).doc(id) diff --git a/src/stores/User/user.store.ts b/src/stores/User/user.store.ts index afb94f4ee5..9bdc3bb9b9 100644 --- a/src/stores/User/user.store.ts +++ b/src/stores/User/user.store.ts @@ -18,7 +18,6 @@ import { logger } from '../../logger' import { auth, EmailAuthProvider } from '../../utils/firebase' import { getLocationData } from '../../utils/getLocationData' import { formatLowerNoSpecial } from '../../utils/helpers' -import { incrementDocViewCount } from '../common/incrementDocViewCount' import { ModuleStore } from '../common/module.store' import { Storage } from '../storage' @@ -274,14 +273,6 @@ export class UserStore extends ModuleStore { return this.activeUser } - public async incrementViewCount(user: Partial) { - return await incrementDocViewCount({ - collection: COLLECTION_NAME, - db: this.db, - doc: user, - }) - } - public async updateUserImpact( fields: IImpactYearFieldList, year: IImpactYear, diff --git a/src/stores/common/incrementDocViewCount.tsx b/src/stores/common/incrementDocViewCount.tsx deleted file mode 100644 index 0549bc00ee..0000000000 --- a/src/stores/common/incrementDocViewCount.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import type { IHowto, IQuestion, IResearch, IUserDB } from 'oa-shared' -import type { DatabaseV2 } from '../databaseV2/DatabaseV2' -import type { DBEndpoint } from '../databaseV2/endpoints' - -type ICollection = Partial - -interface IProps { - collection: DBEndpoint - db: DatabaseV2 - doc: ICollection -} - -export const incrementDocViewCount = async ({ - collection, - db, - doc, -}: IProps) => { - const { _id, total_views } = doc - const dbRef = db.collection(collection).doc(_id) - - const incrementedViews = (total_views || 0) + 1 - - await dbRef.update( - { total_views: incrementedViews }, - { keep_modified_timestamp: true }, - ) -} diff --git a/src/utils/incrementViewCount.test.ts b/src/utils/incrementViewCount.test.ts deleted file mode 100644 index c897ee2eea..0000000000 --- a/src/utils/incrementViewCount.test.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { incrementViewCount } from 'src/utils/incrementViewCount' -import { describe, expect, it, vi } from 'vitest' - -describe('incrementViewCount', () => { - it('calls the store incrementor', async () => { - const document = { - _id: 'dfghdxhg', - total_views: 42, - } - - const store = { - incrementViewCount: vi.fn(), - } as any - - await incrementViewCount({ - document, - documentType: 'howto', - store, - }) - - expect(store.incrementViewCount).toHaveBeenCalledWith(document) - }) - - it('only calls the incrementor once', async () => { - const document = { - _id: 'j343tdf', - total_views: 55, - } - - const store = { - incrementViewCount: vi.fn(), - } as any - - await incrementViewCount({ - document, - documentType: 'howto', - store, - }) - await incrementViewCount({ - document, - documentType: 'howto', - store, - }) - - expect(store.incrementViewCount).toHaveBeenCalledTimes(1) - }) -}) diff --git a/src/utils/incrementViewCount.ts b/src/utils/incrementViewCount.ts deleted file mode 100644 index 88dd19f94f..0000000000 --- a/src/utils/incrementViewCount.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { - addIDToSessionStorageArray, - retrieveSessionStorageArray, -} from './sessionStorage' - -import type { IHowtoDB, IQuestion, IResearch, IUserDB } from 'oa-shared' -import type { IStores } from 'src/stores/RootStore' - -type IDocument = Partial -type IDocumentType = 'howto' | 'question' | 'research' | 'user' - -interface IProps { - document: IDocument - documentType: IDocumentType - store: IStores['howtoStore' | 'researchStore' | 'questionStore' | 'userStore'] -} - -export const incrementViewCount = async ({ - document, - documentType, - store, -}: IProps) => { - const { _id } = document - if (!_id) return - - const sessionStorageArray = retrieveSessionStorageArray(documentType) - if (sessionStorageArray.includes(_id)) return - - await store.incrementViewCount(document) - addIDToSessionStorageArray(documentType, _id) -}