Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 16 additions & 4 deletions github/commands/GithubCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
IPersistence,
IRead,
} from "@rocket.chat/apps-engine/definition/accessors";
import { ProcessorsEnum } from "../enum/Processors";
import { GithubApp } from "../GithubApp";
import { initiatorMessage } from "../lib/initiatorMessage";
import { helperMessage } from "../lib/helperMessage";
Expand All @@ -17,15 +16,13 @@ import { pullDetailsModal } from "../modals/pullDetailsModal";
import { authorize } from "../oath2/authentication";
import { SubcommandEnum } from "../enum/Subcommands";
import { getAccessTokenForUser,revokeUserAccessToken } from "../persistance/auth";
import { IUser } from "@rocket.chat/apps-engine/definition/users";
import { removeToken } from "../persistance/auth";
import { getWebhookUrl } from "../helpers/getWebhookURL";
import { githubWebHooks } from "../endpoints/githubEndpoints";
import { sendDirectMessage, sendNotification } from "../lib/message";
import { createSubscription, deleteSubscription, updateSubscription } from "../helpers/githubSDK";
import { Subscription } from "../persistance/subscriptions";
import { ISubscription } from "../definitions/subscription";
import { subsciptionsModal } from "../modals/subscriptionsModal";
import { NewIssueStarterModal } from "../modals/newIssueStarterModal";


