* [CORL-863] Allow streams to be converted/configured to Q&A (#2809)

* Create preliminary schema changes for Q&A

Adds a mode and expert User onto the StorySettings.
Adds a mode selection drop down on a story's
Configure tab.

CORL-863

* Allow multiple experts, remove form elements from search

Makes the previous expert user on a Q&A story now
an array of users who can be assigned.

Converts the previous form based search that was
pulled from the admin community area into a set
of events built on callbacks.

CORL-863

* Create addExpertToStory mutation

CORL-863

* Create removeExpertFromStory mutation

CORL-863

* Conditionally show the the expert selection options

CORL-863

* Create a dropdown search control for Q&A experts

CORL-863

* Fixing up tests to match new QA stream options

Adds a few localization fixes to make sure tests
pass.
Updates existing snapshots.

CORL-863

* Add load more button to expert search list

CORL-863

* Update experts query to match react upgrades

CORL-863

* Move the Q&A config to its own section under stream config

Create enable and disable Q&A mutations/button toggle.

CORL-863

* Fix alignment and layout of expert list items

CORL-863

* Define translations and update tests

CORL-863

* Use official copy for Q&A config

CORL-863

* [CORL-856] Show expert badge on comments (#2829)

* Create preliminary schema changes for Q&A

Adds a mode and expert User onto the StorySettings.
Adds a mode selection drop down on a story's
Configure tab.

CORL-863

* Allow multiple experts, remove form elements from search

Makes the previous expert user on a Q&A story now
an array of users who can be assigned.

Converts the previous form based search that was
pulled from the admin community area into a set
of events built on callbacks.

CORL-863

* Create addExpertToStory mutation

CORL-863

* Create removeExpertFromStory mutation

CORL-863

* Conditionally show the the expert selection options

CORL-863

* Create a dropdown search control for Q&A experts

CORL-863

* Fixing up tests to match new QA stream options

Adds a few localization fixes to make sure tests
pass.
Updates existing snapshots.

CORL-863

* Add load more button to expert search list

CORL-863

* Update experts query to match react upgrades

CORL-863

* Move the Q&A config to its own section under stream config

Create enable and disable Q&A mutations/button toggle.

CORL-863

* Fix alignment and layout of expert list items

CORL-863

* Define translations and update tests

CORL-863

* Use official copy for Q&A config

CORL-863

* Show expert badges on comments when Q&A is enabled

CORL-856

* Update mutation responses and tests due to added expert fields

CORL-856

* Use EXPERT user tags to denote expert users

Removes the need for viewerIsExpert and
authorIsExpert loader/resolvers on Stories
and Comments respectively.

CORL-856

* [CORL-879] Add an unanswered tab to stream when in Q&A mode (#2838)

* Create preliminary schema changes for Q&A

Adds a mode and expert User onto the StorySettings.
Adds a mode selection drop down on a story's
Configure tab.

CORL-863

* Allow multiple experts, remove form elements from search

Makes the previous expert user on a Q&A story now
an array of users who can be assigned.

Converts the previous form based search that was
pulled from the admin community area into a set
of events built on callbacks.

CORL-863

* Create addExpertToStory mutation

CORL-863

* Create removeExpertFromStory mutation

CORL-863

* Conditionally show the the expert selection options

CORL-863

* Create a dropdown search control for Q&A experts

CORL-863

* Fixing up tests to match new QA stream options

Adds a few localization fixes to make sure tests
pass.
Updates existing snapshots.

CORL-863

* Add load more button to expert search list

CORL-863

* Update experts query to match react upgrades

CORL-863

* Move the Q&A config to its own section under stream config

Create enable and disable Q&A mutations/button toggle.

CORL-863

* Fix alignment and layout of expert list items

CORL-863

* Define translations and update tests

CORL-863

* Use official copy for Q&A config

CORL-863

* Show expert badges on comments when Q&A is enabled

CORL-856

* Update mutation responses and tests due to added expert fields

CORL-856

* Use EXPERT user tags to denote expert users

Removes the need for viewerIsExpert and
authorIsExpert loader/resolvers on Stories
and Comments respectively.

CORL-856

* Show an unanswered comment stream when Q&A is enabled

CORL-879

* Do not visually show the unanswered tag

CORL-879

* [CORL-859] Convert Featured stream into Answered for Q&A (#2842)

* Create preliminary schema changes for Q&A

Adds a mode and expert User onto the StorySettings.
Adds a mode selection drop down on a story's
Configure tab.

CORL-863

* Allow multiple experts, remove form elements from search

Makes the previous expert user on a Q&A story now
an array of users who can be assigned.

Converts the previous form based search that was
pulled from the admin community area into a set
of events built on callbacks.

CORL-863

* Create addExpertToStory mutation

CORL-863

* Create removeExpertFromStory mutation

CORL-863

* Conditionally show the the expert selection options

CORL-863

* Create a dropdown search control for Q&A experts

CORL-863

* Fixing up tests to match new QA stream options

Adds a few localization fixes to make sure tests
pass.
Updates existing snapshots.

CORL-863

* Add load more button to expert search list

CORL-863

* Update experts query to match react upgrades

CORL-863

* Move the Q&A config to its own section under stream config

Create enable and disable Q&A mutations/button toggle.

CORL-863

* Fix alignment and layout of expert list items

CORL-863

* Define translations and update tests

CORL-863

* Use official copy for Q&A config

CORL-863

* Show expert badges on comments when Q&A is enabled

CORL-856

* Update mutation responses and tests due to added expert fields

CORL-856

* Use EXPERT user tags to denote expert users

Removes the need for viewerIsExpert and
authorIsExpert loader/resolvers on Stories
and Comments respectively.

CORL-856

* Show an unanswered comment stream when Q&A is enabled

CORL-879

* Create preliminary schema changes for Q&A

Adds a mode and expert User onto the StorySettings.
Adds a mode selection drop down on a story's
Configure tab.

CORL-863

* Do not visually show the unanswered tag

CORL-879

* Allow multiple experts, remove form elements from search

Makes the previous expert user on a Q&A story now
an array of users who can be assigned.

Converts the previous form based search that was
pulled from the admin community area into a set
of events built on callbacks.

CORL-863

* Create addExpertToStory mutation

CORL-863

* Create removeExpertFromStory mutation

CORL-863

* Create a dropdown search control for Q&A experts

CORL-863

* Fixing up tests to match new QA stream options

Adds a few localization fixes to make sure tests
pass.
Updates existing snapshots.

CORL-863

* Add load more button to expert search list

CORL-863

* Update experts query to match react upgrades

CORL-863

* Move the Q&A config to its own section under stream config

Create enable and disable Q&A mutations/button toggle.

CORL-863

* Fix alignment and layout of expert list items

CORL-863

* Define translations and update tests

CORL-863

* Show expert badges on comments when Q&A is enabled

CORL-856

* Use official copy for Q&A config

CORL-863

* Update mutation responses and tests due to added expert fields

CORL-856

* Use EXPERT user tags to denote expert users

Removes the need for viewerIsExpert and
authorIsExpert loader/resolvers on Stories
and Comments respectively.

CORL-856

* Create the answered stream for Q&A

CORL-859

* Sort the Q&A on Most Voted by default

CORL-859

* Fix type mismatch between post comment form and fragments

CORL-859

* Adding localizations for Q&A tags

* Hide feature flags in Q&A streams

* Allow experts to clear answered questions

Can click a button that shows up in the unanswered
tab under answered questions to refresh the stream,
clearing the answered questions from that tab.

* Show arrow upvote icon when in Q&A mode

Also localized the upvote text so it can
be translated.

* Hide mod/report buttons on answered questions

* Remove unnecessary fragment container

* Remove errant debug console log

* Make story mode required on story settings

* Make remove button outlined, not filled

* Further schema changes around Q&A and experts

Rename add/remove story expert to removeStoryExpert
and addStoryExpert naming.

Replace enableQA and disableQA mutators with single
updateStoryMode mutator.

* Remove story mode from UpdateStorySettings

* Replace inline string val's with enum

* add dependencies to useEffect

* docs cleanup around tags.type index check

* Approve a question when it is answered in Q&A

Approves using the author's id as the moderator

* Add comment around use of TAG on comments edge

* Use tagFilter instead of $elemMatch to filter by tags

* Improve responsive styles for expert list items

* Update copy to "Done" from "Remove answered questions"

* Text styling for no users found text

Styles the expert search list to have proper
text styling when no users are found for the
search keyword.

* Remove duplicate checks around story experts

Not necessary as Mongo does this for us.

* Fix a missed "sort imports" during rebase

* Refactor Q&A moderation phases for clarity

Simplify logic and update comments.
Rename: "answered" -> "tagExpertAnswers"
Rename: "unanswered" -> "tagUnansweredQuestions"

* Remove username & email from add expert mutation

* Format expert list emails with Localized

* Break out no comments logic into fragment

* Remove ref handling from expert search field

Use value assignment on TextField instead.

* Replace Box with Flex and CSS

* Show Q&A tooltip on Answered tab

Co-authored-by: Kim Gardner <kgardnr@gmail.com>
This commit is contained in:
Nick Funk
2020-02-25 20:10:25 -05:00
committed by GitHub
co-authored by Kim Gardner
parent 184f145c4a
commit 1a3401710f
100 changed files with 3761 additions and 114 deletions
+6 -1
View File
@@ -200,12 +200,17 @@ export default (ctx: Context) => ({
"tags.type": tag,
},
}).then(primeCommentsFromConnection(ctx)),
forStory: (storyID: string, { first, orderBy, after }: StoryToCommentsArgs) =>
forStory: (
storyID: string,
{ first, orderBy, after, tag }: StoryToCommentsArgs
) =>
retrieveCommentStoryConnection(ctx.mongo, ctx.tenant.id, storyID, {
first: defaultTo(first, 10),
orderBy: defaultTo(orderBy, GQLCOMMENT_SORT.CREATED_AT_DESC),
after,
filter: {
// Ensure we filter by the requested tag
...tagFilter(tag),
// Only get Comments that are top level. If the client wants to load
// another layer, they can request another nested connection.
parentID: null,
+12
View File
@@ -5,24 +5,30 @@ import GraphContext from "coral-server/graph/context";
import { mapFieldsetToErrorCodes } from "coral-server/graph/errors";
import { Story } from "coral-server/models/story";
import {
addStoryExpert,
close,
create,
merge,
open,
remove,
removeStoryExpert,
update,
updateSettings,
updateStoryMode,
} from "coral-server/services/stories";
import { scrape } from "coral-server/services/stories/scraper";
import {
GQLAddExpertInput,
GQLCloseStoryInput,
GQLCreateStoryInput,
GQLMergeStoriesInput,
GQLOpenStoryInput,
GQLRemoveExpertInput,
GQLRemoveStoryInput,
GQLScrapeStoryInput,
GQLUpdateStoryInput,
GQLUpdateStoryModeInput,
GQLUpdateStorySettingsInput,
} from "coral-server/graph/schema/__generated__/types";
@@ -70,4 +76,10 @@ export const Stories = (ctx: GraphContext) => ({
remove(ctx.mongo, ctx.tenant, input.id, input.includeComments),
scrape: async (input: GQLScrapeStoryInput): Promise<Readonly<Story> | null> =>
scrape(ctx.mongo, ctx.config, ctx.tenant.id, input.id),
updateStoryMode: async (input: GQLUpdateStoryModeInput) =>
updateStoryMode(ctx.mongo, ctx.tenant, input.storyID, input.mode),
addStoryExpert: async (input: GQLAddExpertInput) =>
addStoryExpert(ctx.mongo, ctx.tenant, input.storyID, input.userID),
removeStoryExpert: async (input: GQLRemoveExpertInput) =>
removeStoryExpert(ctx.mongo, ctx.tenant, input.storyID, input.userID),
});
@@ -256,6 +256,18 @@ export const Mutation: Required<GQLMutationTypeResolver<void>> = {
site: await ctx.mutators.Sites.update(input),
clientMutationId: input.clientMutationId,
}),
updateStoryMode: async (source, { input }, ctx) => ({
story: await ctx.mutators.Stories.updateStoryMode(input),
clientMutationId: input.clientMutationId,
}),
addStoryExpert: async (source, { input }, ctx) => ({
story: await ctx.mutators.Stories.addStoryExpert(input),
clientMutationId: input.clientMutationId,
}),
removeStoryExpert: async (source, { input }, ctx) => ({
story: await ctx.mutators.Stories.removeStoryExpert(input),
clientMutationId: input.clientMutationId,
}),
createWebhookEndpoint: async (
source,
{ input: { clientMutationId, ...input } },
@@ -1,6 +1,9 @@
import * as story from "coral-server/models/story";
import { GQLStorySettingsTypeResolver } from "../schema/__generated__/types";
import {
GQLSTORY_MODE,
GQLStorySettingsTypeResolver,
} from "../schema/__generated__/types";
export const StorySettings: GQLStorySettingsTypeResolver<
story.StorySettings
@@ -18,4 +21,18 @@ export const StorySettings: GQLStorySettingsTypeResolver<
enabled: false,
};
},
mode: s => {
if (s.mode) {
return s.mode;
}
return GQLSTORY_MODE.COMMENTS;
},
experts: (s, input, ctx) => {
if (s.expertIDs) {
return ctx.loaders.Users.user.loadMany(s.expertIDs);
}
return [];
},
};
+167
View File
@@ -372,6 +372,18 @@ type WordList {
suspect: [String!]!
}
enum STORY_MODE {
"""
Coments is when a story is used for general commenting.
"""
COMMENTS
"""
QA is used for when the story is in Q&A mode.
"""
QA
}
################################################################################
## Moderation
################################################################################
@@ -2345,6 +2357,18 @@ enum TAG {
FEATURED is used when a Comment is marked as such by a staff member.
"""
FEATURED
"""
EXPERT is used when an a Comment is written by a User that is assigned as
an expert on a story.
"""
EXPERT
"""
UNANSWERED is used when a Comment is written by a User and is unanswered
by an expert for that Comment's story.
"""
UNANSWERED
}
"""
@@ -2376,6 +2400,11 @@ type CommentTagCounts {
FEATURED is the count of Comment's with the FEATURED tag.
"""
FEATURED: Int!
"""
UNANSWERED is the count of Comment's with the UNANSWERED tag.
"""
UNANSWERED: Int!
}
"""
@@ -2720,6 +2749,19 @@ type StorySettings {
messageBox stores settings related to the Story Message Box.
"""
messageBox: StoryMessageBox!
"""
mode is whether the story stream is in commenting or Q&A mode.
This will determine the appearance of the stream and how it functions.
"""
mode: STORY_MODE!
"""
experts are used during Q&A mode to assign users to answer questions
on a Q&A stream. It is an optional parameter and is only used when
the story stream is in Q&A mode.
"""
experts: [User!]!
}
"""
@@ -2783,6 +2825,14 @@ type Story {
first: Int = 20 @constraint(max: 50)
orderBy: COMMENT_SORT = CREATED_AT_DESC
after: Cursor
"""
This is a workaround to allow filtering for current Q&A
functionality. This is used to filter on UNANSWERED to
populate its corresponding Unanswered questions tab. In
the future, we want a dedicated edge for unansweredComments.
"""
tag: TAG
): CommentsConnection!
"""
@@ -5827,6 +5877,97 @@ type DisableFeatureFlagPayload {
flags: [FEATURE_FLAG!]!
}
#########################
# Add / Remove Expert
#########################
input AddExpertInput {
"""
clientMutationId is required for Relay support.
"""
clientMutationId: String!
"""
storyID is the story to add the expert to.
"""
storyID: ID!
"""
userID is the user to add as an expert to the story.
"""
userID: ID!
}
type AddExpertPayload {
"""
clientMutationId is required for Relay support.
"""
clientMutationId: String!
"""
story is the resultant story the expert was added to.
"""
story: Story!
}
input RemoveExpertInput {
"""
clientMutationId is required for Relay support.
"""
clientMutationId: String!
"""
storyID is the story to remove the expert from.
"""
storyID: ID!
"""
userID is the user to remove as an expert from the story.
"""
userID: ID!
}
type RemoveExpertPayload {
"""
clientMutationId is required for Relay support.
"""
clientMutationId: String!
"""
story is the resultant story the expert was removed from.
"""
story: Story!
}
input UpdateStoryModeInput {
"""
storyID is the story id to enable Q&A on.
"""
storyID: ID!
"""
mode is the mode to set the story to.
"""
mode: STORY_MODE!
"""
clientMutationId is required for Relay support.
"""
clientMutationId: String!
}
type UpdateStoryModePayload {
"""
story is the resultant story that Q&A was enabled on.
"""
story: Story!
"""
clientMutationId is required for Relay support.
"""
clientMutationId: String!
}
##################
## Mutation
##################
@@ -6199,10 +6340,16 @@ type Mutation {
input: DisableFeatureFlagInput!
): DisableFeatureFlagPayload! @auth(roles: [ADMIN])
"""
createAnnouncement creates a global announcement.
"""
createAnnouncement(
input: CreateAnnouncementInput!
): CreateAnnouncementPayload! @auth(roles: [ADMIN])
"""
deleteAnnouncement removes a global announcement.
"""
deleteAnnouncement(
input: DeleteAnnouncementInput!
): DeleteAnnouncementPayload! @auth(roles: [ADMIN])
@@ -6253,6 +6400,26 @@ type Mutation {
rotateWebhookEndpointSecret(
input: RotateWebhookEndpointSecretInput!
): RotateWebhookEndpointSecretPayload! @auth(roles: [ADMIN])
"""
updateStoryMode will set the story mode.
"""
updateStoryMode(input: UpdateStoryModeInput!): UpdateStoryModePayload!
@auth(roles: [ADMIN, MODERATOR])
"""
addStoryExpert adds an expert to a story.
"""
addStoryExpert(
input: AddExpertInput!
): AddExpertPayload! @auth(roles: [ADMIN, MODERATOR])
"""
removeStoryExpert removes an expert from a story.
"""
removeStoryExpert(
input: RemoveExpertInput!
): RemoveExpertPayload! @auth(roles: [ADMIN, MODERATOR])
}
##################