bjira 0.0.11 → 0.0.15

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/aqtinstall.log ADDED
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bjira",
3
- "version": "0.0.11",
3
+ "version": "0.0.15",
4
4
  "description": "A simple jira CLI tool",
5
5
  "main": "src/index.js",
6
6
  "author": {
@@ -26,7 +26,9 @@
26
26
  "dependencies": {
27
27
  "commander": "^8.2.0",
28
28
  "execa": "^5.1.1",
29
+ "fuzzy": "^0.1.3",
29
30
  "inquirer": "^8.2.0",
31
+ "inquirer-autocomplete-prompt": "^1.4.0",
30
32
  "jira-client": "^6.22.0",
31
33
  "ora": "^6.0.1",
32
34
  "temp": "^0.9.4"
package/src/ask.js CHANGED
@@ -3,6 +3,10 @@
3
3
  // SPDX-License-Identifier: MIT
4
4
 
5
5
  import inquirer from 'inquirer';
6
+ import inquirerAutocompletePrompt from 'inquirer-autocomplete-prompt';
7
+ import fuzzy from 'fuzzy';
8
+
9
+ inquirer.registerPrompt('autocomplete', inquirerAutocompletePrompt);
6
10
 
7
11
  class Ask {
8
12
  static async askString(message, defaultValue = undefined) {
@@ -48,11 +52,12 @@ class Ask {
48
52
 
49
53
  static async askList(message, list) {
50
54
  const answer = await inquirer.prompt([{
51
- type: 'list',
55
+ type: 'autocomplete',
52
56
  name: 'value',
53
57
  message,
54
- choices: list,
55
- filter: name => list.indexOf(name),
58
+ source: (answers, input) => fuzzy.filter(input || '', list, {
59
+ extract: el => el.name
60
+ }).map(el => el.original),
56
61
  }]);
57
62
  return answer.value;
58
63
  }
package/src/create.js CHANGED
@@ -6,9 +6,11 @@ import color from 'chalk';
6
6
 
7
7
  import Ask from './ask.js';
8
8
  import Command from './command.js';
9
+ import Field from './field.js';
9
10
  import Jira from './jira.js';
10
11
  import Issue from './issue.js';
11
12
  import Project from './project.js';
13
+ import Query from './query.js';
12
14
  import Set from './set.js';
13
15
  import Sprint from './sprint.js';
14
16
  import User from './user.js';
@@ -22,9 +24,17 @@ class Create extends Command {
22
24
  const jira = new Jira(program);
23
25
 
24
26
  const project = await Project.pickProject(jira);
25
- const issueTypePos = await Ask.askList('Issue type:', project.issueTypes.map(issueType => issueType.name));
27
+ const issueType = await Ask.askList('Issue type:',
28
+ project.issueTypes.map(issueType => ({
29
+ name: issueType.name,
30
+ value: {
31
+ name: issueType.name,
32
+ id: issueType.id
33
+ }
34
+ })));
26
35
 
27
- // Create the issue object
36
+ const meta = await jira.spin('Retrieving issue metadata...',
37
+ jira.apiRequest(`/issue/createmeta?projectKeys=${project.key}&issuetypeNames=${issueType.name}&expand=projects.issuetypes.fields`));
28
38
  const newIssue = {
29
39
  fields: {
30
40
  project: {
@@ -32,7 +42,7 @@ class Create extends Command {
32
42
  },
33
43
  summary: await Ask.askString('Summary:', 'New Issue'),
34
44
  issuetype: {
35
- id: project.issueTypes[issueTypePos].id
45
+ id: issueType.id
36
46
  }
37
47
  }
38
48
  };
@@ -44,11 +54,33 @@ class Create extends Command {
44
54
  }
45
55
  }
46
56
 
47
- if (project.issueTypes[issueTypePos].name === 'Task') {
48
- const parentIssue = await Ask.askString('Please provide the epic:');
49
- if (parentIssue !== '') {
57
+ const issueFields = meta.projects.find(p => p.key === project.key)
58
+ .issuetypes.find(i => i.name === issueType.name).fields;
59
+ const requiredFields = Object.keys(issueFields).filter(
60
+ key => issueFields[key].required &&
61
+ Field.isSupported(issueFields[key].schema.type) &&
62
+ !["summary", "description", "project"].includes(key)).map(key => issueFields[key]);
63
+
64
+ for (const field of requiredFields) {
65
+ newIssue.fields[field.key] = await Ask.askString(`${field.name}:`);
66
+ }
67
+
68
+ if (issueType.name !== 'Epic' &&
69
+ await Ask.askBoolean('Do you want to set a parent epic?')) {
70
+ const result = await Query.runQuery(jira, `project = "${project.key}" and type = "epic"`, 999999);
71
+ if (!result.issues || result.issues.length === 0) {
72
+ console.log("No epics yet.");
73
+ } else {
74
+ const resultFields = await Field.listFields(jira);
75
+ result.issues.forEach(epic => Issue.replaceFields(epic, resultFields));
76
+
77
+ const parentKey = await Ask.askList('Choose the epic:',
78
+ result.issues.map(epic => ({
79
+ name: `${epic.key} - ${epic.fields['Summary']}`,
80
+ value: epic.key
81
+ })));
50
82
  newIssue.fields.parent = {
51
- key: parentIssue
83
+ key: parentKey
52
84
  };
53
85
  }
54
86
  }
@@ -10,10 +10,23 @@ class ErrorHandler {
10
10
  head: ['Errors']
11
11
  });
12
12
 
13
- e.error.errorMessages.forEach(error => table.addRow([{
14
- color: "blue",
15
- text: error
16
- }]));
13
+ if (typeof e.error === "string") {
14
+ table.addRow([{
15
+ color: "blue",
16
+ text: e.error
17
+ }]);
18
+ } else if ("errorMessages" in e.error) {
19
+ e.error.errorMessages.forEach(error => table.addRow([{
20
+ color: "blue",
21
+ text: error
22
+ }]));
23
+ } else {
24
+ table.addRow([{
25
+ color: "blue",
26
+ text: e.error
27
+ }]);
28
+ }
29
+
17
30
  console.log(table.toString());
18
31
  }
19
32
 
package/src/field.js CHANGED
@@ -125,7 +125,7 @@ class Field extends Command {
125
125
  };
126
126
  case 'string':
127
127
  return {
128
- value: Ask.askString(`${fieldName}:`), key: fieldData.key
128
+ value: await Ask.askString(`${fieldName}:`), key: fieldData.key
129
129
  };
130
130
  }
131
131
 
package/src/issue.js CHANGED
@@ -17,7 +17,7 @@ class Issue extends Command {
17
17
  addOptions(program) {
18
18
  const cmd = program.command('show')
19
19
  .description('Show an issue')
20
- .option('-C, --comments')
20
+ .option('-C, --comments', 'Show the comments too')
21
21
  .argument('<id>', 'The issue ID')
22
22
  .action(async id => {
23
23
  const jira = new Jira(program);
package/src/project.js CHANGED
@@ -35,23 +35,38 @@ class Project extends Command {
35
35
  jira.apiRequest('/issue/createmeta'));
36
36
 
37
37
  meta.projects.sort((a, b) => {
38
- if (a.key === jira.latestProject) {
38
+ if (a.name < b.name) {
39
39
  return -1;
40
40
  }
41
41
 
42
- if (b.key === jira.latestProject) {
42
+ if (a.name > b.name) {
43
43
  return 1;
44
44
  }
45
45
 
46
- return a.name > b.name;
46
+ return 0;
47
47
  });
48
48
 
49
- const projectPos = await Ask.askList('Project:', meta.projects.map(project => project.name));
50
- const project = meta.projects[projectPos];
49
+ const projects = [];
50
+ if (jira.latestProject) {
51
+ const latestProject = meta.projects.find(project => project.key === jira.latestProject);
52
+ if (latestProject) {
53
+ projects.push({
54
+ name: `Latest: ${latestProject.name}`,
55
+ value: latestProject.key
56
+ });
57
+ }
58
+ }
59
+
60
+ meta.projects.forEach(project => projects.push({
61
+ name: project.name,
62
+ value: project.key
63
+ }));
51
64
 
52
- jira.latestProject = project.key;
65
+ const projectKey = await Ask.askList('Project:', projects);
66
+ jira.latestProject = projectKey;
53
67
  jira.syncConfig();
54
68
 
69
+ const project = meta.projects.find(project => project.key === projectKey);
55
70
  return {
56
71
  name: project.name,
57
72
  key: project.key,
package/src/query.js CHANGED
@@ -9,6 +9,8 @@ import Issue from './issue.js';
9
9
  import Jira from './jira.js';
10
10
  import Table from './table.js';
11
11
 
12
+ import color from 'chalk';
13
+
12
14
  const DEFAULT_QUERY_LIMIT = 20;
13
15
 
14
16
  class Query extends Command {
@@ -24,31 +26,15 @@ class Query extends Command {
24
26
  const opts = cmd.opts();
25
27
 
26
28
  const resultFields = await Field.listFields(jira);
29
+ const result = await Query.runQuery(jira, query, opts.limit);
27
30
 
28
- let expectedResult = opts.limit;
29
- let issues = [];
30
-
31
- while (issues.length < opts.limit) {
32
- const result = await jira.spin('Running query...',
33
- jira.api.searchJira(query, {
34
- startAt: issues.lengh,
35
- maxResults: opts.limit - issues.length
36
- }));
37
-
38
- if (result.warningMessages) {
39
- ErrorHandler.showWarningMessages(result.warningMessages);
40
- return;
41
- }
42
-
43
- issues = issues.concat(result.issues);
44
- if (issues.length >= result.total) break;
45
- }
46
-
47
- Query.showIssues(jira, issues, resultFields);
31
+ Query.showIssues(jira, result.issues, result.total, resultFields);
48
32
  });
49
33
  }
50
34
 
51
- static showIssues(jira, issues, fields) {
35
+ static showIssues(jira, issues, total, fields) {
36
+ console.log(`Showing ${color.bold(issues.length)} issues of ${color.bold(total)}`);
37
+
52
38
  const table = new Table({
53
39
  head: ['Key', 'Status', 'Type', 'Assignee', 'Summary']
54
40
  });
@@ -71,6 +57,33 @@ class Query extends Command {
71
57
 
72
58
  console.log(table.toString());
73
59
  }
60
+
61
+ static async runQuery(jira, query, expectedResult) {
62
+ let issues = [];
63
+ let total = 0;
64
+ while (issues.length < (expectedResult === undefined ? issues.length + 1 : expectedResult)) {
65
+ const result = await jira.spin('Running query...',
66
+ jira.api.searchJira(query, {
67
+ startAt: issues.lengh,
68
+ maxResults: expectedResult - issues.length
69
+ }));
70
+
71
+ if (result.warningMessages) {
72
+ ErrorHandler.showWarningMessages(result.warningMessages);
73
+ return;
74
+ }
75
+
76
+ total = result.total;
77
+ issues = issues.concat(result.issues);
78
+
79
+ if (issues.length >= total) break;
80
+ }
81
+
82
+ return {
83
+ total,
84
+ issues
85
+ };
86
+ }
74
87
  };
75
88
 
76
89
  export default Query;
package/src/run.js CHANGED
@@ -44,27 +44,9 @@ class Run extends Command {
44
44
  }
45
45
 
46
46
  const resultFields = await Field.listFields(jira);
47
+ const result = await Query.runQuery(jira, query, opts.limit);
47
48
 
48
- let expectedResult = opts.limit;
49
- let issues = [];
50
-
51
- while (issues.length < opts.limit) {
52
- const result = await jira.spin('Running query...',
53
- jira.api.searchJira(query, {
54
- startAt: issues.lengh,
55
- maxResults: opts.limit - issues.length
56
- }));
57
-
58
- if (result.warningMessages) {
59
- ErrorHandler.showWarningMessages(result.warningMessages);
60
- return;
61
- }
62
-
63
- issues = issues.concat(result.issues);
64
- if (issues.length >= result.total) break;
65
- }
66
-
67
- Query.showIssues(jira, issues, resultFields);
49
+ Query.showIssues(jira, result.issues, result.total, resultFields);
68
50
  });
69
51
  }
70
52
 
package/src/set.js CHANGED
@@ -58,12 +58,16 @@ class Set extends Command {
58
58
  static async assignIssue(jira, id) {
59
59
  const userList = await User.pickUser(jira);
60
60
  const activeUsers = userList.filter(user => user.active);
61
- const assignee = await Ask.askList('Assignee:', activeUsers.map(user => user.displayName));
61
+ const assigneeId = await Ask.askList('Assignee:',
62
+ activeUsers.map(user => ({
63
+ name: user.displayName,
64
+ value: user.accountId
65
+ })));
62
66
 
63
67
  const issue = {
64
68
  fields: {
65
69
  assignee: {
66
- accountId: activeUsers[assignee].accountId
70
+ accountId: assigneeId
67
71
  }
68
72
  }
69
73
  }
@@ -90,10 +94,14 @@ class Set extends Command {
90
94
 
91
95
  static async setStatus(jira, id) {
92
96
  const transitionList = await jira.spin('Retrieving transitions...', jira.api.listTransitions(id));
93
- const transitionPos = await Ask.askList('Status:', transitionList.transitions.map(transition => transition.name));
97
+ const transitionId = await Ask.askList('Status:',
98
+ transitionList.transitions.map(transition => ({
99
+ name: transition.name,
100
+ value: transition.id
101
+ })));
94
102
  const transition = {
95
103
  transition: {
96
- id: transitionList.transitions[transitionPos].id
104
+ id: transitionId
97
105
  }
98
106
  };
99
107
 
package/src/sprint.js CHANGED
@@ -2,10 +2,16 @@
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';
9
+ import Field from './field.js';
10
+ import Issue from './issue.js';
7
11
  import Jira from './jira.js';
8
12
  import Project from './project.js';
13
+ import Table from './table.js';
14
+ import User from './user.js';
9
15
 
10
16
  class Sprint extends Command {
11
17
  addOptions(program) {
@@ -34,6 +40,108 @@ class Sprint extends Command {
34
40
  }
35
41
  }));
36
42
  });
43
+
44
+ const showCmd = sprintCmd.command('show')
45
+ .description('Show the sprint')
46
+ .option('-a, --all', 'Show all the users')
47
+ .action(async () => {
48
+ const jira = new Jira(program);
49
+ const data = await Sprint.pickSprint(jira);
50
+ if (data === null) {
51
+ console.log("No active sprints");
52
+ return;
53
+ }
54
+
55
+ const resultFields = await Field.listFields(jira);
56
+
57
+ let issues = [];
58
+ while (true) {
59
+ const results = await jira.spin('Retrieving issues...',
60
+ jira.api.getBoardIssuesForSprint(data.boardId, data.sprintId, issues.length));
61
+ issues = issues.concat(results.issues);
62
+ if (issues.length >= results.total) break;
63
+ }
64
+
65
+ if (issues.length === 0) {
66
+ console.log("No issues");
67
+ return;
68
+ }
69
+
70
+ const statuses = [];
71
+
72
+ const users = [];
73
+ issues.forEach(issue => {
74
+ issue = Issue.replaceFields(issue, resultFields);
75
+
76
+ const accountId = issue.fields['Assignee'] ? issue.fields['Assignee'].accountId : null;
77
+ let user = users.find(user => user.accountId === accountId);
78
+ if (!user) {
79
+ user = {
80
+ accountId,
81
+ displayName: issue.fields['Assignee']?.displayName,
82
+ user: issue.fields['Assignee'],
83
+ statuses: []
84
+ };
85
+ users.push(user);
86
+ }
87
+
88
+ if (!statuses.includes(issue.fields['Status'].name)) {
89
+ statuses.push(issue.fields['Status'].name);
90
+ }
91
+
92
+ let status = user.statuses.find(status => issue.fields['Status'].name === status.name);
93
+ if (!status) {
94
+ status = {
95
+ name: issue.fields['Status'].name,
96
+ issues: []
97
+ };
98
+ user.statuses.push(status);
99
+ }
100
+
101
+ status.issues.push(issue);
102
+ });
103
+
104
+ const currentUser = await jira.spin('Retrieving current user...', jira.api.getCurrentUser());
105
+ const transitionList = await jira.spin('Retrieving transitions...', jira.api.listTransitions(issues[0].id));
106
+
107
+ console.log('\n' + color.blue(data.sprintName) + '\n');
108
+
109
+ let filteredUsers;
110
+ if (showCmd.opts().all) {
111
+ filteredUsers = User.sortUsers(currentUser, users);
112
+ } else {
113
+ filteredUsers = [users.find(user => user.accountId === currentUser.accountId)];
114
+ }
115
+
116
+ filteredUsers.forEach(user => {
117
+ console.log(color.yellow(Issue.showUser(user.user)));
118
+
119
+ const maxIssues = Math.max(...user.statuses.map(status => status.issues.length));
120
+
121
+ const statuses = user.statuses.sort((a, b) => {
122
+ const posA = transitionList.transitions.findIndex(transition => transition.name === a.name);
123
+ const posB = transitionList.transitions.findIndex(transition => transition.name === b.name);
124
+ return posA - posB;
125
+ });
126
+
127
+ const table = new Table({
128
+ head: statuses.map(status => status.name),
129
+ });
130
+
131
+ for (let i = 0; i < maxIssues; ++i) {
132
+ const line = statuses.map(status => {
133
+ if (status.issues.length > i) {
134
+ return `${status.issues[i].key} ${status.issues[i].fields['Summary'].trim()}`
135
+ }
136
+ return ""
137
+ });
138
+ table.addRow(line);
139
+ }
140
+
141
+ console.log(table.toString() + '\n');
142
+ });
143
+
144
+ });
37
145
  }
38
146
 
39
147
  static async pickSprint(jira) {
@@ -42,34 +150,53 @@ class Sprint extends Command {
42
150
  jira.api.getAllBoards(undefined, undefined, undefined, undefined,
43
151
  project.key));
44
152
 
45
- const boardPos = await Ask.askList('Board:', boardList.values.map(board => board.name));
153
+ const boardId = await Ask.askList('Board:',
154
+ boardList.values.map(board => ({
155
+ name: board.name,
156
+ value: board.id
157
+ })));
46
158
 
47
159
  const sprintList = await jira.spin('Retrieving sprints...',
48
- jira.api.getAllSprints(boardList.values[boardPos].id));
160
+ jira.api.getAllSprints(boardId));
49
161
 
50
162
  const sprints = sprintList.values.filter(sprint => sprint.state === 'active' || sprint.state === 'future');
51
163
 
52
164
  if (sprints.length === 0) {
53
- return 0;
165
+ return null;
54
166
  }
55
167
 
56
168
  if (sprints.length > 1) {
57
- const sprintPos = await Ask.askList('Sprint:', sprints.map(sprint => sprint.name));
58
- return sprints[sprintPos].id;
169
+ const sprintData = await Ask.askList('Sprint:',
170
+ sprints.map(sprint => ({
171
+ name: sprint.name,
172
+ value: {
173
+ id: sprint.id,
174
+ name: sprint.name
175
+ }
176
+ })));
177
+ return {
178
+ boardId,
179
+ sprintId: sprintData.id,
180
+ sprintName: sprintData.name,
181
+ };
59
182
  }
60
183
 
61
- return sprints[0].id;
184
+ return {
185
+ boardId,
186
+ sprintId: sprints[0].id,
187
+ sprintName: sprints[0].name,
188
+ };
62
189
  }
63
190
 
64
191
  static async add(jira, id) {
65
- const sprintId = await Sprint.pickSprint(jira);
66
- if (sprintId === 0) {
192
+ const data = await Sprint.pickSprint(jira);
193
+ if (data === null) {
67
194
  console.log("No active sprints");
68
195
  return;
69
196
  }
70
197
 
71
198
  await jira.spin('Adding the issue to the sprint...',
72
- jira.apiAgileRequest(`/sprint/${sprintId}/issue`, {
199
+ jira.apiAgileRequest(`/sprint/${data.sprintId}/issue`, {
73
200
  method: 'POST',
74
201
  followAllRedirects: true,
75
202
  body: {
package/src/user.js CHANGED
@@ -8,7 +8,10 @@ class User {
8
8
  static async pickUser(jira) {
9
9
  const currentUser = await jira.spin('Retrieving current user...', jira.api.getCurrentUser());
10
10
  const userList = await jira.spin('Retrieving users...', jira.api.getUsers(0, 1000));
11
+ return User.sortUsers(currentUser, userList);
12
+ }
11
13
 
14
+ static sortUsers(currentUser, userList) {
12
15
  userList.sort((a, b) => {
13
16
  if (a.accountId === currentUser.accountId) {
14
17
  return -1;
@@ -18,7 +21,15 @@ class User {
18
21
  return 1;
19
22
  }
20
23
 
21
- return a.displayName > b.displayName;
24
+ if (a.displayName < b.displayName) {
25
+ return -1;
26
+ }
27
+
28
+ if (a.displayName > b.displayName) {
29
+ return 1;
30
+ }
31
+
32
+ return 0;
22
33
  });
23
34
 
24
35
  return userList;