[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:
Wyatt Johnson
2020-07-30 17:18:59 -04:00
committed by GitHub
co-authored by tessalt Chi Vinh Le
parent 9a9c92d360
commit dfdea2b9d2
25 changed files with 886 additions and 48 deletions
+27
View File
@@ -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({
+33 -8
View File
@@ -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) => {
+13
View File
@@ -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));
},
};
+12
View File
@@ -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));
},
};
+20 -1
View File
@@ -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])
}
"""