diff --git a/.gitignore b/.gitignore index 8e5962e..7c2dc0e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ out -node_modules \ No newline at end of file +node_modules +.vscode \ No newline at end of file diff --git a/package.json b/package.json index 440a2c1..474cc32 100644 --- a/package.json +++ b/package.json @@ -19,15 +19,25 @@ "Other" ], "activationEvents": [ - "onCommand:extension.jiraCommit" + "onCommand:extension.jiraCommit", + "onCommand:extension.jiraTasks", + "onCommand:extension.jiraDoTasks" ], "main": "./out/src/extension", "contributes": { "commands": [ - { - "command": "extension.jiraCommit", - "title": "jira issue add git commit as comment" - } + { + "command": "extension.jiraCommit", + "title": "jira issue add git commit as comment" + }, + { + "command": "extension.jiraTasks", + "title": "jira show tasks" + }, + { + "command": "extension.jiraDoTasks", + "title": "jira do task" + } ] }, "scripts": { @@ -41,6 +51,7 @@ "vscode": "^0.11.0" }, "dependencies": { + "copy-paste": "^1.3.0", "jira-client": "^3.0.2" } -} \ No newline at end of file +} diff --git a/src/extension.ts b/src/extension.ts index 817ec2d..540074a 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -2,82 +2,70 @@ import * as vscode from 'vscode'; import * as cp from 'child_process'; -import * as historyUtil from './historyUtils'; import * as path from 'path'; import * as fs from 'fs'; -let JiraApi = require('jira-client'); +import { JiraClient } from './jira'; +import { QuickPickItem } from 'vscode'; +import { Handler } from './handler'; +const Copy = require('copy-paste'); export function activate(context: vscode.ExtensionContext) { console.log('Congratulations, your extension "jira" is now active!'); - let commits: any[]; - let cwd = vscode.workspace.rootPath; - let issueNumber: string; + let commits: any[], + cwd = vscode.workspace.rootPath, + issueNumber: string, + jiraConfDir: string = path.join(cwd,'.vscode','jira.json'), + client: JiraClient = null; + + fs.stat(jiraConfDir, function (err, stats) { + if (err === null) { + let jiraConfig = require(jiraConfDir); + client = new JiraClient(jiraConfig); + if (!client.server) { + Handler.error('ERROR: can not get jira host at config file'); + } + Handler.setClient(client); + } else { + Handler.error('ERROR: no config file at' + `${cwd}/.vscode/jira.json`); + } + }); let comment = vscode.commands.registerCommand('extension.jiraCommit', () => { + if (!client.isConnection()) { + Handler.error('ERROR: can not connect jira host'); + return; + } + Handler.addCommentForIssue(); + }); + let tasks = vscode.commands.registerCommand("extension.jiraTasks", () => { + if (!client.isConnection()) { + Handler.error('ERROR: can not connect jira host'); + return; + } + Handler.getMyIssues(function(data) { + if (data) { + Copy.copy(`${data.label} ${data.detail}`, function () { + vscode.window.setStatusBarMessage('The issue is copied.', 2000); + }); + } + }); + }); - let jira_conf_Dir = path.join(cwd,'.vscode','jira.json'); - let jira_conf = require(jira_conf_Dir); - // console.log(jira_conf['host']); - - if (jira_conf['host'] !== undefined) { - vscode.window.showInputBox({ placeHolder: 'ID of a Issue' }).then((data) => { - if ((data !== undefined) && (data !== null)) { - issueNumber = data; - - historyUtil.getGitRepositoryPath(vscode.window.activeTextEditor.document.fileName).then((gitRepositoryPath) => { - - historyUtil.gitLog(gitRepositoryPath, []).then((log) => { - commits = log; - let comment: string; - let items = []; - for (let l in log) { - items.push(log[l].message) - } - let options = { matchOnDescription: false, placeHolder: "select Commit" }; - - vscode.window.showQuickPick(items, options).then((data) => { - - comment = historyUtil.parseLog(commits[items.indexOf(data)]); - - console.log(comment); - - let jira = new JiraApi(jira_conf); - - jira.findIssue(issueNumber).then((issue) => { - jira.addComment(issueNumber, comment).then((ret) => { - console.log(ret); - - }).catch((err) => { - console.error(err); - vscode.window.showErrorMessage(`ERROR: comment Issue ${issueNumber}: ${err}`); - }); - }).catch((err) => { - vscode.window.showErrorMessage(`ERROR: Issue ${issueNumber} not found!`); - }); - }) - - }, (err) => { - vscode.window.showErrorMessage('ERROR: ' + err); - }); - - - }, (err) => { - vscode.window.showErrorMessage('ERROR: ' + err); - }); - } - }) - } else { - vscode.window.showErrorMessage('ERROR: no config file at' + `${cwd}/.vscode/jira.json`); + let doTask = vscode.commands.registerCommand("extension.jiraDoTasks", () => { + if (!client.isConnection()) { + Handler.error('ERROR: can not connect jira host'); + return; } + Handler.doMyIssue(); }); - context.subscriptions.push(comment); + context.subscriptions.concat([comment, tasks, doTask]); } // this method is called when your extension is deactivated diff --git a/src/handler.ts b/src/handler.ts new file mode 100644 index 0000000..a7563b2 --- /dev/null +++ b/src/handler.ts @@ -0,0 +1,260 @@ +"use strict"; + +import * as vscode from "vscode"; +import * as historyUtil from "./historyUtils"; + +import { JiraClient } from "./jira"; +import { InputBoxOptions, QuickPickItem, QuickPickOptions } from "vscode"; + +/** + * vscode util class. + * + * @export + * @class Handler + */ +export class Handler { + + /** + * JIRA client instance. + * + * @private + * @static + * @type {JiraClient} + * @memberof Handler + */ + private static client: JiraClient = null; + + /** + * Set a jira client. + * + * @static + * @param {JiraClient} client + * @memberof Handler + */ + public static setClient(client: JiraClient): void { + this.client = client; + } + + /** + * Show error message. + * + * @static + * @param {any} error + * @memberof Handler + */ + public static error (error): void { + vscode.window.showErrorMessage(error); + } + + /** + * vscode input box. + * + * @static + * @param {InputBoxOptions} options + * @returns {Promise} + * @memberof Handler + */ + public static inputBox(options: InputBoxOptions): Promise { + return new Promise((resolve, reject) => { + vscode.window.showInputBox(options).then(data => { + if (data) { + resolve(data); + } else { + reject(); + } + }); + }); + } + + /** + * Add comment for issue. + * + * @static + * @memberof Handler + */ + public static addCommentForIssue (): void { + let self = this; + this.inputBox({ placeHolder: "ID of a Issue" }).then((data) => { + if ((data !== undefined) && (data !== null)) { + let issueNumber = data; + + historyUtil.getGitRepositoryPath(vscode.window.activeTextEditor.document.fileName).then((gitRepositoryPath) => { + + historyUtil.gitLog(gitRepositoryPath, []).then((log) => { + let comment: string; + let items = []; + for (let l in log) { + items.push(log[l].message); + } + let options = { matchOnDescription: false, placeHolder: "select Commit" }; + + vscode.window.showQuickPick(items, options).then((data) => { + + comment = historyUtil.parseLog(log[items.indexOf(data)]); + + console.log(comment); + + self.client.findIssueAndComment(issueNumber, comment, function (code, data) { + if (data instanceof String) { + if (code === 200) { + vscode.window.setStatusBarMessage(data as string); + } else { + this.error(data as string); + } + } else { + console.log(data); + } + }); + }); + }, (err) => { + this.error("ERROR: " + err); + }); + }, (err) => { + this.error("ERROR: " + err); + }); + } + }); + } + + /** + * Get my jira issue tasks. + * + * @static + * @param {(item: QuickPickItem) => void} [callback] + * @memberof Handler + */ + public static getMyIssues (callback?: (item: QuickPickItem) => void): void { + + let self = this; + this.client.listStatuses(function (code, data) { + if (code === 400) { + return self.error(data); + } else { + let items: QuickPickItem[] = [], + options: QuickPickOptions = { + placeHolder: "select task status!", + matchOnDescription: true + }, + statuses: any = null; + + if (data instanceof Array) { + statuses = data[0].statuses; + } else { + statuses = data.statuses; + } + + items.push({ + label: 'All', + description: 'All issue statuses' + }); + for (let status of statuses) { + let item: QuickPickItem = { + label: status.name, + description: status.description, + }; + items.push(item); + } + vscode.window.showQuickPick(items, options).then((data) => { + if (data) { + self.getIssuesByStatus(data, callback); + } + }); + } + }); + } + + /** + * Get issues by status. + * + * @private + * @static + * @param {QuickPickItem} status + * @param {(item: QuickPickItem) => void} [callback] + * @memberof Handler + */ + private static getIssuesByStatus (status: QuickPickItem, callback?: (item: QuickPickItem) => void): void { + let other = status.label !== 'All' ? `and status = ${status.label}`: '', + url = `assignee = ${this.client.username} ${other} order by priority desc`; + + this.client.searchJira(url, function (code, data) { + if (code === 400) { + return console.error(data); + } + let items: QuickPickItem[] = [], + options = { + placeHolder: "select task!", + matchOnDetail: true + }; + + for (let issue of data.issues) { + let item: QuickPickItem = { + label: issue.key, + description: issue.fields.status.name, + detail: issue.fields.summary, + }; + items.push(item); + } + vscode.window.showQuickPick(items, options).then((data) => { + callback(data); + }); + }); + } + + /** + * Do my issure. + * + * @static + * @memberof Handler + */ + public static doMyIssue(): void { + let self = this; + this.getMyIssues(function (item) { + if (!item) return; + + self.client.transitions(item.label, function (code, data) { + if (code === 200) { + let items: QuickPickItem[] = [], + options = { + placeHolder: "select one transition!", + matchOnDetail: true + }; + + for (let trans of data.transitions) { + let item: QuickPickItem = { + label: trans.id, + description: trans.name + }; + items.push(item); + } + vscode.window.showQuickPick(items, options).then((data) => { + if (data) { + self.doIssueTransition(item.label, data); + } + }); + } else { + self.error(data); + } + }); + }); + } + + /** + * Handle issue with a transtion + * + * @private + * @static + * @param {string} issueKey + * @param {QuickPickItem} item + * @memberof Handler + */ + private static doIssueTransition (issueKey: string, item: QuickPickItem): void { + this.client.doTransition(issueKey, item.label, function (code, data) { + if (code !== 200) { + vscode.window.setStatusBarMessage(`Transition failured. ${data}`); + } else { + vscode.window.setStatusBarMessage('Transition successed.', 2000); + } + }); + } + +} \ No newline at end of file diff --git a/src/jira.ts b/src/jira.ts new file mode 100644 index 0000000..32616cd --- /dev/null +++ b/src/jira.ts @@ -0,0 +1,178 @@ +"use strict"; +let JiraApi = require("jira-client"); +/** + * JIRA client api proxy + * + * @class JiraClient + */ +export class JiraClient { + + /** + * Jira api client + * + * @private + * @type {*} + * @memberof JiraClient + */ + private client: any; + + /** + * JIRA server host + * + * @private + * @type {String} + * @memberof JiraClient + */ + public server: String; + + /** + * current login user. + * + * @type {string} + * @memberof JiraClient + */ + public username: string; + + /** + * Connected the jira server. + * + * @private + * @type {boolean} + * @memberof JiraClient + */ + private connecting: boolean = false; + + /** + * Jira config object. + * + * @private + * @type {Object} + * @memberof JiraClient + */ + private config: Object = null; + + constructor (config: Object) { + this.client = new JiraApi(config); + this.server = this.client.host; + this.connecting = true; + this.config = config; + this.username = config["username"]; + } + + isConnection (): boolean { + return this.connecting; + } + /** + * Add issue comment by issue key or issue id + * + * @param {string} issueNumber + * @param {string} comment + * @param {(code: number, data: String | any) => void} callback + * @memberof JiraClient + */ + findIssueAndComment (issueNumber: string, comment: string, callback: (code: number, data: string | any) => void): void { + this.client.findIssue(issueNumber).then((issue) => { + this.client.addComment(issueNumber, comment).then((ret) => { + callback(200, ret); + }).catch((err) => { + callback(400, `ERROR: comment Issue ${issueNumber}: ${err}`); + }); + }).catch((err) => { + callback(400, `ERROR: Issue ${issueNumber} not found!`); + }); + } + + /** + * JIRA search api. + * + * @param {string} jql + * @param {((code: number, data: string | any) => void)} callback + * @memberof JiraClient + */ + searchJira (jql: string, callback: (code: number, data: string | any) => void): void { + this.client.searchJira(jql).then((data) => { + callback(200, data); + }, (err) => { + callback(400, err); + }).catch((err) => { + callback(400, err); + }); + } + + /** + * Get status for project. + * + * @param {((code: number, data: string | any) => void)} callback + * @param {string} [project] + * @memberof JiraClient + */ + listStatuses (callback: (code: number, data: string | any) => void, project?: string): void { + if (!project) { + project = this.config["defaultProject"]; + } + if (!project) { + callback(400, "No project found can not get statuses!"); + } + + this.makeUri({ + pathname: `/project/${project}/statuses` + }).then(data => { + callback(200, data); + }, err => { + callback(400, err); + }); + + } + + /** + * Make uri with the jiri api not support interface. + * + * @private + * @param {{pathname: string, query?: any}} query + * @param {{method: string, items: any[]}} [body] + * @returns {*} + * @memberof JiraClient + */ + private makeUri (query: {pathname: string, query?: any}, body?: {method: string, items: any[]}): any { + return this.client.doRequest( + this.client.makeRequestHeader( + this.client.makeUri(query), + body + ) + ); + } + + /** + * Get transitions for current issue. + * + * @param {string} issueKey + * @param {((code: number, data: string | any) => void)} callback + * @memberof JiraClient + */ + transitions (issueKey: string, callback: (code: number, data: string | any) => void): void { + this.client.listTransitions(issueKey).then(data => { + callback(200, data); + }, (err) => { + callback(400, err); + }); + } + + /** + * Do transitions for current issue. + * + * @param {string} issueKey + * @param {string} transitionId + * @param {((code: number, data: string | any) => void)} callback + * @memberof JiraClient + */ + doTransition (issueKey: string, transitionId: string, callback: (code: number, data: string | any) => void): void { + this.client.transitionIssue(issueKey, { + "transition": transitionId + }).then(data => { + callback(200, data); + }, (err) => { + callback(400, err); + }); + } + +}