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 ADDED
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bjira",
3
- "version": "0.0.16",
3
+ "version": "0.0.17",
4
4
  "description": "A simple jira CLI tool",
5
5
  "main": "src/index.js",
6
6
  "author": {
package/src/create.js CHANGED
@@ -33,8 +33,8 @@ class Create extends Command {
33
33
  }
34
34
  })));
35
35
 
36
- const meta = await jira.spin('Retrieving issue metadata...',
37
- jira.apiRequest(`/issue/createmeta?projectKeys=${project.key}&issuetypeNames=${issueType.name}&expand=projects.issuetypes.fields`));
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].schema.type) &&
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
- newIssue.fields[field.key] = await Ask.askString(`${field.name}:`);
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
- if (jira.fields && jira.fields.length > 0 &&
104
- await Ask.askBoolean('Do you want to set custom fields?')) {
105
- for (let fieldName of jira.fields) {
106
- await Set.setCustomField(jira, fieldName, issue.key);
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
 
@@ -20,6 +20,11 @@ class ErrorHandler {
20
20
  color: "blue",
21
21
  text: error
22
22
  }]));
23
+ } else if ("message" in e.error) {
24
+ table.addRow([{
25
+ color: "blue",
26
+ text: e.error.message
27
+ }]);
23
28
  } else {
24
29
  table.addRow([{
25
30
  color: "blue",
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 Field.listFields(jira);
20
-
21
- const table = new Table({
22
- head: ['Name', 'Supported', 'Type']
23
- });
24
-
25
- resultFields.forEach(field => {
26
- const supported = Field.isSupported(field.schema?.type);
27
- table.addRow([{
28
- color: "blue",
29
- text: field.name
30
- }, supported, supported ? field.schema?.type : ""]);
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 resultFields = await Field.listFields(jira);
42
-
43
- const fieldData = resultFields.find(field => field.name === fieldName);
44
- if (!fieldData) {
45
- console.log("Unknown field.");
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
- if (!Field.isSupported(fieldData.schema?.type)) {
50
- console.log("Unsupported field.");
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
- jira.addField(fieldName);
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
- if (!jira.fields.includes(fieldName)) {
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(fieldName => table.addRow([{
106
+ jira.fields.forEach(field => table.addRow([{
87
107
  color: "blue",
88
- text: fieldName
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(fieldType) {
99
- return ["string", "number"].includes(fieldType);
100
- }
123
+ static isSupported(fieldData) {
124
+ if (["string", "number"].includes(fieldData.schema?.type)) {
125
+ return true;
126
+ }
101
127
 
102
- static async askFieldIfSupported(jira, fieldName) {
103
- const resultFields = await Field.listFields(jira);
128
+ if ("allowedValues" in fieldData) {
129
+ return true;
130
+ }
104
131
 
105
- let fieldData;
106
- resultFields.forEach(field => {
107
- if (field.name === fieldName) fieldData = field;
108
- });
132
+ return false;
133
+ }
109
134
 
110
- if (!fieldData) {
111
- console.log(`Unable to find the field "${fieldName}"`);
135
+ static fieldValue(field, fieldData) {
136
+ if (!Field.isSupported(fieldData)) {
112
137
  return null;
113
138
  }
114
139
 
115
- if (!Field.isSupported(fieldData.schema?.type)) {
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(`${fieldName}:`), key: fieldData.key
176
+ value: await Ask.askNumber(`${fieldData.name}:`), key: fieldData.key
125
177
  };
126
178
  case 'string':
127
179
  return {
128
- value: await Ask.askString(`${fieldName}:`), key: fieldData.key
180
+ value: await Ask.askString(`${fieldData.name}:`), key: fieldData.key
129
181
  };
130
182
  }
131
183
 
132
- return null;
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.forEach(fieldName => table.addRow([fieldName, issue.fields[fieldName] || "unset"]));
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.push(fieldName);
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) || this._config.fields.indexOf(fieldName) === -1) return;
47
+ removeField(projectName, issueTypeName, fieldName) {
48
+ if (!Array.isArray(this._config.fields)) return;
42
49
 
43
- this._config.fields.splice(this._config.fields.indexOf(fieldName), 1);
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
- issues.forEach(issue => table.addRow([{
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
- text: issue.key
115
+ style: issue.incompleted ? "italic" : "normal",
116
+ text: pre + issue.issue.key
45
117
  }, {
46
118
  color: "green",
47
- text: issue.fields.status.name
119
+ style: issue.incompleted ? "italic" : "normal",
120
+ text: issue.issue.fields['Status'].name
48
121
  }, {
49
122
  color: "green",
50
- text: issue.fields.issuetype.name
123
+ style: issue.incompleted ? "italic" : "normal",
124
+ text: issue.issue.fields['Issue Type'].name
51
125
  }, {
52
126
  color: "yellow",
53
- text: Issue.showUser(issue.fields.assignee)
54
- },
55
- issue.fields.summary
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('<id>', 'The issue ID')
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('<id>', 'The issue ID')
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('Updating the issue...', jira.api.updateIssue(id, issue));
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('<id>', 'The issue ID')
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
- await Set.setCustomField(jira, fieldName, id);
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('Updating the issue...', jira.api.updateIssue(id, issue));
90
+ await jira.spin(`Updating issue ${id}...`, jira.api.updateIssue(id, issue));
76
91
  }
77
92
 
78
- static async setCustomField(jira, fieldName, id) {
79
- const field = await Field.askFieldIfSupported(jira, fieldName);
80
- if (!field) {
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('Updating the issue...', jira.api.updateIssue(id, {
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 transitionId = await Ask.askList('Status:',
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.id
115
+ value: transition
101
116
  })));
117
+
102
118
  const transition = {
103
119
  transition: {
104
- id: transitionId
120
+ id: transitionData.id
105
121
  }
106
122
  };
107
123
 
108
- await jira.spin('Updating the issue...', jira.api.transitionIssue(id, transition));
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._colorize(column.rows[row], this._toWidth(this._computeLine(column.rows[row], i), column.width))).join(" "));
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 < width) return [row];
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 column of this._columns) {
146
- if (column.width === max) {
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;