Skip to content

Commit

Permalink
Merge branch 'master' into subscribed-tab-improvements-20240606
Browse files Browse the repository at this point in the history
  • Loading branch information
darkruby501 committed Jun 7, 2024
2 parents 51916f0 + c229e36 commit 8c52cce
Show file tree
Hide file tree
Showing 12 changed files with 234 additions and 20 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/deployEABeta.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ concurrency: deploy-ea-beta
on:
workflow_dispatch:
push:
branches: [floating-audio-player]
branches: [audio-play-from-heading-fixes]
jobs:
deploy:
runs-on: ubuntu-latest
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ export const menuTabs: ForumOptions<Array<MenuTab>> = {
showOnCompressed: true
}, {
id: 'community',
title: 'Join a group',
title: 'Groups directory',
link: communityPath,
iconComponent: GroupsIcon,
selectedIconComponent: GroupsSelectedIcon,
Expand Down
12 changes: 11 additions & 1 deletion packages/lesswrong/components/posts/PostsPage/PostsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,12 @@ export const styles = (theme: ThemeType): JssStyles => ({
postBody: {
width: "max-content",
},
audioPlayerHidden: {
// Only show the play button next to headings if the audio player is visible
'& .t3a-heading-play-button': {
display: 'none !important'
},
},
postContent: {
marginBottom: isFriendlyUI ? 40 : undefined
},
Expand Down Expand Up @@ -683,7 +689,11 @@ const PostsPage = ({fullPost, postPreload, eagerPostComments, refetch, classes}:
// the same time.

const postBodySection =
<div id="postBody" className={classNames(classes.centralColumn, classes.postBody)}>
<div id="postBody" className={classNames(
classes.centralColumn,
classes.postBody,
!showEmbeddedPlayer && classes.audioPlayerHidden
)}>
{showSplashPageHeader && !commentId && !isDebateResponseLink && <h1 className={classes.secondSplashPageHeader}>
{post.title}
</h1>}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -231,9 +231,7 @@ const SuggestedFollowCard = ({user, handleSubscribeOrDismiss, hidden, classes}:
className={classNames(classes.subscribeButton, classes.followButton)}
onClick={() => handleSubscribeOrDismiss(user)}
>
<div>
Follow
</div>
Follow
</div>
</div>
</li>);
Expand Down
8 changes: 6 additions & 2 deletions packages/lesswrong/lib/utils/unflatten.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import * as _ from 'underscore';

export interface ThreadableCommentType {
_id: string
parentCommentId: string
topLevelCommentId: string
parentCommentId?: string | null
topLevelCommentId?: string | null
}
export interface CommentTreeNode<T extends ThreadableCommentType> {
item: T,
Expand Down Expand Up @@ -88,3 +88,7 @@ export function commentTreesEqual<Fragment extends ThreadableCommentType>(a: Arr
}
return true;
}

export function flattenCommentBranch<T extends ThreadableCommentType>(branch: CommentTreeNode<T>): T[] {
return [branch.item, ...branch.children.flatMap(flattenCommentBranch)];
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ async function flagOrBlockUserOnManyDMs({
validate: false,
});

if (allUsersEverContacted.length > MAX_ALLOWED_CONTACTS_BEFORE_BLOCK) {
if (allUsersEverContacted.length > MAX_ALLOWED_CONTACTS_BEFORE_BLOCK && !currentUser.reviewedAt) {
logger('Blocking user')
throw new Error(`You cannot message more than ${MAX_ALLOWED_CONTACTS_BEFORE_BLOCK} users before your account has been reviewed. Please contact us if you'd like to message more people.`)
}
Expand Down
2 changes: 1 addition & 1 deletion packages/lesswrong/server/callbacks/votingCallbacks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ voteCallbacks.castVoteAsync.add(async ({newDocument, vote}: VoteDocTuple, collec

const makeMarketComment = async (postId: string, year: number, marketUrl: string, botUser: DbUser) => {

const commentString = `<p>The <a href="https://www.lesswrong.com/bestoflesswrong">LessWrong Review</a> runs every year to select the posts that have most stood the test of time. This post is not yet eligible for review, but will be at the end of ${year+1}. The top fifty or so posts are featured prominently on the site throughout the year. <a href=${marketUrl}>Will this post make the top fifty?</a></p>
const commentString = `<p>The <a href="https://www.lesswrong.com/bestoflesswrong">LessWrong Review</a> runs every year to select the posts that have most stood the test of time. This post is not yet eligible for review, but will be at the end of ${year+1}. The top fifty or so posts are featured prominently on the site throughout the year.</p><p>Hopefully, the review is better than karma at judging enduring value. If we have accurate prediction markets on the review results, maybe we can have better incentives on LessWrong today. <a href="${marketUrl}">Will this post make the top fifty?</a></p>
`

const result = await createMutator({
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { randomId } from "@/lib/random";
import { createAdminContext } from "../vulcan-lib";
import { registerMigration } from "./migrationUtils";
import groupBy from "lodash/groupBy";
import uniq from "lodash/uniq";

function chunkWithoutSplitting<T>(dict: Record<string, T[]>, limit: number): T[][] {
const result: T[][] = [];
let currentChunk: T[] = [];

for (const key in dict) {
const values = dict[key];

if (currentChunk.length + values.length <= limit) {
currentChunk.push(...values);
} else {
if (currentChunk.length > 0) {
result.push(currentChunk);
currentChunk = [];
}

currentChunk.push(...values);

if (currentChunk.length > limit) {
// eslint-disable-next-line no-console
console.log(`Chunked ${currentChunk.length} values for id ${key}, which is over the ${limit} limit`);
result.push(currentChunk);
currentChunk = [];
}
}
}

if (currentChunk.length > 0) {
result.push(currentChunk);
}

return result;
}



registerMigration({
name: "backfillUserFeedSubscriptions",
dateWritten: "2024-06-04",
idempotent: true,
action: async () => {
const adminContext = createAdminContext();
const { Subscriptions } = adminContext;

const subscriptions = await Subscriptions.find({
collectionName: 'Users',
type: { $in: ['newPosts', 'newUserComments']
} }).fetch();

const currentSubscriptionStates = new Map<string, DbSubscription>();

// Get the most recent subscription record for each (userId, type, documentId) tuple
subscriptions.forEach(sub => {
const key = `${sub.userId}-${sub.type}-${sub.documentId}`;
const existingSub = currentSubscriptionStates.get(key);

if (!existingSub || existingSub.createdAt < sub.createdAt) {
currentSubscriptionStates.set(key, sub);
}
});

// Use only those most-recent subscriptions which are "active"
const activeSubscriptions = Array.from(currentSubscriptionStates.values()).filter(({ state, deleted }) => !deleted && state === 'subscribed');

const subscriptionsByType = groupBy(activeSubscriptions, ({ type }) => type);

const postAndCommentSubscriptions = uniq([...subscriptionsByType['newPosts'], ...subscriptionsByType['newUserComments']].map(({ userId, documentId }) => ({ userId, documentId })));

const now = new Date();

const bulkWriteOperations = postAndCommentSubscriptions.map(({ userId, documentId }) => ({
insertOne: {
document: {
_id: randomId(),
collectionName: 'Users',
type: 'newActivityForFeed',
state: 'subscribed',
deleted: false,
userId,
documentId,
createdAt: now,
schemaVersion: 1,
legacyData: null,
}
}
} as const));

const writeOperationsById = groupBy(bulkWriteOperations, ({ insertOne: { document: { userId } } }) => userId);

for (const batch of chunkWithoutSplitting(writeOperationsById, 1000)) {
await Subscriptions.rawCollection().bulkWrite(batch);
}
},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { Comments } from "@/lib/collections/comments";
import { getSqlClientOrThrow } from "../sql/sqlClient";
import { createAdminContext, updateMutator } from "../vulcan-lib";
import { registerMigration } from "./migrationUtils";

registerMigration({
name: "rewriteOldReviewBotComments",
dateWritten: "2024-06-05",
idempotent: true,
action: async () => {
const sql = getSqlClientOrThrow()

const context = createAdminContext()

const oldReviewBotComments: DbComment[] = await sql.many(`
SELECT * FROM "Comments"
WHERE "userId" = 'tBchiz3RM7rPwujrJ' AND "deleted" = FALSE
`);

const reviewBotUser: DbUser = await sql.one(`
SELECT * FROM "Users"
WHERE "_id" = 'tBchiz3RM7rPwujrJ'
`);

for (const comment of oldReviewBotComments) {
if (!comment.contents?.html) throw new Error("oh no!", String(comment.contents));
const manifoldUrl = comment.contents.html.match(/https:\/\/manifold.markets\/LessWrong\/([A-Za-z\d-])+/)?.[0];
const year = comment.contents.html.match(`but will be at the end of (\\d+)`)?.[1];
if (!manifoldUrl || !year) {
// eslint-disable-next-line no-console
console.warn("Skipping comment", comment._id, "because we didn't find a manifold URL or year", comment.contents.html)
continue
}

const newContent = `<p>The <a href="https://www.lesswrong.com/bestoflesswrong">LessWrong Review</a> runs every year to select the posts that have most stood the test of time. This post is not yet eligible for review, but will be at the end of ${year}. The top fifty or so posts are featured prominently on the site throughout the year.</p><p>Hopefully, the review is better than karma at judging enduring value. If we have accurate prediction markets on the review results, maybe we can have better incentives on LessWrong today. <a href="${manifoldUrl}">Will this post make the top fifty?</a></p>`;

if (newContent === comment.contents.html) continue;

await updateMutator({
collection: Comments,
documentId: comment._id,
currentUser: reviewBotUser,
context,
data: {
contents: {
originalContents: {
type: "html",
data: newContent,
}
},
}
})
}
},
});
2 changes: 2 additions & 0 deletions packages/lesswrong/server/manualMigrations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,3 +136,5 @@ import './2024-03-06-backfillDefaultVotingSystem'
import './2024-03-19-importACXmeetups'
import './2024-05-09-fixUnsubmittedFrontpagePosts'
import './2024-05-26-setNotNullClientIds'
import './2024-06-04-backfillUserFeedSubscriptions'
import './2024-06-05-rewriteOldReviewBotComments'
Original file line number Diff line number Diff line change
@@ -1,9 +1,44 @@
import { defineFeedResolver } from "../utils/feedUtil";
import { addGraphQLSchema } from "../vulcan-lib";
import keyBy from "lodash/keyBy";
import { loadByIds } from "../../lib/loaders";
import { filterNonnull } from "../../lib/utils/typeGuardUtils";
import { accessFilterMultiple } from "../../lib/utils/schemaUtils";
import { unflattenComments, flattenCommentBranch } from "@/lib/utils/unflatten";
import keyBy from "lodash/keyBy";
import sortBy from "lodash/sortBy";
import filter from "lodash/fp/filter";
import type { PostAndCommentsResultRow } from "../repos/PostsRepo";

function ensureHasId<T extends DbObject>(record: Partial<T>): record is Partial<T> & HasIdType {
return !!record._id;
}

function getPostComments(postAndCommentsRow: PostAndCommentsResultRow, commentsById: Record<string, Partial<DbComment> & HasIdType>) {
return filterNonnull(postAndCommentsRow.fullCommentTreeIds?.map(commentId => commentsById[commentId]) ?? []);
}

/**
* Return the ids for comments which, for each separate branch in a given tree, are the most recent comment by a user you're subscribed to, in that branch.
* Each branch should have zero or one comment IDs returned.
*/
function getExpandedCommentIds(postAndCommentsRow: PostAndCommentsResultRow, commentsById: Record<string, Partial<DbComment> & HasIdType>) {
const expansionCandidateIds = new Set(postAndCommentsRow.commentIds);
const allPostComments = getPostComments(postAndCommentsRow, commentsById);
const commentsTree = unflattenComments(allPostComments);

const expandCommentIds: string[] = [];

for (let commentNode of commentsTree) {
// Get the entire "branch" of comments as a flattened list, sorted by most recent comments first
const commentBranch = sortBy(flattenCommentBranch(commentNode), 'postedAt').reverse();
const commentToExpand = commentBranch.find(({ _id }) => expansionCandidateIds.has(_id));
if (commentToExpand) {
expandCommentIds.push(commentToExpand._id);
}
}

return expandCommentIds;
}

addGraphQLSchema(`
type SubscribedPostAndComments {
Expand Down Expand Up @@ -34,30 +69,40 @@ defineFeedResolver<Date>({
const postsAndComments = postsAndCommentsAll.slice(offset, (offset??0)+limit);
const isLastPage = postsAndComments.length < limit;
const lastFeedItem = postsAndComments.at(-1);
const nextCutoff = isLastPage ? null : (lastFeedItem?.last_commented ?? lastFeedItem?.postedAt) ?? null;
const nextCutoff = isLastPage ? null : (lastFeedItem?.last_commented ?? lastFeedItem?.postedAt ?? null);

const postIds: string[] = postsAndComments.map(row => row.postId);
const postIds: string[] = filterNonnull(postsAndComments.map(row => row.postId));
const commentIds: string[] = filterNonnull(postsAndComments.flatMap(row => row.fullCommentTreeIds ?? []));

const [posts, comments] = await Promise.all([
loadByIds(context, "Posts", postIds).then(posts => accessFilterMultiple(currentUser, context.Posts, posts, context)),
loadByIds(context, "Comments", commentIds).then(comments => accessFilterMultiple(currentUser, context.Comments, comments, context)),
loadByIds(context, "Posts", postIds)
.then(posts => accessFilterMultiple(currentUser, context.Posts, posts, context))
.then(filterNonnull)
.then(filter(ensureHasId)),
loadByIds(context, "Comments", commentIds)
.then(comments => accessFilterMultiple(currentUser, context.Comments, comments, context))
.then(filterNonnull)
.then(filter(ensureHasId)),
]);
const postsById = keyBy(filterNonnull(posts), p=>p._id);
const commentsById = keyBy(filterNonnull(comments), c=>c._id);

const postsById = keyBy(posts, p=>p._id);
const commentsById = keyBy(comments, c=>c._id);

return {
cutoff: nextCutoff,
endOffset: (offset??0)+postsAndComments.length,
results: postsAndComments.map(postAndCommentsRow => {
const allPostComments = getPostComments(postAndCommentsRow, commentsById);
const expandCommentIds = getExpandedCommentIds(postAndCommentsRow, commentsById);

return {
type: "postCommented",
postCommented: {
_id: postAndCommentsRow.postId,
post: postsById[postAndCommentsRow.postId],
postIsFromSubscribedUser: !!postAndCommentsRow.subscribedPosts,
comments: filterNonnull(postAndCommentsRow.fullCommentTreeIds?.map(commentId => commentsById[commentId]) ?? []),
expandCommentIds: postAndCommentsRow.commentIds
comments: allPostComments,
expandCommentIds
}
};
})
Expand Down
3 changes: 2 additions & 1 deletion packages/lesswrong/themes/globalStyles/globalStyles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,8 @@ const audioPlayerStyles = (theme: ThemeType): JssStyles => ({
marginRight: -1, // The margins here have been changed from the default to fit the EA Forum
},
/* Refine this to match the dimensions of your heading typeface */
'h2 .t3a-heading-play-button': { marginTop: 9 },
'h1 .t3a-heading-play-button': { marginTop: 10 },
'h2 .t3a-heading-play-button': { marginTop: 7 },
'h3 .t3a-heading-play-button': { marginTop: 3 },
}
})
Expand Down

0 comments on commit 8c52cce

Please sign in to comment.