export class GithubCommand implements ISlashCommand {
Expand Down Expand Up @@ -100,6 +97,21 @@ export class GithubCommand implements ISlashCommand {
}
break;
}
case SubcommandEnum.NEW_ISSUE :{
let accessToken = await getAccessTokenForUser(read,context.getSender(),this.app.oauth2Config);
if(accessToken && accessToken.token){
const triggerId= context.getTriggerId();
if(triggerId){
const modal = await NewIssueStarterModal({modify,read,persistence,http,slashcommandcontext:context});
await modify.getUiController().openModalView(modal,{triggerId},context.getSender());
}else{
console.log("Inavlid Trigger ID !");
}
}else{
await sendNotification(read,modify,context.getSender(),room,"Login to subscribe to repository events ! `/github login`");
}
break;
}
default:{
await helperMessage({room,read, persistence, modify, http});
break;
Expand Down
25 changes: 25 additions & 0 deletions github/enum/Modals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,29 @@ export enum ModalsEnum {
ADD_SUBSCRIPTION_EVENT_INPUT='add-repo-subscription-events',
SUBSCRIPTION_REFRESH_LABEL="Refresh",
SUBSCRIPTION_REFRESH_ACTION="subscription-refresh",
NEW_ISSUE_VIEW="new-issue-view",
NEW_ISSUE_TITLE = 'New GitHub Issue',
NEW_ISSUE_ACTION = "new-issue-action",
ISSUE_TITLE_INPUT = "new-issue-title-input",
ISSUE_TITLE_LABEL = "New Issue Title",
ISSUE_TITLE_ACTION = "new-issue-title-action",
ISSUE_TITLE_PLACEHOLDER = "[Bug/Feature] Issue Title",
ISSUE_BODY_INPUT = "issue-body-input",
ISSUE_BODY_INPUT_LABEL = "New Issue Text",
ISSUE_BODY_INPUT_ACTION = "new-issue-body-action",
ISSUE_BODY_INPUT_PLACEHOLDER = "Leave a comment",
ISSUE_LABELS_INPUT = "issue-labels-input",
ISSUE_LABELS_INPUT_LABEL = "Add Issue Labels",
ISSUE_LABELS_INPUT_ACTION = "issue-labels-input-action",
ISSUE_LABELS_INPUT_PLACEHOLDER = "ui/ux bug feature-request",
ISSUE_ASSIGNEES_INPUT = "issue-assignees-input",
ISSUE_ASSIGNEES_INPUT_LABEL = "Add Assignees",
ISSUE_ASSIGNEES_INPUT_ACTION = "issue-assignee-input-action",
ISSUE_ASSIGNEES_INPUT_PLACEHOLDER = "samad-yar-khan RonLek Sing-Li",
NEW_ISSUE_STARTER_VIEW = "new-issue-starter-view",
NEW_ISSUE_STARTER__ACTION= "new-issue-starter-action",
ISSUE_TEMPLATE_SELECTION_VIEW= "issue-template-selection-view",
ISSUE_TEMPLATE_SELECTION_ACTION = "issue-template-selection-action",
ISSUE_TEMPLATE_SELECTION_LABEL = "Select",
BLANK_GITHUB_TEMPLATE = "blank-github-app-template"
}
3 changes: 2 additions & 1 deletion github/enum/Subcommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ export enum SubcommandEnum {
HELP = 'help',
SUBSCRIBE = 'subscribe',
UNSUBSCRIBE = 'unsubscribe',
TEST = 'test'
TEST = 'test',
NEW_ISSUE = 'issue'
}
59 changes: 58 additions & 1 deletion github/handlers/ExecuteBlockActionHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,16 @@ import {
} from "@rocket.chat/apps-engine/definition/uikit";
import { AddSubscriptionModal } from "../modals/addSubscriptionsModal";
import { deleteSubsciptionsModal } from "../modals/deleteSubscriptions";
import { deleteSubscription, updateSubscription } from "../helpers/githubSDK";
import { deleteSubscription, updateSubscription, getIssueTemplateCode } from "../helpers/githubSDK";
import { Subscription } from "../persistance/subscriptions";
import { getAccessTokenForUser } from "../persistance/auth";
import { GithubApp } from "../GithubApp";
import { IAuthData } from "@rocket.chat/apps-engine/definition/oauth2/IOAuth2";
import { storeInteractionRoomData, getInteractionRoomData } from "../persistance/roomInteraction";
import { sendNotification } from "../lib/message";
import { subsciptionsModal } from "../modals/subscriptionsModal";
import { NewIssueModal } from "../modals/newIssueModal";

export class ExecuteBlockActionHandler {
constructor(
private readonly app: GithubApp,
Expand Down Expand Up @@ -180,6 +182,61 @@ export class ExecuteBlockActionHandler {
await this.modify.getUiController().updateModalView(modal, { triggerId: context.getInteractionData().triggerId }, context.getInteractionData().user);
break;
}
case ModalsEnum.ISSUE_TEMPLATE_SELECTION_ACTION:{
let { user } = await context.getInteractionData();
let accessToken = await getAccessTokenForUser(this.read, user, this.app.oauth2Config) as IAuthData;
let value: string = context.getInteractionData().value as string;
let actionDetailsArray = value?.trim()?.split(" ");
if(accessToken && actionDetailsArray?.length == 2){

if(actionDetailsArray[1] !== ModalsEnum.BLANK_GITHUB_TEMPLATE){

let templateResponse = await getIssueTemplateCode(this.http,actionDetailsArray[1],accessToken.token);
// console.log(templateResponse);
let data = {};
if(templateResponse?.template){
data = {
template : templateResponse.template,
repository:actionDetailsArray[0]
};
}else{
data = {
template : "",
repository:actionDetailsArray[0]
};
}
const newIssueModal = await NewIssueModal({
data,
modify: this.modify,
read: this.read,
persistence: this.persistence,
http: this.http,
uikitcontext: context,
});
return context
.getInteractionResponder()
.openModalViewResponse(newIssueModal);

}else{
let data = {
repository:actionDetailsArray[0]
};
const newIssueModal = await NewIssueModal({
data,
modify: this.modify,
read: this.read,
persistence: this.persistence,
http: this.http,
uikitcontext: context,
});
return context
.getInteractionResponder()
.openModalViewResponse(newIssueModal);
}
}

break;
}
}

} catch (error) {
Expand Down
1 change: 1 addition & 0 deletions github/handlers/ExecuteViewClosedHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export class ExecuteViewClosedHandler {
case ModalsEnum.PULL_VIEW ||
ModalsEnum.CODE_VIEW ||
ModalsEnum.ADD_SUBSCRIPTION_VIEW ||
ModalsEnum.NEW_ISSUE_VIEW ||
ModalsEnum.SUBSCRIPTION_VIEW:
const modal = await pullDetailsModal({
modify: this.modify,
Expand Down
69 changes: 67 additions & 2 deletions github/handlers/ExecuteViewSubmitHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ import { getInteractionRoomData } from '../persistance/roomInteraction';
import { Subscription } from '../persistance/subscriptions';
import { GithubApp } from '../GithubApp';
import { getWebhookUrl } from '../helpers/getWebhookURL';
import { addSubscribedEvents, createSubscription, updateSubscription } from '../helpers/githubSDK';
import { addSubscribedEvents, createSubscription, updateSubscription, createNewIssue, getIssueTemplates } from '../helpers/githubSDK';
import { getAccessTokenForUser } from '../persistance/auth';
import { subsciptionsModal } from '../modals/subscriptionsModal';

import { NewIssueModal } from '../modals/newIssueModal';
import { issueTemplateSelectionModal } from '../modals/issueTemplateSelectionModal';

export class ExecuteViewSubmitHandler {
constructor(
Expand Down Expand Up @@ -101,6 +102,70 @@ export class ExecuteViewSubmitHandler {
}
}
break;
case ModalsEnum.NEW_ISSUE_VIEW: {
const { roomId } = await getInteractionRoomData(this.read.getPersistenceReader(), user.id);
if (roomId) {
let room = await this.read.getRoomReader().getById(roomId) as IRoom;
let repository = view.state?.[ModalsEnum.REPO_NAME_INPUT]?.[ModalsEnum.REPO_NAME_INPUT_ACTION] as string;
let title = view.state?.[ModalsEnum.ISSUE_TITLE_INPUT]?.[ModalsEnum.ISSUE_TITLE_ACTION] as string;
let issueBody = view.state?.[ModalsEnum.ISSUE_BODY_INPUT]?.[ModalsEnum.ISSUE_BODY_INPUT_ACTION];
let issueLabels = view.state?.[ModalsEnum.ISSUE_LABELS_INPUT]?.[ModalsEnum.ISSUE_LABELS_INPUT_ACTION] as string;
issueLabels = issueLabels.trim();
let issueAssignees = view.state?.[ModalsEnum.ISSUE_ASSIGNEES_INPUT]?.[ModalsEnum.ISSUE_ASSIGNEES_INPUT_ACTION] as string;
repository = repository.trim();
title = title.trim();
issueAssignees = issueAssignees.trim();
let issueLabelsArray:Array<string> = issueLabels.split(" ");
let issueAssigneesArray:Array<string> = issueAssignees.split(" ");
if(repository && repository?.length && title && title?.length){
let accessToken = await getAccessTokenForUser(this.read, user, this.app.oauth2Config);
if (!accessToken) {
await sendNotification(this.read, this.modify, user, room, "Login To Github !");
} else {
let reponse = await createNewIssue(this.http,repository,title,issueBody,issueLabelsArray,issueAssigneesArray,accessToken?.token)
if(reponse && reponse?.id){
await sendNotification(this.read,this.modify,user,room,`Created New Issue | [#${reponse.number} ](${reponse.html_url}) *[${reponse.title}](${reponse.html_url})*`)
}else{
await sendNotification(this.read,this.modify,user,room,`Invalid Issue !`);
}
}
}else{
await sendNotification(this.read,this.modify,user,room,`Invalid Issue !`);
}
}
break;
}
case ModalsEnum.NEW_ISSUE_STARTER_VIEW:{
const { roomId } = await getInteractionRoomData(this.read.getPersistenceReader(), user.id);

if (roomId) {
let room = await this.read.getRoomReader().getById(roomId) as IRoom;
let repository = view.state?.[ModalsEnum.REPO_NAME_INPUT]?.[ModalsEnum.REPO_NAME_INPUT_ACTION] as string;
let accessToken = await getAccessTokenForUser(this.read, user, this.app.oauth2Config);
if (!accessToken) {
await sendNotification(this.read, this.modify, user, room, `Login To Github ! -> /github login`);
}else{

repository=repository?.trim();
let response = await getIssueTemplates(this.http,repository,accessToken.token);
if((!response.template_not_found) && response?.templates?.length){
const issueTemplateSelection = await issueTemplateSelectionModal({ data: response, modify: this.modify, read: this.read, persistence: this.persistence, http: this.http, uikitcontext: context });
return context
.getInteractionResponder()
.openModalViewResponse(issueTemplateSelection);
}else{
let data = {
repository: repository
}
const createNewIssue = await NewIssueModal({ data: data, modify: this.modify, read: this.read, persistence: this.persistence, http: this.http, uikitcontext: context });
return context
.getInteractionResponder()
.openModalViewResponse(createNewIssue);
}
}
}
break;
}
default:
break;
}
Expand Down
72 changes: 72 additions & 0 deletions github/helpers/githubSDK.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,3 +153,75 @@ export async function removeSubscribedEvents(
}
);
}

export async function createNewIssue(
http: IHttp,
repoName: string,
issueTitle: string,
issueBody: string,
issueLabels: Array<string>,
issueAssignees : Array<string>,
access_token: string,
) {
return postReqeust(http, access_token, BaseApiHost + repoName + "/issues", {
title: issueTitle,
body: issueBody,
assignees: issueAssignees,
labels: issueLabels
});
}

export async function getIssueTemplates(
http: IHttp,
repoName: string,
access_token: string,
) {
//this does not use the get , post , patch method defined because we dont want to throw an error for an eror response
const response = await http.get(`https://api.github.com/repos/${repoName}/contents/.github/ISSUE_TEMPLATE/`, {
headers: {
Authorization: `token ${access_token}`,
"Content-Type": "application/json",
}
});

// If it isn't a 2xx code, something wrong happened
let repsonseJSON :any = {};
if (!response.statusCode.toString().startsWith("2")) {
repsonseJSON["template_not_found"] = true;
}else{
repsonseJSON = {
templates : JSON.parse(response.content || "{}"),
repository: repoName,
template_not_found : false
}
}
return repsonseJSON;
}

export async function getIssueTemplateCode(
http: IHttp,
templateDownloadUrl: string,
access_token: string,
) {
//this does not use the get , post , patch method defined because we dont want to throw an error for an eror response
const response = await http.get(templateDownloadUrl, {
headers: {
Authorization: `token ${access_token}`,
"Content-Type": "application/json",
}
});

// If it isn't a 2xx code, something wrong happened
let repsonseJSON :any = {};

if (!response.statusCode.toString().startsWith("2")) {
repsonseJSON = {
template : ""
}
}else{
repsonseJSON = {
template : response.content || "",
}
}
return repsonseJSON;
}
8 changes: 5 additions & 3 deletions github/lib/helperMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,11 @@ export async function helperMessage({
5) Get Recent Pull Request of a Repository -> /github Username/RepositoryName pulls
6) Review a Pull Request -> /github Username/RepositoryName pulls pullNumber
7) Login to GitHub -> /github login
8) View/Add/Delete/Update Repository Subscriptions -> /github subscribe
9) Subscribe to all repository events -> /github Username/RepositoryName subscribe
10) Unsubscribe to all repository events -> /github Username/RepositoryName unsubscribe
8) Logout from GitHub -> /github logout
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of having 7 and 8. Is it possible to have a better user experience of ....

