bjira 0.0.16 → 0.0.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/nohup.out +0 -0
- package/package.json +1 -1
- package/src/create.js +12 -9
- package/src/errorhandler.js +5 -0
- package/src/field.js +111 -50
- package/src/issue.js +32 -4
- package/src/jira.js +16 -5
- package/src/project.js +5 -0
- package/src/query.js +100 -11
- package/src/run.js +2 -1
- package/src/set.js +45 -15
- package/src/table.js +15 -4
package/nohup.out
ADDED
|
File without changes
|
package/package.json
CHANGED
package/src/create.js
CHANGED
|
@@ -33,8 +33,8 @@ class Create extends Command {
|
|
|
33
33
|
}
|
|
34
34
|
})));
|
|
35
35
|
|
|
36
|
-
const meta = await
|
|
37
|
-
|
|
36
|
+
const meta = await Project.metadata(jira, project.key, issueType.name);
|
|
37
|
+
|
|
38
38
|
const newIssue = {
|
|
39
39
|
fields: {
|
|
40
40
|
project: {
|
|
@@ -58,11 +58,12 @@ class Create extends Command {
|
|
|
58
58
|
.issuetypes.find(i => i.name === issueType.name).fields;
|
|
59
59
|
const requiredFields = Object.keys(issueFields).filter(
|
|
60
60
|
key => issueFields[key].required &&
|
|
61
|
-
Field.isSupported(issueFields[key]
|
|
62
|
-
!["summary", "description", "project"].includes(key)).map(key => issueFields[key]);
|
|
61
|
+
Field.isSupported(issueFields[key]) &&
|
|
62
|
+
!["issuetype", "summary", "description", "project"].includes(key)).map(key => issueFields[key]);
|
|
63
63
|
|
|
64
64
|
for (const field of requiredFields) {
|
|
65
|
-
|
|
65
|
+
const fieldData = await Field.askFieldIfSupported(field);
|
|
66
|
+
newIssue.fields[fieldData.key] = fieldData.value;
|
|
66
67
|
}
|
|
67
68
|
|
|
68
69
|
if (issueType.name !== 'Epic' &&
|
|
@@ -100,10 +101,12 @@ class Create extends Command {
|
|
|
100
101
|
await Set.setStatus(jira, issue.key);
|
|
101
102
|
}
|
|
102
103
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
104
|
+
const customFields = jira.fields.filter(
|
|
105
|
+
field => field.projectName === project.key &&
|
|
106
|
+
field.issueTypeName === issueType.name);
|
|
107
|
+
if (customFields.length > 0 && await Ask.askBoolean('Do you want to set custom fields?')) {
|
|
108
|
+
for (let customField of customFields) {
|
|
109
|
+
await Set.setCustomField(jira, customField, issue.key);
|
|
107
110
|
}
|
|
108
111
|
}
|
|
109
112
|
|
package/src/errorhandler.js
CHANGED
package/src/field.js
CHANGED
|
@@ -2,9 +2,12 @@
|
|
|
2
2
|
//
|
|
3
3
|
// SPDX-License-Identifier: MIT
|
|
4
4
|
|
|
5
|
+
import color from 'chalk';
|
|
6
|
+
|
|
5
7
|
import Ask from './ask.js';
|
|
6
8
|
import Command from './command.js';
|
|
7
9
|
import Jira from './jira.js';
|
|
10
|
+
import Project from './project.js';
|
|
8
11
|
import Table from './table.js';
|
|
9
12
|
|
|
10
13
|
class Field extends Command {
|
|
@@ -16,59 +19,76 @@ class Field extends Command {
|
|
|
16
19
|
.action(async () => {
|
|
17
20
|
const jira = new Jira(program);
|
|
18
21
|
|
|
19
|
-
const resultFields = await
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
22
|
+
const resultFields = await jira.spin('Retrieving the fields...',
|
|
23
|
+
jira.apiRequest('/issue/createmeta?expand=projects.issuetypes.fields'));
|
|
24
|
+
|
|
25
|
+
resultFields.projects.forEach(project => {
|
|
26
|
+
console.log(`\nProject: ${color.blue(project.name)} (${color.blue(project.key)})`);
|
|
27
|
+
project.issuetypes.forEach(issueType => {
|
|
28
|
+
console.log(`\nIssue type: ${color.yellow(issueType.name)}`);
|
|
29
|
+
|
|
30
|
+
const table = new Table({
|
|
31
|
+
head: ['Name', 'Supported', 'Type']
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
for (const fieldName in issueType.fields) {
|
|
35
|
+
const field = issueType.fields[fieldName];
|
|
36
|
+
const supported = Field.isSupported(field);
|
|
37
|
+
table.addRow([{
|
|
38
|
+
color: "blue",
|
|
39
|
+
text: field.name
|
|
40
|
+
}, supported, supported ? field.schema?.type : ""]);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
console.log(table.toString());
|
|
44
|
+
});
|
|
31
45
|
});
|
|
32
|
-
console.log(table.toString());
|
|
33
46
|
});
|
|
34
47
|
|
|
35
48
|
cmd.command("add")
|
|
36
49
|
.description("Add a custom field to be shown")
|
|
50
|
+
.argument('<project>', 'The project name')
|
|
51
|
+
.argument('<issueType>', 'The issue type')
|
|
37
52
|
.argument('<field>', 'The field name')
|
|
38
|
-
.action(async fieldName => {
|
|
53
|
+
.action(async (projectName, issueTypeName, fieldName) => {
|
|
39
54
|
const jira = new Jira(program);
|
|
40
55
|
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
if (!
|
|
45
|
-
console.log(
|
|
56
|
+
const meta = await Project.metadata(jira, projectName, issueTypeName);
|
|
57
|
+
const issueType = meta.projects.find(p => p.key === projectName)
|
|
58
|
+
.issuetypes.find(i => i.name === issueTypeName);
|
|
59
|
+
if (!issueType) {
|
|
60
|
+
console.log(`Issue type ${issueTypeName} does not exist.`);
|
|
46
61
|
return;
|
|
47
62
|
}
|
|
48
63
|
|
|
49
|
-
|
|
50
|
-
|
|
64
|
+
for (const name in issueType.fields) {
|
|
65
|
+
const field = issueType.fields[name];
|
|
66
|
+
if (field.name !== fieldName) continue;
|
|
67
|
+
|
|
68
|
+
if (!Field.isSupported(field)) {
|
|
69
|
+
console.log("Unsupported field.");
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
jira.addField(projectName, issueTypeName, fieldName);
|
|
74
|
+
jira.syncConfig();
|
|
75
|
+
|
|
76
|
+
console.log('Config file succesfully updated');
|
|
51
77
|
return;
|
|
52
78
|
}
|
|
53
79
|
|
|
54
|
-
|
|
55
|
-
jira.syncConfig();
|
|
56
|
-
|
|
57
|
-
console.log('Config file succesfully updated');
|
|
80
|
+
console.log(`Field ${fieldName} does not exist in Issue type ${issueTypeName} for project ${projectName}`);
|
|
58
81
|
});
|
|
59
82
|
|
|
60
83
|
cmd.command("remove")
|
|
61
84
|
.description("Remove a custom field")
|
|
85
|
+
.argument('<project>', 'The project name')
|
|
86
|
+
.argument('<issueType>', 'The issue type')
|
|
62
87
|
.argument('<field>', 'The field name')
|
|
63
|
-
.action(async fieldName => {
|
|
88
|
+
.action(async (projectName, issueTypeName, fieldName) => {
|
|
64
89
|
const jira = new Jira(program);
|
|
65
90
|
|
|
66
|
-
|
|
67
|
-
console.log("Unknown field.");
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
jira.removeField(fieldName);
|
|
91
|
+
jira.removeField(projectName, issueTypeName, fieldName);
|
|
72
92
|
jira.syncConfig();
|
|
73
93
|
|
|
74
94
|
console.log('Config file succesfully updated');
|
|
@@ -80,12 +100,17 @@ class Field extends Command {
|
|
|
80
100
|
const jira = new Jira(program);
|
|
81
101
|
|
|
82
102
|
const table = new Table({
|
|
83
|
-
head: ['Name']
|
|
103
|
+
head: ['Project', 'Issue Type', 'Name']
|
|
84
104
|
});
|
|
85
105
|
|
|
86
|
-
jira.fields.forEach(
|
|
106
|
+
jira.fields.forEach(field => table.addRow([{
|
|
87
107
|
color: "blue",
|
|
88
|
-
text:
|
|
108
|
+
text: field.projectName,
|
|
109
|
+
}, {
|
|
110
|
+
color: "yellow",
|
|
111
|
+
text: field.issueTypeName
|
|
112
|
+
}, {
|
|
113
|
+
text: field.fieldName
|
|
89
114
|
}]));
|
|
90
115
|
console.log(table.toString());
|
|
91
116
|
});
|
|
@@ -95,24 +120,51 @@ class Field extends Command {
|
|
|
95
120
|
return await jira.spin('Retrieving the fields...', jira.api.listFields());
|
|
96
121
|
}
|
|
97
122
|
|
|
98
|
-
static isSupported(
|
|
99
|
-
|
|
100
|
-
|
|
123
|
+
static isSupported(fieldData) {
|
|
124
|
+
if (["string", "number"].includes(fieldData.schema?.type)) {
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
101
127
|
|
|
102
|
-
|
|
103
|
-
|
|
128
|
+
if ("allowedValues" in fieldData) {
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
104
131
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
if (field.name === fieldName) fieldData = field;
|
|
108
|
-
});
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
109
134
|
|
|
110
|
-
|
|
111
|
-
|
|
135
|
+
static fieldValue(field, fieldData) {
|
|
136
|
+
if (!Field.isSupported(fieldData)) {
|
|
112
137
|
return null;
|
|
113
138
|
}
|
|
114
139
|
|
|
115
|
-
|
|
140
|
+
let type;
|
|
141
|
+
switch (fieldData.schema.type) {
|
|
142
|
+
case 'number':
|
|
143
|
+
return field;
|
|
144
|
+
case 'string':
|
|
145
|
+
return field;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return field.name;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
static async fetchAndAskFieldIfSupported(jira, field) {
|
|
152
|
+
const meta = await Project.metadata(jira, field.projectName, field.issueTypeName);
|
|
153
|
+
const fields = meta.projects.find(p => p.key === field.projectName)
|
|
154
|
+
.issuetypes.find(i => i.name === field.issueTypeName).fields;
|
|
155
|
+
|
|
156
|
+
for (const name in fields) {
|
|
157
|
+
const fieldObj = fields[name];
|
|
158
|
+
if (fieldObj.name === field.fieldName) {
|
|
159
|
+
return Field.askFieldIfSupported(fieldObj);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
static async askFieldIfSupported(fieldData) {
|
|
167
|
+
if (!Field.isSupported(fieldData)) {
|
|
116
168
|
console.log("Unsupported field");
|
|
117
169
|
return null;
|
|
118
170
|
}
|
|
@@ -121,15 +173,24 @@ class Field extends Command {
|
|
|
121
173
|
switch (fieldData.schema.type) {
|
|
122
174
|
case 'number':
|
|
123
175
|
return {
|
|
124
|
-
value: await Ask.askNumber(`${
|
|
176
|
+
value: await Ask.askNumber(`${fieldData.name}:`), key: fieldData.key
|
|
125
177
|
};
|
|
126
178
|
case 'string':
|
|
127
179
|
return {
|
|
128
|
-
value: await Ask.askString(`${
|
|
180
|
+
value: await Ask.askString(`${fieldData.name}:`), key: fieldData.key
|
|
129
181
|
};
|
|
130
182
|
}
|
|
131
183
|
|
|
132
|
-
return
|
|
184
|
+
return {
|
|
185
|
+
key: fieldData.key,
|
|
186
|
+
value: await Ask.askList(`${fieldData.name}:`,
|
|
187
|
+
fieldData.allowedValues.map(value => ({
|
|
188
|
+
name: value.name,
|
|
189
|
+
value: {
|
|
190
|
+
id: value.id
|
|
191
|
+
}
|
|
192
|
+
})))
|
|
193
|
+
};
|
|
133
194
|
}
|
|
134
195
|
};
|
|
135
196
|
|
package/src/issue.js
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
import Command from './command.js';
|
|
6
6
|
import Field from './field.js';
|
|
7
7
|
import Jira from './jira.js';
|
|
8
|
+
import Project from './project.js';
|
|
9
|
+
import Query from './query.js';
|
|
8
10
|
import Table from './table.js';
|
|
9
11
|
|
|
10
12
|
const DEFAULT_QUERY_LIMIT = 20;
|
|
@@ -18,6 +20,7 @@ class Issue extends Command {
|
|
|
18
20
|
const cmd = program.command('show')
|
|
19
21
|
.description('Show an issue')
|
|
20
22
|
.option('-C, --comments', 'Show the comments too')
|
|
23
|
+
.option('-s, --subissues', 'Show the comments too')
|
|
21
24
|
.argument('<id>', 'The issue ID')
|
|
22
25
|
.action(async id => {
|
|
23
26
|
const jira = new Jira(program);
|
|
@@ -64,9 +67,6 @@ class Issue extends Command {
|
|
|
64
67
|
[
|
|
65
68
|
'Assignee', Issue.showUser(issue.fields['Assignee'])
|
|
66
69
|
],
|
|
67
|
-
[
|
|
68
|
-
'Priority', issue.fields['Priority'].name
|
|
69
|
-
],
|
|
70
70
|
[
|
|
71
71
|
'Epic Link', {
|
|
72
72
|
color: "yellow",
|
|
@@ -84,7 +84,22 @@ class Issue extends Command {
|
|
|
84
84
|
]
|
|
85
85
|
]);
|
|
86
86
|
|
|
87
|
-
jira.fields.
|
|
87
|
+
const customFields = jira.fields.filter(
|
|
88
|
+
field => field.projectName === issue.fields['Project'].key &&
|
|
89
|
+
field.issueTypeName === issue.fields['Issue Type'].name);
|
|
90
|
+
if (customFields.length > 0) {
|
|
91
|
+
const meta = await Project.metadata(jira, issue.fields['Project'].key, issue.fields['Issue Type'].name);
|
|
92
|
+
customFields.forEach(field => {
|
|
93
|
+
const fields = meta.projects.find(p => p.key === field.projectName)
|
|
94
|
+
.issuetypes.find(i => i.name === field.issueTypeName).fields;
|
|
95
|
+
for (const name in fields) {
|
|
96
|
+
const fieldObj = fields[name];
|
|
97
|
+
if (fieldObj.name === field.fieldName) {
|
|
98
|
+
table.addRow([field.fieldName, Field.fieldValue(issue.fields[field.fieldName], fieldObj) || "unset"]);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
}
|
|
88
103
|
|
|
89
104
|
table.addRows([
|
|
90
105
|
[
|
|
@@ -139,6 +154,19 @@ class Issue extends Command {
|
|
|
139
154
|
}
|
|
140
155
|
|
|
141
156
|
console.log(table.toString());
|
|
157
|
+
|
|
158
|
+
if (cmd.opts().subissues) {
|
|
159
|
+
console.log("\nSub-issues:");
|
|
160
|
+
const children = await Query.runQuery(jira, `parent = "${id}"`, 999999);
|
|
161
|
+
await Query.showIssues(jira, children.issues, children.total, resultFields, false);
|
|
162
|
+
|
|
163
|
+
if (issue.fields['Issue Type'].name === 'Epic') {
|
|
164
|
+
console.log("\nEpic issues:");
|
|
165
|
+
const children = await jira.spin('Fetching child issues...', jira.api.getIssuesForEpic(id));
|
|
166
|
+
await Query.showIssues(jira, children.issues, children.total, resultFields, false);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
142
170
|
});
|
|
143
171
|
}
|
|
144
172
|
|
package/src/jira.js
CHANGED
|
@@ -31,16 +31,27 @@ class Jira {
|
|
|
31
31
|
this._config.latestProject = latestProject;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
addField(fieldName) {
|
|
34
|
+
addField(projectName, issueTypeName, fieldName) {
|
|
35
35
|
if (!Array.isArray(this._config.fields)) this._config.fields = [];
|
|
36
36
|
|
|
37
|
-
this._config.fields.
|
|
37
|
+
if (!this._config.fields.find(field => field.projectName === projectName &&
|
|
38
|
+
field.issueTypeName === issueTypeName && field.fieldName === fieldName)) {
|
|
39
|
+
this._config.fields.push({
|
|
40
|
+
projectName,
|
|
41
|
+
issueTypeName,
|
|
42
|
+
fieldName
|
|
43
|
+
});
|
|
44
|
+
}
|
|
38
45
|
}
|
|
39
46
|
|
|
40
|
-
removeField(fieldName) {
|
|
41
|
-
if (!Array.isArray(this._config.fields)
|
|
47
|
+
removeField(projectName, issueTypeName, fieldName) {
|
|
48
|
+
if (!Array.isArray(this._config.fields)) return;
|
|
42
49
|
|
|
43
|
-
this._config.fields.
|
|
50
|
+
const pos = this._config.fields.findIndex(field => field.projectName === projectName &&
|
|
51
|
+
field.issueTypeName === issueTypeName && field.fieldName === fieldName);
|
|
52
|
+
if (pos !== -1) {
|
|
53
|
+
this._config.fields.splice(pos, 1);
|
|
54
|
+
}
|
|
44
55
|
}
|
|
45
56
|
|
|
46
57
|
get fields() {
|
package/src/project.js
CHANGED
|
@@ -73,6 +73,11 @@ class Project extends Command {
|
|
|
73
73
|
issueTypes: project.issuetypes,
|
|
74
74
|
};
|
|
75
75
|
}
|
|
76
|
+
|
|
77
|
+
static async metadata(jira, project, issueType) {
|
|
78
|
+
return await jira.spin('Retrieving the fields...',
|
|
79
|
+
jira.apiRequest(`/issue/createmeta?projectKeys=${project}&issuetypeNames=${issueType}&expand=projects.issuetypes.fields`));
|
|
80
|
+
}
|
|
76
81
|
};
|
|
77
82
|
|
|
78
83
|
export default Project;
|
package/src/query.js
CHANGED
|
@@ -21,6 +21,7 @@ class Query extends Command {
|
|
|
21
21
|
.option('-l, --limit <limit>',
|
|
22
22
|
`Set the query limit. Default ${DEFAULT_QUERY_LIMIT}`,
|
|
23
23
|
DEFAULT_QUERY_LIMIT)
|
|
24
|
+
.option('-g, --grouped', 'Group the issues by parenting')
|
|
24
25
|
.action(async query => {
|
|
25
26
|
const jira = new Jira(program);
|
|
26
27
|
const opts = cmd.opts();
|
|
@@ -28,32 +29,120 @@ class Query extends Command {
|
|
|
28
29
|
const resultFields = await Field.listFields(jira);
|
|
29
30
|
const result = await Query.runQuery(jira, query, opts.limit);
|
|
30
31
|
|
|
31
|
-
Query.showIssues(jira, result.issues, result.total, resultFields);
|
|
32
|
+
await Query.showIssues(jira, result.issues, result.total, resultFields, opts.grouped);
|
|
32
33
|
});
|
|
33
34
|
}
|
|
34
35
|
|
|
35
|
-
static showIssues(jira, issues, total, fields) {
|
|
36
|
+
static async showIssues(jira, issues, total, fields, grouped) {
|
|
36
37
|
console.log(`Showing ${color.bold(issues.length)} issues of ${color.bold(total)}`);
|
|
37
38
|
|
|
39
|
+
issues = issues.map(issue => Issue.replaceFields(issue, fields)).map(issue => ({
|
|
40
|
+
children: [],
|
|
41
|
+
issue
|
|
42
|
+
}));
|
|
43
|
+
|
|
44
|
+
const addIssueInTree = (issues, issue, parentId) => {
|
|
45
|
+
for (const existingIssue of issues) {
|
|
46
|
+
if (existingIssue.issue.key == parentId) {
|
|
47
|
+
existingIssue.children.push(issue);
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (addIssueInTree(existingIssue.children, issue, parentId)) {
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return false;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const computeIssueInTree = async (issues, newIssues, issue) => {
|
|
60
|
+
// Top level.
|
|
61
|
+
if (!issue.issue.fields['Epic Link'] && !issue.issue.fields['Parent']) {
|
|
62
|
+
newIssues.push(issue);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const parentId = issue.issue.fields['Epic Link'] || issue.issue.fields['Parent'].key;
|
|
67
|
+
|
|
68
|
+
// In the already processed issues
|
|
69
|
+
if (addIssueInTree(newIssues, issue, parentId)) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// In the non-already processed issues
|
|
74
|
+
if (addIssueInTree(issues, issue, parentId)) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const parentIssueResult = await jira.spin('Running query...', jira.api.findIssue(parentId));
|
|
79
|
+
const parentIssue = Issue.replaceFields(parentIssueResult, fields);
|
|
80
|
+
|
|
81
|
+
const parentIssueData = {
|
|
82
|
+
children: [issue],
|
|
83
|
+
incompleted: true,
|
|
84
|
+
issue: parentIssue
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
await computeIssueInTree(issues, newIssues, parentIssueData);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
if (grouped) {
|
|
91
|
+
const newIssues = [];
|
|
92
|
+
for (; issues.length; issues = issues.splice(1)) {
|
|
93
|
+
await computeIssueInTree(issues, newIssues, issues[0]);
|
|
94
|
+
}
|
|
95
|
+
issues = newIssues;
|
|
96
|
+
}
|
|
97
|
+
|
|
38
98
|
const table = new Table({
|
|
39
|
-
head: ['Key', 'Status', 'Type', 'Assignee', 'Summary']
|
|
99
|
+
head: ['Key', 'Status', 'Type', 'Assignee', 'Summary'],
|
|
100
|
+
unresizableColumns: [0],
|
|
40
101
|
});
|
|
41
102
|
|
|
42
|
-
|
|
103
|
+
const showIssue = (table, issue, nested) => {
|
|
104
|
+
let pre = "";
|
|
105
|
+
if (nested.length) {
|
|
106
|
+
pre += " ";
|
|
107
|
+
for (let i = 0; i < nested.length - 1; ++i) {
|
|
108
|
+
pre += (nested[i]) ? '│ ' : ' ';
|
|
109
|
+
}
|
|
110
|
+
pre += (nested[nested.length - 1]) ? "├─ " : "└─ ";
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
table.addRow([{
|
|
43
114
|
color: "blue",
|
|
44
|
-
|
|
115
|
+
style: issue.incompleted ? "italic" : "normal",
|
|
116
|
+
text: pre + issue.issue.key
|
|
45
117
|
}, {
|
|
46
118
|
color: "green",
|
|
47
|
-
|
|
119
|
+
style: issue.incompleted ? "italic" : "normal",
|
|
120
|
+
text: issue.issue.fields['Status'].name
|
|
48
121
|
}, {
|
|
49
122
|
color: "green",
|
|
50
|
-
|
|
123
|
+
style: issue.incompleted ? "italic" : "normal",
|
|
124
|
+
text: issue.issue.fields['Issue Type'].name
|
|
51
125
|
}, {
|
|
52
126
|
color: "yellow",
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
127
|
+
style: issue.incompleted ? "italic" : "normal",
|
|
128
|
+
text: Issue.showUser(issue.issue.fields['Assignee'])
|
|
129
|
+
}, {
|
|
130
|
+
style: issue.incompleted ? "italic" : "normal",
|
|
131
|
+
text: issue.issue.fields['Summary']
|
|
132
|
+
}]);
|
|
133
|
+
|
|
134
|
+
if (issue.children.length) {
|
|
135
|
+
const newNested = [];
|
|
136
|
+
nested.forEach(n => newNested.push(n));
|
|
137
|
+
newNested.push(false);
|
|
138
|
+
issue.children.forEach((subissue, pos) => {
|
|
139
|
+
newNested[nested.length] = (pos < issue.children.length - 1);
|
|
140
|
+
showIssue(table, subissue, newNested);
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
issues.forEach(issue => showIssue(table, issue, []));
|
|
57
146
|
|
|
58
147
|
console.log(table.toString());
|
|
59
148
|
}
|
package/src/run.js
CHANGED
|
@@ -19,6 +19,7 @@ class Run extends Command {
|
|
|
19
19
|
.option('-l, --limit <limit>',
|
|
20
20
|
`Set the query limit. Default ${DEFAULT_QUERY_LIMIT}`,
|
|
21
21
|
DEFAULT_QUERY_LIMIT)
|
|
22
|
+
.option('-g, --grouped', 'Group the issues by parenting')
|
|
22
23
|
.action(async name => {
|
|
23
24
|
const jira = new Jira(program);
|
|
24
25
|
const opts = cmd.opts();
|
|
@@ -46,7 +47,7 @@ class Run extends Command {
|
|
|
46
47
|
const resultFields = await Field.listFields(jira);
|
|
47
48
|
const result = await Query.runQuery(jira, query, opts.limit);
|
|
48
49
|
|
|
49
|
-
Query.showIssues(jira, result.issues, result.total, resultFields);
|
|
50
|
+
await Query.showIssues(jira, result.issues, result.total, resultFields, opts.grouped);
|
|
50
51
|
});
|
|
51
52
|
}
|
|
52
53
|
|
package/src/set.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import Ask from './ask.js';
|
|
6
6
|
import Command from './command.js';
|
|
7
7
|
import Field from './field.js';
|
|
8
|
+
import Issue from './issue.js';
|
|
8
9
|
import Jira from './jira.js';
|
|
9
10
|
import User from './user.js';
|
|
10
11
|
|
|
@@ -14,7 +15,7 @@ class Set extends Command {
|
|
|
14
15
|
.description('Update fields in an issue');
|
|
15
16
|
setCmd.command('assignee')
|
|
16
17
|
.description('Assign the issue to somebody')
|
|
17
|
-
.argument('<
|
|
18
|
+
.argument('<ID>', 'The issue ID')
|
|
18
19
|
.action(async id => {
|
|
19
20
|
const jira = new Jira(program);
|
|
20
21
|
await Set.assignIssue(jira, id);
|
|
@@ -22,7 +23,7 @@ class Set extends Command {
|
|
|
22
23
|
|
|
23
24
|
setCmd.command('unassign')
|
|
24
25
|
.description('Unassign the issue')
|
|
25
|
-
.argument('<
|
|
26
|
+
.argument('<ID>', 'The issue ID')
|
|
26
27
|
.action(async id => {
|
|
27
28
|
const jira = new Jira(program);
|
|
28
29
|
|
|
@@ -34,12 +35,12 @@ class Set extends Command {
|
|
|
34
35
|
}
|
|
35
36
|
}
|
|
36
37
|
|
|
37
|
-
await jira.spin(
|
|
38
|
+
await jira.spin(`Updating issue ${id}...`, jira.api.updateIssue(id, issue));
|
|
38
39
|
});
|
|
39
40
|
|
|
40
41
|
setCmd.command('status')
|
|
41
42
|
.description('Change the status')
|
|
42
|
-
.argument('<
|
|
43
|
+
.argument('<ID>', 'The issue ID')
|
|
43
44
|
.action(async id => {
|
|
44
45
|
const jira = new Jira(program);
|
|
45
46
|
await Set.setStatus(jira, id);
|
|
@@ -51,7 +52,21 @@ class Set extends Command {
|
|
|
51
52
|
.argument('<id>', 'The issue ID')
|
|
52
53
|
.action(async (fieldName, id) => {
|
|
53
54
|
const jira = new Jira(program);
|
|
54
|
-
|
|
55
|
+
|
|
56
|
+
const resultFields = await Field.listFields(jira);
|
|
57
|
+
const result = await jira.spin('Running query...', jira.api.findIssue(id));
|
|
58
|
+
const issue = Issue.replaceFields(result, resultFields);
|
|
59
|
+
|
|
60
|
+
const customField = jira.fields.find(
|
|
61
|
+
field => field.projectName === issue.fields['Project'].key &&
|
|
62
|
+
field.issueTypeName === issue.fields['Issue Type'].name &&
|
|
63
|
+
field.fieldName === fieldName);
|
|
64
|
+
if (!customField) {
|
|
65
|
+
console.log("Unknown custom field");
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
await Set.setCustomField(jira, customField, id);
|
|
55
70
|
});
|
|
56
71
|
}
|
|
57
72
|
|
|
@@ -72,12 +87,12 @@ class Set extends Command {
|
|
|
72
87
|
}
|
|
73
88
|
}
|
|
74
89
|
|
|
75
|
-
await jira.spin(
|
|
90
|
+
await jira.spin(`Updating issue ${id}...`, jira.api.updateIssue(id, issue));
|
|
76
91
|
}
|
|
77
92
|
|
|
78
|
-
static async setCustomField(jira,
|
|
79
|
-
const field = await Field.
|
|
80
|
-
if (
|
|
93
|
+
static async setCustomField(jira, customField, id) {
|
|
94
|
+
const field = await Field.fetchAndAskFieldIfSupported(jira, customField);
|
|
95
|
+
if (field === null) {
|
|
81
96
|
console.log("Unsupported field type");
|
|
82
97
|
return;
|
|
83
98
|
}
|
|
@@ -85,7 +100,7 @@ class Set extends Command {
|
|
|
85
100
|
const data = {};
|
|
86
101
|
data[field.key] = field.value;
|
|
87
102
|
|
|
88
|
-
await jira.spin(
|
|
103
|
+
await jira.spin(`Updating issue ${id}...`, jira.api.updateIssue(id, {
|
|
89
104
|
fields: {
|
|
90
105
|
...data
|
|
91
106
|
}
|
|
@@ -94,18 +109,33 @@ class Set extends Command {
|
|
|
94
109
|
|
|
95
110
|
static async setStatus(jira, id) {
|
|
96
111
|
const transitionList = await jira.spin('Retrieving transitions...', jira.api.listTransitions(id));
|
|
97
|
-
const
|
|
98
|
-
transitionList.transitions.map(transition => ({
|
|
112
|
+
const transitionData = await Ask.askList('Status:',
|
|
113
|
+
transitionList.transitions.filter(transition => transition.isAvailable).map(transition => ({
|
|
99
114
|
name: transition.name,
|
|
100
|
-
value: transition
|
|
115
|
+
value: transition
|
|
101
116
|
})));
|
|
117
|
+
|
|
102
118
|
const transition = {
|
|
103
119
|
transition: {
|
|
104
|
-
id:
|
|
120
|
+
id: transitionData.id
|
|
105
121
|
}
|
|
106
122
|
};
|
|
107
123
|
|
|
108
|
-
|
|
124
|
+
for (const field of Object.keys(transitionData.fields)) {
|
|
125
|
+
const fieldData = transitionData.fields[field];
|
|
126
|
+
|
|
127
|
+
if (!fieldData.required) continue;
|
|
128
|
+
|
|
129
|
+
if (!Field.isSupported(fieldData)) {
|
|
130
|
+
console.log(`Field ${field} is required but it's not supported`);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const fieldValue = await Field.askFieldIfSupported(fieldData);
|
|
135
|
+
transition.transition[fieldValue.key] = fieldValue.value;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
await jira.spin(`Updating issue ${id}...`, jira.api.transitionIssue(id, transition));
|
|
109
139
|
}
|
|
110
140
|
};
|
|
111
141
|
|
package/src/table.js
CHANGED
|
@@ -12,6 +12,8 @@ class Table {
|
|
|
12
12
|
options.head?.forEach(head => {
|
|
13
13
|
this._columns.push(this._createColumn(head, 0));
|
|
14
14
|
});
|
|
15
|
+
|
|
16
|
+
this._unresizableColumns = options.unresizableColumns || [];
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
addRow(row) {
|
|
@@ -58,7 +60,7 @@ class Table {
|
|
|
58
60
|
const rowHeight = Math.max(...this._columns.map(column => this._computeRowHeight(column, row)));
|
|
59
61
|
|
|
60
62
|
for (let i = 0; i < rowHeight; ++i) {
|
|
61
|
-
lines.push(this._columns.map(column => this.
|
|
63
|
+
lines.push(this._columns.map(column => this._stylize(column.rows[row], this._toWidth(this._computeLine(column.rows[row], i), column.width))).join(" "));
|
|
62
64
|
}
|
|
63
65
|
}
|
|
64
66
|
|
|
@@ -118,7 +120,7 @@ class Table {
|
|
|
118
120
|
}
|
|
119
121
|
|
|
120
122
|
_maybeSplitRow(row, width) {
|
|
121
|
-
if (row.length
|
|
123
|
+
if (row.length <= width) return [row];
|
|
122
124
|
|
|
123
125
|
const rows = [];
|
|
124
126
|
let currentRow = "";
|
|
@@ -142,8 +144,9 @@ class Table {
|
|
|
142
144
|
|
|
143
145
|
_resizeWidthOfOne() {
|
|
144
146
|
const max = Math.max(...this._columns.map(column => column.width));
|
|
145
|
-
for (let
|
|
146
|
-
|
|
147
|
+
for (let columnId in this._columns) {
|
|
148
|
+
const column = this._columns[columnId];
|
|
149
|
+
if (!this._unresizableColumns.includes(columnId) && column.width === max) {
|
|
147
150
|
--column.width;
|
|
148
151
|
break;
|
|
149
152
|
}
|
|
@@ -154,6 +157,14 @@ class Table {
|
|
|
154
157
|
return str.slice(0, width);
|
|
155
158
|
}
|
|
156
159
|
|
|
160
|
+
_stylize(row, text) {
|
|
161
|
+
if (!("style" in row) || row.style === "normal") {
|
|
162
|
+
return this._colorize(row, text);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return color[row.style].apply(null, [this._colorize(row, text)]);
|
|
166
|
+
}
|
|
167
|
+
|
|
157
168
|
_colorize(row, text) {
|
|
158
169
|
if (!("color" in row)) {
|
|
159
170
|
return text;
|