docbrown/app/javascript/packs/homePageFeed.jsx
Ridhwana ccacf0bc3a
Refactor the home feed + add user tag filtering on in-feed billboards (#19600)
* feat: fix of there is no article

* feat: set the feed order in a data structure rather than in a view

* feat: update yarn.lock

* feat: push the items to the array

* feat: update feed to only show billboards if we have the correct length of items in the feed

* fix: organizedFeedItems.legth

* feat: rename 'featured' to 'image'

* feat: rename 'featured' to 'image'

* feat: rename 'featured' to 'image'

* feat: add some utilities for the feed that we can use in the FeedTest

* feat/WIP: the setup for the feed tests and a first working test

* test: imageItem

* fix: the podcasts can be passed through as an array of objects in the feedItems so that they can be grouped in one card

* feat: remove podcastEpisode state and the attribute in the object

* fix: check that the items exist before trying to slice them

* feat: setup the userdata and the podcast data in the test

* whoops - commit the podcast episodes

* feat: write soem more tests for the feed and including the podcasts

* feat: add some more tests

* feat: add more billboards tests to chcek the order of stuff

* feat: set the timeframe not empty

* feat: update the logic for organizaed feed by inserting the last on first

* doc: jsdoc for functions

* refactor: break the code up into smaller functions

* refactor: make the code more readable and easier to follow

* refactor: pull function out into a utlity

* feat: add specs to utility

* feat: chcek if pinned post

* test the latest timeframe correctly

* chore: update var name

* move the podcast items out of the object clause

* feat: update text

* feat: add a pack file that duplicates initializeDisplayAdVisibility

* feat: create callbacks that will help us to determine when the feed has been rendered so that we can observe the dsplay ads accordingly

* chore: rename to billboards instead of display ad

* feat: abstract out a function that will work for any tagged resource and also limit the article tags within a scope of the article_id

* feat: update the spec for the previous changes

* feat: write tests for the user_tags

* feat: add user_tags to for_display adn the query

* feat: add user_tags to the endpoint where we query the tags to send it through to for_display

* feat: pass through the suer tags to the async request

* feat: update the user_tags fiter query + tests

* feat: update the admin view to show targeted tags on the new feed option billboards too

* feat: add tests for targeted tags field on admin

* refactor: consolidate all of the options into one

* feat: move comment close to attribute

* feat: use a helper method

* feat: include helper on ads query

* feat: move the query from the frontend to live on the backend

* feat: first pass at some error handling whilst maintaining the order of the items

* test: for feed error

* feat: abstract out some code

* update the feed items for errors

* chore: comment

* feat: update the variable names

* feat: update honeybadger message

* feat: update the name of the variable

* refactor: do not conflate the duty of the untagged_ads

* refactor: rename the variables + add note for clarity

* fix: update var
2023-07-06 16:47:08 +02:00

174 lines
4 KiB
JavaScript

import { h, render } from 'preact';
import PropTypes from 'prop-types';
import { Article, LoadingArticle } from '../articles';
import { Feed } from '../articles/Feed';
import { TodaysPodcasts, PodcastEpisode } from '../podcasts';
import { articlePropTypes } from '../common-prop-types';
import { getUserDataAndCsrfToken } from '@utilities/getUserDataAndCsrfToken';
/**
* Sends analytics about the featured article.
*
* @param {number} articleId
*/
function sendFeaturedArticleGoogleAnalytics(articleId) {
(function logFeaturedArticleImpressionGA() {
if (!window.ga || !ga.create) {
setTimeout(logFeaturedArticleImpressionGA, 20);
return;
}
ga(
'send',
'event',
'view',
'featured-feed-impression',
`articles-${articleId}`,
null,
);
})();
}
function sendFeaturedArticleAnalyticsGA4(articleId) {
(function logFeaturedArticleImpressionGA4() {
if (!window.gtag) {
setTimeout(logFeaturedArticleImpressionGA4, 20);
return;
}
gtag('event', 'featured-feed-impression', {
event_category: 'view',
event_label: `articles-${articleId}`,
});
})();
}
function feedConstruct(
pinnedItem,
imageItem,
feedItems,
bookmarkedFeedItems,
bookmarkClick,
currentUserId,
) {
const commonProps = {
bookmarkClick,
};
const feedStyle = JSON.parse(document.body.dataset.user).feed_style;
if (imageItem) {
sendFeaturedArticleGoogleAnalytics(imageItem.id);
sendFeaturedArticleAnalyticsGA4(imageItem.id);
}
return feedItems.map((item) => {
// billboard is an html string
if (typeof item === 'string') {
return (
<div
key={item.id}
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{
__html: item,
}}
/>
);
}
if (Array.isArray(item) && item[0].podcast) {
return <PodcastEpisodes key={item.id} episodes={item} />;
}
if (typeof item === 'object') {
return (
<Article
{...commonProps}
key={item.id}
article={item}
pinned={item.id === pinnedItem?.id}
isFeatured={item.id === imageItem?.id}
feedStyle={feedStyle}
isBookmarked={bookmarkedFeedItems.has(item.id)}
saveable={item.user_id != currentUserId}
// For "saveable" props, "!=" is used instead of "!==" to compare user_id
// and currentUserId because currentUserId is a String while user_id is an Integer
/>
);
}
});
}
const FeedLoading = () => (
<div data-testid="feed-loading">
<LoadingArticle version="featured" />
<LoadingArticle />
<LoadingArticle />
<LoadingArticle />
<LoadingArticle />
<LoadingArticle />
<LoadingArticle />
</div>
);
const PodcastEpisodes = ({ episodes }) => (
<TodaysPodcasts>
{episodes.map((episode) => (
<PodcastEpisode episode={episode} key={episode.podcast.id} />
))}
</TodaysPodcasts>
);
PodcastEpisodes.defaultProps = {
episodes: [],
};
PodcastEpisodes.propTypes = {
episodes: PropTypes.arrayOf(articlePropTypes),
};
/**
* Renders the main feed.
*/
export const renderFeed = async (timeFrame, afterRender) => {
const feedContainer = document.getElementById('homepage-feed');
const { currentUser } = await getUserDataAndCsrfToken();
const currentUserId = currentUser && currentUser.id;
const callback = ({
pinnedItem,
imageItem,
feedItems,
bookmarkedFeedItems,
bookmarkClick,
}) => {
if (feedItems.length === 0) {
// Fancy loading ✨
return <FeedLoading />;
}
return (
<div>
{feedConstruct(
pinnedItem,
imageItem,
feedItems,
bookmarkedFeedItems,
bookmarkClick,
currentUserId,
)}
</div>
);
};
render(
<Feed
timeFrame={timeFrame}
renderFeed={callback}
afterRender={afterRender}
/>,
feedContainer,
feedContainer.firstElementChild,
);
};