a) logging in within the app settings session
b) from time to time, the app will ask the user to re-enter credential for security

The will gave a better "set and forget" experience to most users. Casual users will never be bothered with logging in and logging out.

Copy link
Copy Markdown
Contributor Author

@samad-yar-khan samad-yar-khan Jul 31, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Sing-Li We cannot have the login phase in the apps settings session because the app settings are only visible to the users which have Application Access given by the Server Admin so I don't think it will be convenient.

The logout feature will not be used by most users, its just there for convenience incase anyone wants to remove credentials from the server. The Login will be done by the user through the slash command, but they will only be prompted to login while using some feature which requires auth. Apart from that, using RC Scheduler we will automatically logout the user periodically and they will be sent a direct message by GitHub App bot to login again. It follows the similar workflow as you have mentioned in b), the user does not even have to enter credentials, they will be logged in directly once the click on the login button as the token is regenerated by GitHub. This was suggested by @RonLek to keep the system scalable and not store tokens forever in App Memory. I have added the reasoning behind it in Wiki/Auth.
Let me know if I am missing something, maybe we can improve the login user experience in some way, we can discuss this in the upcoming the weekly meeting 😅

9) View/Add/Delete/Update Repository Subscriptions -> /github subscribe
10) Subscribe to all repository events -> /github Username/RepositoryName subscribe
11) Unsubscribe to all repository events -> /github Username/RepositoryName unsubscribe
12) Add New Issues to GitHub Repository -> /github issue

`;

Expand Down
2 changes: 1 addition & 1 deletion github/lib/issuesListMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export async function issueListMessage({
.getCreator()
.startMessage()
.setText(
`[ #${issue.number} ](${issue.html_url}) *${issue.title}*`
`[ #${issue.number} ](${issue.html_url}) *[${issue.title}](${issue.html_url})*`
);
if (room) {
textSender.setRoom(room);
Expand Down
Loading