From e55593f0e0b401ed9d64830a452144d338189888 Mon Sep 17 00:00:00 2001 From: lagel Date: Wed, 2 Aug 2017 23:42:19 +0800 Subject: [PATCH 1/4] Add get jira issues --- .gitignore | 3 +- package.json | 6 +- src/extension.ts | 92 +++++++++----------------- src/handler.ts | 167 +++++++++++++++++++++++++++++++++++++++++++++++ src/jira.ts | 145 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 351 insertions(+), 62 deletions(-) create mode 100644 src/handler.ts create mode 100644 src/jira.ts 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..2f3504a 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,8 @@ "Other" ], "activationEvents": [ - "onCommand:extension.jiraCommit" + "onCommand:extension.jiraCommit", + "onCommand:extension.jiraTasks" ], "main": "./out/src/extension", "contributes": { @@ -27,6 +28,9 @@ { "command": "extension.jiraCommit", "title": "jira issue add git commit as comment" + }, { + "command": "extension.jiraTasks", + "title": "jira show tasks" } ] }, diff --git a/src/extension.ts b/src/extension.ts index 817ec2d..e5e6371 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -2,11 +2,12 @@ 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'; @@ -14,70 +15,41 @@ 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'); + } + } 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(client); + }); - - 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 tasks = vscode.commands.registerCommand("extension.jiraTasks", () => { + if (!client.isConnection()) { + Handler.error('ERROR: can not connect jira host'); + return; } + Handler.getMyIssues(client); }); - context.subscriptions.push(comment); + context.subscriptions.concat([comment, tasks]); } // 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..fd63112 --- /dev/null +++ b/src/handler.ts @@ -0,0 +1,167 @@ +"use strict"; + +import * as vscode from "vscode"; +import * as historyUtil from "./historyUtils"; + +import { JiraClient } from "./jira"; +import { InputBoxOptions, QuickPickItem } from "vscode"; + +/** + * vscode util class. + * + * @export + * @class Handler + */ +export class Handler { + + /** + * 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 (client: JiraClient): void { + 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); + + 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 {JiraClient} client + * @memberof Handler + */ + public static getMyIssues (client: JiraClient): void { + + let self = this; + client.listStatuses(function (code, data) { + if (code === 400) { + return self.error(data); + } else { + let items: QuickPickItem[] = [], + options = { + placeHolder: "select task status!", + matchOnDescription: true + }; + + for (let status of data[0].statuses) { + let item: QuickPickItem = { + label: status.name, + description: status.description, + }; + items.push(item); + } + vscode.window.showQuickPick(items, options).then((data) => { + if (data) { + self.getIssuesByStatus(client, data); + } + }); + } + }); + } + + /** + * Get issues by status. + * + * @private + * @param {JiraClient} client + * @param {QuickPickItem} status + * @memberof Handler + */ + private static getIssuesByStatus (client: JiraClient, status: QuickPickItem): void { + client.searchJira(`assignee = ${client.username} and status = ${status.label} order by priority desc`, function (code, data) { + if (code === 400) { + return console.error(data); + } + console.log(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) => { + console.log(data); + }); + }); + } + +} \ No newline at end of file diff --git a/src/jira.ts b/src/jira.ts new file mode 100644 index 0000000..b399bf3 --- /dev/null +++ b/src/jira.ts @@ -0,0 +1,145 @@ +"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 + ) + ); + } + +} From 3aaf0f3809ebe976ea3fb6d28a16302fec1fe942 Mon Sep 17 00:00:00 2001 From: lagel Date: Thu, 3 Aug 2017 10:04:18 +0800 Subject: [PATCH 2/4] Improve the multiple subtask statuses --- src/handler.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/handler.ts b/src/handler.ts index fd63112..82bf6d8 100644 --- a/src/handler.ts +++ b/src/handler.ts @@ -112,9 +112,16 @@ export class Handler { options = { placeHolder: "select task status!", matchOnDescription: true - }; + }, + statuses: any = null; + + if (data instanceof Array) { + statuses = data[0].statuses; + } else { + statuses = data.statuses; + } - for (let status of data[0].statuses) { + for (let status of statuses) { let item: QuickPickItem = { label: status.name, description: status.description, From f54cb3105a26f829189ed6fe181e3d270231d0af Mon Sep 17 00:00:00 2001 From: lagel Date: Thu, 3 Aug 2017 14:39:00 +0800 Subject: [PATCH 3/4] Add transition function for issue --- package.json | 6 +- src/extension.ts | 15 ++++- src/handler.ts | 148 +++++++++++++++++++++++++++++++++++++---------- src/jira.ts | 33 +++++++++++ 4 files changed, 167 insertions(+), 35 deletions(-) diff --git a/package.json b/package.json index 2f3504a..a0d6e24 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,8 @@ ], "activationEvents": [ "onCommand:extension.jiraCommit", - "onCommand:extension.jiraTasks" + "onCommand:extension.jiraTasks", + "onCommand:extension.jiraDoTasks" ], "main": "./out/src/extension", "contributes": { @@ -31,6 +32,9 @@ }, { "command": "extension.jiraTasks", "title": "jira show tasks" + }, { + "command": "extension.jiraDoTasks", + "title": "jira do task" } ] }, diff --git a/src/extension.ts b/src/extension.ts index e5e6371..5d903cf 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -28,6 +28,7 @@ export function activate(context: vscode.ExtensionContext) { 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`); } @@ -38,7 +39,7 @@ export function activate(context: vscode.ExtensionContext) { Handler.error('ERROR: can not connect jira host'); return; } - Handler.addCommentForIssue(client); + Handler.addCommentForIssue(); }); let tasks = vscode.commands.registerCommand("extension.jiraTasks", () => { @@ -46,10 +47,18 @@ export function activate(context: vscode.ExtensionContext) { Handler.error('ERROR: can not connect jira host'); return; } - Handler.getMyIssues(client); + Handler.getMyIssues(); }); - context.subscriptions.concat([comment, tasks]); + let doTask = vscode.commands.registerCommand("extension.jiraDoTasks", () => { + if (!client.isConnection()) { + Handler.error('ERROR: can not connect jira host'); + return; + } + Handler.doMyIssue(); + }); + + 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 index 82bf6d8..a7563b2 100644 --- a/src/handler.ts +++ b/src/handler.ts @@ -4,7 +4,7 @@ import * as vscode from "vscode"; import * as historyUtil from "./historyUtils"; import { JiraClient } from "./jira"; -import { InputBoxOptions, QuickPickItem } from "vscode"; +import { InputBoxOptions, QuickPickItem, QuickPickOptions } from "vscode"; /** * vscode util class. @@ -14,6 +14,27 @@ import { InputBoxOptions, QuickPickItem } from "vscode"; */ 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. * @@ -51,7 +72,8 @@ export class Handler { * @static * @memberof Handler */ - public static addCommentForIssue (client: JiraClient): void { + public static addCommentForIssue (): void { + let self = this; this.inputBox({ placeHolder: "ID of a Issue" }).then((data) => { if ((data !== undefined) && (data !== null)) { let issueNumber = data; @@ -72,7 +94,7 @@ export class Handler { console.log(comment); - client.findIssueAndComment(issueNumber, comment, function (code, data) { + self.client.findIssueAndComment(issueNumber, comment, function (code, data) { if (data instanceof String) { if (code === 200) { vscode.window.setStatusBarMessage(data as string); @@ -98,18 +120,18 @@ export class Handler { * Get my jira issue tasks. * * @static - * @param {JiraClient} client + * @param {(item: QuickPickItem) => void} [callback] * @memberof Handler */ - public static getMyIssues (client: JiraClient): void { + public static getMyIssues (callback?: (item: QuickPickItem) => void): void { let self = this; - client.listStatuses(function (code, data) { + this.client.listStatuses(function (code, data) { if (code === 400) { return self.error(data); } else { let items: QuickPickItem[] = [], - options = { + options: QuickPickOptions = { placeHolder: "select task status!", matchOnDescription: true }, @@ -121,6 +143,10 @@ export class Handler { statuses = data.statuses; } + items.push({ + label: 'All', + description: 'All issue statuses' + }); for (let status of statuses) { let item: QuickPickItem = { label: status.name, @@ -130,7 +156,7 @@ export class Handler { } vscode.window.showQuickPick(items, options).then((data) => { if (data) { - self.getIssuesByStatus(client, data); + self.getIssuesByStatus(data, callback); } }); } @@ -141,34 +167,94 @@ export class Handler { * Get issues by status. * * @private - * @param {JiraClient} client + * @static * @param {QuickPickItem} status + * @param {(item: QuickPickItem) => void} [callback] * @memberof Handler */ - private static getIssuesByStatus (client: JiraClient, status: QuickPickItem): void { - client.searchJira(`assignee = ${client.username} and status = ${status.label} order by priority desc`, function (code, data) { - if (code === 400) { - return console.error(data); - } - console.log(data); - let items: QuickPickItem[] = [], - options = { - placeHolder: "select task!", - matchOnDetail: true - }; + 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`; - for (let issue of data.issues) { - let item: QuickPickItem = { - label: issue.key, - description: issue.fields.status.name, - detail: issue.fields.summary, - }; - items.push(item); + 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); } - vscode.window.showQuickPick(items, options).then((data) => { - console.log(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 index b399bf3..32616cd 100644 --- a/src/jira.ts +++ b/src/jira.ts @@ -142,4 +142,37 @@ export class JiraClient { ); } + /** + * 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); + }); + } + } From ec1df52e9de96666269309b656dfa5bda39e8c9f Mon Sep 17 00:00:00 2001 From: lagel Date: Sat, 5 Aug 2017 10:53:42 +0800 Subject: [PATCH 4/4] Add copy issue feature --- package.json | 25 ++++++++++++++----------- src/extension.ts | 9 ++++++++- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index a0d6e24..474cc32 100644 --- a/package.json +++ b/package.json @@ -26,16 +26,18 @@ "main": "./out/src/extension", "contributes": { "commands": [ - { - "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" - } + { + "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": { @@ -49,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 5d903cf..540074a 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -9,6 +9,7 @@ import { JiraClient } from './jira'; import { QuickPickItem } from 'vscode'; import { Handler } from './handler'; +const Copy = require('copy-paste'); export function activate(context: vscode.ExtensionContext) { @@ -47,7 +48,13 @@ export function activate(context: vscode.ExtensionContext) { Handler.error('ERROR: can not connect jira host'); return; } - Handler.getMyIssues(); + Handler.getMyIssues(function(data) { + if (data) { + Copy.copy(`${data.label} ${data.detail}`, function () { + vscode.window.setStatusBarMessage('The issue is copied.', 2000); + }); + } + }); }); let doTask = vscode.commands.registerCommand("extension.jiraDoTasks", () => {