mirror of
https://github.com/wassname/talk.git
synced 2026-07-26 13:37:38 +08:00
[CORL-1150] Discussions Tab (#3050)
* feat: Added Site.topStories edge * feat: Added User.ongoingDiscussions edge * connect discussions tab to relay * style discussions tab * add message if no ongoing discussions * style site name in story row * fix: make line-heights relative * add custom class hooks * feat: condition display based on feature flag * add target attribute to story links * fix: expose some featureFlags * fix: fixed sorting * fix: copy fixes Co-authored-by: tessalt <tessathornton@gmail.com> Co-authored-by: Chi Vinh Le <vinh@vinh.tech>
This commit is contained in:
co-authored by
tessalt
Chi Vinh Le
parent
9a9c92d360
commit
dfdea2b9d2
@@ -3,6 +3,8 @@ import { defaultTo } from "lodash";
|
||||
import { DateTime } from "luxon";
|
||||
|
||||
import GraphContext from "coral-server/graph/context";
|
||||
import { retrieveOngoingDiscussions } from "coral-server/models/comment";
|
||||
import { retrieveTopStoryMetrics } from "coral-server/models/comment/metrics";
|
||||
import { Connection } from "coral-server/models/helpers";
|
||||
import { CloseCommenting } from "coral-server/models/settings";
|
||||
import {
|
||||
@@ -24,6 +26,8 @@ import { scraper } from "coral-server/services/stories/scraper";
|
||||
import {
|
||||
GQLSTORY_STATUS,
|
||||
QueryToStoriesArgs,
|
||||
SiteToTopStoriesArgs,
|
||||
UserToOngoingDiscussionsArgs,
|
||||
} from "coral-server/graph/schema/__generated__/types";
|
||||
|
||||
import { createManyBatchLoadFn } from "./util";
|
||||
@@ -208,6 +212,29 @@ export default (ctx: GraphContext) => ({
|
||||
...queryFilter(query),
|
||||
},
|
||||
}).then(primeStoriesFromConnection(ctx)),
|
||||
topStories: (siteID: string, { limit }: SiteToTopStoriesArgs) => {
|
||||
// Find top active stories in the last 24 hours.
|
||||
const start = DateTime.fromJSDate(ctx.now).minus({ hours: 24 }).toJSDate();
|
||||
|
||||
return retrieveTopStoryMetrics(
|
||||
ctx.mongo,
|
||||
ctx.tenant.id,
|
||||
siteID,
|
||||
defaultTo(limit, 5),
|
||||
start,
|
||||
ctx.now
|
||||
);
|
||||
},
|
||||
ongoingDiscussions: (
|
||||
authorID: string,
|
||||
{ limit }: UserToOngoingDiscussionsArgs
|
||||
) =>
|
||||
retrieveOngoingDiscussions(
|
||||
ctx.mongo,
|
||||
ctx.tenant.id,
|
||||
authorID,
|
||||
defaultTo(limit, 5)
|
||||
),
|
||||
debugScrapeMetadata: new DataLoader(
|
||||
createManyBatchLoadFn((url: string) =>
|
||||
scraper.scrape({
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
retrieveAnnouncementIfEnabled,
|
||||
Tenant,
|
||||
} from "coral-server/models/tenant";
|
||||
import { hasModeratorRole } from "coral-server/models/user/helpers";
|
||||
|
||||
import {
|
||||
GQLFEATURE_FLAG,
|
||||
@@ -10,19 +11,43 @@ import {
|
||||
GQLWEBHOOK_EVENT_NAME,
|
||||
} from "coral-server/graph/schema/__generated__/types";
|
||||
|
||||
const filterValidFeatureFlags = () => {
|
||||
// Compute the valid flags based on this enum.
|
||||
const flags = Object.values(GQLFEATURE_FLAG);
|
||||
import GraphContext from "../context";
|
||||
|
||||
// Return a type guard for the feature flag.
|
||||
return (flag: string | GQLFEATURE_FLAG): flag is GQLFEATURE_FLAG =>
|
||||
flags.includes(flag as GQLFEATURE_FLAG);
|
||||
/**
|
||||
* FEATURE_FLAGS is an array of all the valid feature flags.
|
||||
*/
|
||||
const FEATURE_FLAGS = Object.values(GQLFEATURE_FLAG);
|
||||
|
||||
/**
|
||||
* PUBLIC_FEATURE_FLAGS are flags that are allowed to be returned when accessed
|
||||
* by a user with non-staff permissions.
|
||||
*/
|
||||
const PUBLIC_FEATURE_FLAGS = [GQLFEATURE_FLAG.DISCUSSIONS];
|
||||
|
||||
type FlagFilter = (flag: GQLFEATURE_FLAG | string) => boolean;
|
||||
|
||||
const filterValidFeatureFlags = (ctx: GraphContext): FlagFilter => {
|
||||
const filters: FlagFilter[] = [
|
||||
// Return a type guard for the feature flag.
|
||||
(flag): flag is GQLFEATURE_FLAG =>
|
||||
FEATURE_FLAGS.includes(flag as GQLFEATURE_FLAG),
|
||||
];
|
||||
|
||||
// For anonomous users or users without a moderator role, ensure we only send
|
||||
// back the public flags.
|
||||
if (!ctx.user || !hasModeratorRole(ctx.user)) {
|
||||
filters.push((flag) =>
|
||||
PUBLIC_FEATURE_FLAGS.includes(flag as GQLFEATURE_FLAG)
|
||||
);
|
||||
}
|
||||
|
||||
return (flag) => filters.every((filter) => filter(flag));
|
||||
};
|
||||
|
||||
export const Settings: GQLSettingsTypeResolver<Tenant> = {
|
||||
slack: ({ slack = {} }) => slack,
|
||||
featureFlags: ({ featureFlags = [] }) =>
|
||||
featureFlags.filter(filterValidFeatureFlags()),
|
||||
featureFlags: ({ featureFlags = [] }, args, ctx) =>
|
||||
featureFlags.filter(filterValidFeatureFlags(ctx)),
|
||||
announcement: ({ announcement }) =>
|
||||
retrieveAnnouncementIfEnabled(announcement),
|
||||
multisite: async ({ id }, input, ctx) => {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import * as site from "coral-server/models/site";
|
||||
|
||||
import { hasFeatureFlag } from "coral-server/models/tenant";
|
||||
import {
|
||||
canModerate,
|
||||
@@ -24,4 +25,16 @@ export const Site: GQLSiteTypeResolver<site.Site> = {
|
||||
|
||||
return canModerate(ctx.user, { siteID: id });
|
||||
},
|
||||
topStories: async ({ id }, args, ctx) => {
|
||||
// Get the top Story ID's from the loader.
|
||||
const results = await ctx.loaders.Stories.topStories(id, args);
|
||||
|
||||
// If there isn't any ids, then return nothing!
|
||||
if (results.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Get the Stories!
|
||||
return ctx.loaders.Stories.story.loadMany(results.map(({ _id }) => _id));
|
||||
},
|
||||
};
|
||||
|
||||
@@ -69,4 +69,16 @@ export const User: GQLUserTypeResolver<user.User> = {
|
||||
ignoreable: ({ role }) => !roleIsStaff(role),
|
||||
recentCommentHistory: ({ id }): RecentCommentHistoryInput => ({ userID: id }),
|
||||
profiles: ({ profiles = [] }) => profiles,
|
||||
ongoingDiscussions: async ({ id }, input, ctx) => {
|
||||
// Get the ongoing discussions from the loader.
|
||||
const results = await ctx.loaders.Stories.ongoingDiscussions(id, input);
|
||||
|
||||
// If there isn't any ids, then return nothing!
|
||||
if (results.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Get the Stories!
|
||||
return ctx.loaders.Stories.story.loadMany(results.map(({ _id }) => _id));
|
||||
},
|
||||
};
|
||||
|
||||
@@ -402,6 +402,11 @@ enum FEATURE_FLAG {
|
||||
in production environments.
|
||||
"""
|
||||
REDUCED_SECURITY_MODE
|
||||
|
||||
"""
|
||||
DISCUSSIONS will enable the discussions tab for the comment stream.
|
||||
"""
|
||||
DISCUSSIONS
|
||||
}
|
||||
|
||||
# The moderation mode of the site.
|
||||
@@ -1583,6 +1588,12 @@ type Site {
|
||||
"""
|
||||
canModerate: Boolean!
|
||||
|
||||
"""
|
||||
topStories will return stories that have had the most comments within the last
|
||||
24 hours on this Site.
|
||||
"""
|
||||
topStories(limit: Int = 5 @constraint(max: 5)): [Story!]!
|
||||
|
||||
"""
|
||||
createdAt is when the site was created.
|
||||
"""
|
||||
@@ -1754,7 +1765,7 @@ type Settings {
|
||||
"""
|
||||
featureFlags provides the enabled feature flags.
|
||||
"""
|
||||
featureFlags: [FEATURE_FLAG!]! @auth(roles: [ADMIN, MODERATOR])
|
||||
featureFlags: [FEATURE_FLAG!]!
|
||||
|
||||
"""
|
||||
createdAt is the time that the Settings was created at.
|
||||
@@ -2336,6 +2347,13 @@ type User {
|
||||
after: Cursor
|
||||
): CommentsConnection! @auth(roles: [ADMIN, MODERATOR])
|
||||
|
||||
"""
|
||||
ongoingDiscussions are stories where the given user has written comments in
|
||||
sorted by their last comment date.
|
||||
"""
|
||||
ongoingDiscussions(limit: Int = 5 @constraint(max: 5)): [Story!]!
|
||||
@auth(userIDField: "id", permit: [SUSPENDED, BANNED, PENDING_DELETION])
|
||||
|
||||
"""
|
||||
recentCommentHistory returns recent commenting history by the User.
|
||||
"""
|
||||
@@ -2429,6 +2447,7 @@ type User {
|
||||
ssoURL is the url for managing sso account
|
||||
"""
|
||||
ssoURL: String
|
||||
@auth(userIDField: "id", permit: [SUSPENDED, BANNED, PENDING_DELETION])
|
||||
}
|
||||
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user