Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reopening closed discussions #74

Merged
merged 7 commits into from
Dec 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/handle-stale-discussions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ jobs:
steps:
- name: Stale discussions action
uses: aws-github-ops/handle-stale-discussions@v1
with:
# This will disable auto closing discussions
close-stale-as-answered: false
close-stale-as-outdated: false
close-answered-discussions: false
close-locked-discussions: false
env:
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}

3 changes: 3 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,6 @@ inputs:
page-size:
description: 'Page size count for discussions to be loaded per page'
required: false
open-discussion-instructions-text:
description: 'Comment to post when a discussion is opened'
required: false
18 changes: 17 additions & 1 deletion dist/GithubDiscussionClient.js

Large diffs are not rendered by default.

13 changes: 11 additions & 2 deletions dist/generated/graphql.js

Large diffs are not rendered by default.

123 changes: 86 additions & 37 deletions dist/index.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions dist/types/GithubDiscussionClient.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@ export declare class GithubDiscussionClient {
markDiscussionCommentAsAnswer(commentId: string): Promise<void>;
addAttentionLabelToDiscussion(discussionId: string): Promise<void>;
updateDiscussionComment(commentId: string, body: string): Promise<void>;
reopenDiscussion(discussionId: string): Promise<void>;
}
14 changes: 14 additions & 0 deletions dist/types/generated/graphql.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39132,6 +39132,7 @@ export declare const AddLabelToDiscussion: import("graphql").DocumentNode;
export declare const CloseDiscussionAsOutdated: import("graphql").DocumentNode;
export declare const CloseDiscussionAsResolved: import("graphql").DocumentNode;
export declare const MarkDiscussionCommentAsAnswer: import("graphql").DocumentNode;
export declare const ReopenDiscussion: import("graphql").DocumentNode;
export declare const UpdateDiscussionComment: import("graphql").DocumentNode;
export declare const GetAnswerableDiscussionId: import("graphql").DocumentNode;
export declare const GetCommentMetaData: import("graphql").DocumentNode;
Expand Down Expand Up @@ -39217,6 +39218,19 @@ export type MarkDiscussionCommentAsAnswerMutation = {
clientMutationId?: string | null;
} | null;
};
export type ReopenDiscussionMutationVariables = Exact<{
discussionId: Scalars['ID']['input'];
}>;
export type ReopenDiscussionMutation = {
__typename?: 'Mutation';
reopenDiscussion?: {
__typename?: 'ReopenDiscussionPayload';
discussion?: {
__typename?: 'Discussion';
id: string;
} | null;
} | null;
};
export type UpdateDiscussionCommentMutationVariables = Exact<{
commentId: Scalars['ID']['input'];
body: Scalars['String']['input'];
Expand Down
21 changes: 20 additions & 1 deletion src/GithubDiscussionClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as core from '@actions/core';
import * as github from '@actions/github';
import fetch from 'cross-fetch';
import { DiscussionConnection } from "@octokit/graphql-schema";
import { GetDiscussionCountQuery, GetDiscussionCountQueryVariables, GetDiscussionCount, GetDiscussionDataQuery, GetDiscussionDataQueryVariables, GetDiscussionData, GetAnswerableDiscussionIdQuery, GetAnswerableDiscussionIdQueryVariables, GetAnswerableDiscussionId, GetLabelIdQuery, GetLabelId, CloseDiscussionAsResolvedMutation, CloseDiscussionAsResolved, CloseDiscussionAsOutdatedMutation, CloseDiscussionAsOutdated, AddDiscussionCommentMutation, AddDiscussionComment, MarkDiscussionCommentAsAnswerMutation, MarkDiscussionCommentAsAnswer, AddLabelToDiscussionMutation, AddLabelToDiscussion, UpdateDiscussionCommentMutation, UpdateDiscussionComment, GetDiscussionCommentCountQuery, GetDiscussionCommentCount, DiscussionCommentConnection, GetCommentMetaDataQuery, GetCommentMetaDataQueryVariables, GetCommentMetaData, CloseDiscussionAsResolvedMutationVariables, CloseDiscussionAsOutdatedMutationVariables, AddDiscussionCommentMutationVariables, MarkDiscussionCommentAsAnswerMutationVariables, AddLabelToDiscussionMutationVariables, UpdateDiscussionCommentMutationVariables, GetDiscussionCommentCountQueryVariables, AddInstructionTextReplyMutation, AddInstructionTextReplyMutationVariables, AddInstructionTextReply } from "./generated/graphql";
import { GetDiscussionCountQuery, GetDiscussionCountQueryVariables, GetDiscussionCount, GetDiscussionDataQuery, GetDiscussionDataQueryVariables, GetDiscussionData, GetAnswerableDiscussionIdQuery, GetAnswerableDiscussionIdQueryVariables, GetAnswerableDiscussionId, GetLabelIdQuery, GetLabelId, CloseDiscussionAsResolvedMutation, CloseDiscussionAsResolved, CloseDiscussionAsOutdatedMutation, CloseDiscussionAsOutdated, AddDiscussionCommentMutation, AddDiscussionComment, MarkDiscussionCommentAsAnswerMutation, MarkDiscussionCommentAsAnswer, AddLabelToDiscussionMutation, AddLabelToDiscussion, UpdateDiscussionCommentMutation, UpdateDiscussionComment, GetDiscussionCommentCountQuery, GetDiscussionCommentCount, DiscussionCommentConnection, GetCommentMetaDataQuery, GetCommentMetaDataQueryVariables, GetCommentMetaData, CloseDiscussionAsResolvedMutationVariables, CloseDiscussionAsOutdatedMutationVariables, AddDiscussionCommentMutationVariables, MarkDiscussionCommentAsAnswerMutationVariables, AddLabelToDiscussionMutationVariables, UpdateDiscussionCommentMutationVariables, GetDiscussionCommentCountQueryVariables, AddInstructionTextReplyMutation, AddInstructionTextReplyMutationVariables, AddInstructionTextReply, ReopenDiscussionMutation, ReopenDiscussionMutationVariables, ReopenDiscussion } from "./generated/graphql";

export class GithubDiscussionClient {
private _githubClient: ApolloClient<NormalizedCacheObject>;
Expand Down Expand Up @@ -267,4 +267,23 @@ export class GithubDiscussionClient {
throw new Error(`Error updating discussion comment ${commentId}: ${result.errors}`);
}
}

public async reopenDiscussion(discussionId: string) {
try
{
const result = await this.githubClient.mutate<ReopenDiscussionMutation,ReopenDiscussionMutationVariables>({
mutation: ReopenDiscussion,
variables: {
discussionId
}
});

if (result.errors) {
throw new Error(`Error in reopening discussion ${discussionId}: ${result.errors}`);
}
}
catch(error){
core.warning(`Error in reopening discussion ${discussionId}: ${error}`);
}
}
}
16 changes: 16 additions & 0 deletions src/generated/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41413,6 +41413,15 @@ export const MarkDiscussionCommentAsAnswer = gql`
}
}
`;
export const ReopenDiscussion = gql`
mutation ReopenDiscussion($discussionId: ID!) {
reopenDiscussion(input: {discussionId: $discussionId}) {
discussion {
id
}
}
}
`;
export const UpdateDiscussionComment = gql`
mutation UpdateDiscussionComment($commentId: ID!, $body: String!) {
updateDiscussionComment(input: {commentId: $commentId, body: $body}) {
Expand Down Expand Up @@ -41602,6 +41611,13 @@ export type MarkDiscussionCommentAsAnswerMutationVariables = Exact<{

export type MarkDiscussionCommentAsAnswerMutation = { __typename?: 'Mutation', markDiscussionCommentAsAnswer?: { __typename?: 'MarkDiscussionCommentAsAnswerPayload', clientMutationId?: string | null } | null };

export type ReopenDiscussionMutationVariables = Exact<{
discussionId: Scalars['ID']['input'];
}>;


export type ReopenDiscussionMutation = { __typename?: 'Mutation', reopenDiscussion?: { __typename?: 'ReopenDiscussionPayload', discussion?: { __typename?: 'Discussion', id: string } | null } | null };

export type UpdateDiscussionCommentMutationVariables = Exact<{
commentId: Scalars['ID']['input'];
body: Scalars['String']['input'];
Expand Down
88 changes: 58 additions & 30 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as core from '@actions/core';
import * as github from '@actions/github';
import { GithubDiscussionClient } from "./GithubDiscussionClient";
import { containsKeyword, containsNegativeReaction, containsPositiveReaction, exceedsDaysUntilStale, hasReplies, triggeredByNewComment, hasNonBotReply } from './util';
import { DiscussionCommentEdge } from './generated/graphql';
import { DiscussionCommentEdge, MarkDiscussionCommentAsAnswer } from './generated/graphql';

const PAGE_SIZE = parseInt(core.getInput('page-size', { required: false })) || 50;
const GITHUB_BOT = core.getInput('github-bot', { required: false}) || 'github-actions';
Expand All @@ -18,20 +18,22 @@ const CLOSE_STALE_AS_ANSWERED = closeStaleAsAnsweredInput.toLowerCase() === 'fal
const CLOSE_FOR_STALENESS_RESPONSE_TEXT = core.getInput('stale-response-text', { required: false })
|| 'Closing the discussion for staleness. Please open a new discussion if you have further concerns.';
const INSTRUCTIONS_TEXT = core.getInput('instructions-response-text', { required: false })
|| 'Hello! A team member has marked the above comment as the likely answer to this discussion thread. '
+ '\n \n * If you agree, please upvote that comment, or click on Mark as answer. I will automatically mark the comment as the answer next time I check. '
+ '\n \n * If this answer does not help you, please downvote the answer instead and let us know why it was not helpful. '
|| 'Hello! A team member has suggested the above comment as the likely answer to this discussion thread. '
+ '\n \n * If you agree, please upvote that comment, or click on Mark as answer. I will automatically mark the discussion as answered with upvoted comment, next time I check. '
+ '\n \n * If this answer does not help you, please downvote the answer instead and let us know why it was not helpful. '
+ 'I will add a label to this discussion to gain attention from the team.';
const OPEN_DISCUSSION_INSTRUCTION_TEXT = core.getInput('open-discussion-instructions-text', { required: false })
|| 'Hello! Reopening this discussion to make it searchable. ';

async function main() {
const githubClient = new GithubDiscussionClient();
await githubClient.initializeAttentionLabelId();
if (triggeredByNewComment()) {
if (github.context.payload.comment?.body.indexOf(PROPOSED_ANSWER_KEYWORD) >= 0) {
core.info('Comment created with proposed answer keyword. Adding instuctions reply to comment');
core.info('Proposed keyword found. Adding Bot Instuctions reply!!');
githubClient.addInstructionTextReply(INSTRUCTIONS_TEXT, github.context.payload.discussion!.node_id, github.context.payload.comment!.node_id);
} else {
core.info('Comment created without proposed answer keyword. No action needed');
core.info('Comment created without proposed answer keyword. No action needed!!');
}
} else {
await processDiscussions(githubClient);
Expand All @@ -41,7 +43,7 @@ async function main() {
export async function processDiscussions(githubClient: GithubDiscussionClient) {
const discussionCategoryIDList: string[] = await githubClient.getAnswerableDiscussionCategoryIDs();
if (discussionCategoryIDList.length === 0) {
core.info('No answerable discussions found. Exiting.');
core.info('No answerable discussions found. Exiting!!');
return;
}

Expand All @@ -57,23 +59,23 @@ export async function processDiscussions(githubClient: GithubDiscussionClient) {
for (const discussion of discussions.edges!) {
var discussionId = discussion?.node?.id ? discussion?.node?.id : "";
var discussionNum = discussion?.node?.number ? discussion.node.number : 0;
core.debug(`Processing discussionId: ${discussionId} with number: ${discussionNum} and bodyText: ${discussion?.node?.bodyText}`);
core.info(`Processing discussionId: ${discussionId}, discussion number: ${discussionNum} and bodyText: ${discussion?.node?.bodyText}`);
if (discussionId === "" || discussionNum === 0) {
core.warning(`Can not proceed checking discussion, discussionId is null!`);
core.warning(`Current discussion ID is NULL. Cannot proceed!!`);
continue;
}
else if (discussion?.node?.closed) {
core.debug(`Discussion ${discussionId} is closed, so no action needed.`);
continue;
core.info(`Reopening closed discussion: ${discussionId}`);
reopenClosedDiscussion(discussionId, githubClient);
}
else if (discussion?.node?.locked && CLOSE_LOCKED_DISCUSSIONS) {
core.info(`Discussion ${discussionId} is locked, closing it as resolved`);
githubClient.closeDiscussionAsResolved(discussionId);
core.info(`Discussion ${discussionId} is locked, keeping it open to make it searchable`);
//githubClient.closeDiscussionAsResolved(discussionId);
continue;
}
else if (discussion?.node?.answer != null && CLOSE_ANSWERED_DISCUSSIONS) {
core.info(`Discussion ${discussionId} is already answered, so closing it as resolved.`);
githubClient.closeDiscussionAsResolved(discussionId);
core.info(`Discussion ${discussionId} is already answered, so no action needed!!`);
//githubClient.closeDiscussionAsResolved(discussionId);
continue;
}
else {
Expand All @@ -93,54 +95,66 @@ export async function processComments(discussion: octokit.DiscussionEdge, github
if (commentCount !== 0) {
for (const comment of comments.edges!) {
const commentId = comment?.node?.id;
core.debug(`Processing comment ${commentId} with bodytext: ${comment?.node?.bodyText}`);
core.info(`Processing comment ${commentId} with bodytext: ${comment?.node?.bodyText}`);
if (!comment?.node?.bodyText || !comment.node.id) {
core.warning(`Comment body or id is null in discussion ${discussionId}, skipping comment!`);
core.warning(`Comment body/Id is Null in discussion ${discussionId}, skipping comment!`);
continue;
}
if (!containsKeyword(comment!, PROPOSED_ANSWER_KEYWORD)) {
core.debug(`No answer proposed on comment ${commentId}, no action needed!`);
core.info(`No answer proposed on comment ${commentId}, No action needed!!`);
continue;
}
else {
//core.info("debugging the code for getting reactions");
if (containsNegativeReaction(comment)) {
core.info(`Negative reaction received. Adding attention label to discussion ${discussionId} to receive further attention from a repository maintainer`);
core.info(`Negative reaction received. Adding attention label to discussion ${discussionId} `);
githubClient.addAttentionLabelToDiscussion(discussionId);
}
else if (containsPositiveReaction(comment)) {
core.info(`Positive reaction received. Marking discussion ${discussionId} as answered, and editing answer to remove proposed answer keyword`);
closeAndMarkAsAnswered(comment, discussionId, githubClient);
core.info(`Positive reaction received. Marking discussion ${discussionId} as answered, removing keyword`);
markDiscussionCommentAsAnswer(comment, discussionId, githubClient);
}
else if (!hasReplies(comment)) {
core.info(`Since this has no reply, adding instructions reply to comment ${commentId} in discussion ${discussionId}`);
core.info(`Since this has no reply, adding Bot Instructions text to comment ${commentId} in discussion ${discussionId}`);
githubClient.addInstructionTextReply(INSTRUCTIONS_TEXT, discussionId, commentId!);
}
else if (hasNonBotReply(comment, GITHUB_BOT)) {
core.info(`Discussion ${discussionId} has a reply, but not an instructions reply. Adding attention label`);
core.info(`Discussion ${discussionId} has Non-Bot Reply. Adding attention label`);
githubClient.addAttentionLabelToDiscussion(discussionId);
}
else if (exceedsDaysUntilStale(comment, DAYS_UNTIL_STALE)) {
if (CLOSE_STALE_AS_ANSWERED) {
core.info(`No one has responded or provided a reaction, closing discussion ${discussionId} as answered`);
closeAndMarkAsAnswered(comment, discussionId, githubClient);
} else {
core.info(`No one has responded or provided a reaction, closing discussion ${discussionId} with a comment`);
closeDiscussionForStaleness(discussionId, githubClient);
if (!CLOSE_STALE_AS_ANSWERED) {
core.info(`No one has responded or provided a reaction, marking discussion ${discussionId} as answered`);
markDiscussionCommentAsAnswer(comment, discussionId, githubClient);
//closeAndMarkAsAnswered(comment, discussionId, githubClient);
}
else {
core.info(`No action needed for discussion ${discussionId} !!`);
//closeDiscussionForStaleness(discussionId, githubClient);
}
}
else
{
core.info(`No action needed for discussion ${discussionId} as nothing is found`);
}
}
};
}
else {
core.debug(`No comments found for discussion ${discussionId}, No action needed!`);
core.debug(`No comments found for discussion ${discussionId}, No action needed!!`);
}
}

/* This function is no longer used since we are marking the discussion as answered instead of closing it

function closeDiscussionForStaleness(discussionId: string, githubClient: GithubDiscussionClient) {
githubClient.addCommentToDiscussion(discussionId, CLOSE_FOR_STALENESS_RESPONSE_TEXT);
githubClient.closeDiscussionAsOutdated(discussionId);
}
*/

//This functioon is no longer used since we are marking the discussion as answered instead of closing it
/*
function closeAndMarkAsAnswered(comment: DiscussionCommentEdge, discussionId: string, githubClient: GithubDiscussionClient) {
const bodyText = comment?.node?.bodyText!;
const commentId = comment?.node?.id!;
Expand All @@ -149,5 +163,19 @@ function closeAndMarkAsAnswered(comment: DiscussionCommentEdge, discussionId: st
githubClient.markDiscussionCommentAsAnswer(commentId);
githubClient.closeDiscussionAsResolved(discussionId);
}
*/

function markDiscussionCommentAsAnswer(comment: DiscussionCommentEdge, discussionId: string, githubClient: GithubDiscussionClient) {
const bodyText = comment?.node?.bodyText!;
const commentId = comment?.node?.id!;
const updatedAnswerText = bodyText.replace(PROPOSED_ANSWER_KEYWORD, 'Answer: ');
githubClient.updateDiscussionComment(commentId, updatedAnswerText);
githubClient.markDiscussionCommentAsAnswer(commentId);
}

function reopenClosedDiscussion(discussionId: string, githubClient: GithubDiscussionClient) {
githubClient.addCommentToDiscussion(discussionId, OPEN_DISCUSSION_INSTRUCTION_TEXT);
githubClient.reopenDiscussion(discussionId);
}

main();
7 changes: 7 additions & 0 deletions src/mutations/reopen-discussion.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
mutation ReopenDiscussion($discussionId: ID!){
reopenDiscussion(input: {discussionId: $discussionId}) {
discussion {
id
}
}
}