├── .eslintrc.js
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ ├── custom.md
│ └── feature_request.md
└── workflows
│ └── codeql-analysis.yml
├── .gitignore
├── .prettierrc
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── _config.yml
├── assets
├── assign_issues_1.png
├── assign_issues_2.png
├── board_subcommands.png
├── clear_1.png
├── clear_2.png
├── details.png
├── login_preview.png
├── preview.png
└── task_subcommands.png
├── azure-pipelines.yml
├── package-lock.json
├── package.json
└── src
├── api
├── IssueApis.js
├── JqlClient.js
├── board.js
├── questions.js
├── task.js
└── user.js
├── commands
├── Tasks.js
└── boards.js
├── index.js
├── operations
├── AssignIssue.js
├── PrintIIssue.js
├── Task.js
└── boards.js
├── services
├── AuthServices.js
└── AxiosService.js
├── store.js
├── url.js
└── utility
├── console.js
└── utils.js
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | browser: true,
4 | commonjs: true,
5 | es2021: true,
6 | },
7 | };
8 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Desktop (please complete the following information):**
27 | - OS: [e.g. iOS]
28 | - Browser [e.g. chrome, safari]
29 | - Version [e.g. 22]
30 |
31 | **Smartphone (please complete the following information):**
32 | - Device: [e.g. iPhone6]
33 | - OS: [e.g. iOS8.1]
34 | - Browser [e.g. stock browser, safari]
35 | - Version [e.g. 22]
36 |
37 | **Additional context**
38 | Add any other context about the problem here.
39 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/custom.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Custom issue template
3 | about: Describe this issue template's purpose here.
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.github/workflows/codeql-analysis.yml:
--------------------------------------------------------------------------------
1 | # For most projects, this workflow file will not need changing; you simply need
2 | # to commit it to your repository.
3 | #
4 | # You may wish to alter this file to override the set of languages analyzed,
5 | # or to provide custom queries or build logic.
6 | #
7 | # ******** NOTE ********
8 | # We have attempted to detect the languages in your repository. Please check
9 | # the `language` matrix defined below to confirm you have the correct set of
10 | # supported CodeQL languages.
11 | #
12 | name: "CodeQL"
13 |
14 | on:
15 | push:
16 | branches: [ master, update-*, v-*]
17 | pull_request:
18 | # The branches below must be a subset of the branches above
19 | branches: [ master ]
20 | schedule:
21 | - cron: '35 21 * * 3'
22 |
23 | jobs:
24 | analyze:
25 | name: Analyze
26 | runs-on: ubuntu-latest
27 | permissions:
28 | actions: read
29 | contents: read
30 | security-events: write
31 |
32 | strategy:
33 | fail-fast: false
34 | matrix:
35 | language: [ 'javascript' ]
36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
37 | # Learn more about CodeQL language support at https://git.io/codeql-language-support
38 |
39 | steps:
40 | - name: Checkout repository
41 | uses: actions/checkout@v2
42 |
43 | # Initializes the CodeQL tools for scanning.
44 | - name: Initialize CodeQL
45 | uses: github/codeql-action/init@v1
46 | with:
47 | languages: ${{ matrix.language }}
48 | # If you wish to specify custom queries, you can do so here or in a config file.
49 | # By default, queries listed here will override any specified in a config file.
50 | # Prefix the list here with "+" to use these queries and those in the config file.
51 | # queries: ./path/to/local/query, your-org/your-repo/queries@main
52 |
53 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
54 | # If this step fails, then you should remove it and run the build manually (see below)
55 | - name: Autobuild
56 | uses: github/codeql-action/autobuild@v1
57 |
58 | # ℹ️ Command-line programs to run using the OS shell.
59 | # 📚 https://git.io/JvXDl
60 |
61 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
62 | # and modify them (or add more) to build your code if your project
63 | # uses a compiled language
64 |
65 | #- run: |
66 | # make bootstrap
67 | # make release
68 |
69 | - name: Perform CodeQL Analysis
70 | uses: github/codeql-action/analyze@v1
71 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | /.DS_Store
3 | src/.DS_Store
4 | .DS_Store
5 | src/test.js
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "proseWrap": "always",
3 | "tabWidth": 4,
4 | "printWidth": 80,
5 | "bracketSpacing": true,
6 | "arrowParens": "avoid",
7 | "singleQuote": true
8 | }
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # How to contribute to Jirax
2 |
3 | Thanks for your interest on contributing to Jirax ! Here are a few general guidelines on contributing and reporting bugs to Jirax that we ask you to take a look first. Notice that all of your interactions in the project are expected to follow our [Code of Conduct](#code-of-conduct).
4 |
5 |
6 | When contributing to this repository, please first discuss the change you wish to make via issue,
7 | email, or any other method with the owners of this repository before making a change.
8 |
9 | Please note we have a code of conduct, please follow it in all your interactions with the project.
10 |
11 | ## Pull Request Process
12 |
13 | 1. Ensure any install or build dependencies are removed before the end of the layer when doing a
14 | build.
15 | 2. Update the README.md with details of changes to the interface, this includes new environment
16 | variables, exposed ports, useful file locations and container parameters.
17 | 3. Increase the version numbers in any examples files and the README.md to the new version that this
18 | Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/).
19 | 4. You may merge the Pull Request in once you have the sign-off of two other developers, or if you
20 | do not have permission to do that, you may request the second reviewer to merge it for you.
21 |
22 | Thanks again for your interest on contributing to the project!
23 |
24 | ❤️
25 |
26 | ## Code of Conduct
27 |
28 | ### Our Pledge
29 |
30 | In the interest of fostering an open and welcoming environment, we as
31 | contributors and maintainers pledge to making participation in our project and
32 | our community a harassment-free experience for everyone, regardless of age, body
33 | size, disability, ethnicity, gender identity and expression, level of experience,
34 | nationality, personal appearance, race, religion, or sexual identity and
35 | orientation.
36 |
37 | ### Our Standards
38 |
39 | Examples of behavior that contributes to creating a positive environment
40 | include:
41 |
42 | * Using welcoming and inclusive language
43 | * Being respectful of differing viewpoints and experiences
44 | * Gracefully accepting constructive criticism
45 | * Focusing on what is best for the community
46 | * Showing empathy towards other community members
47 |
48 | Examples of unacceptable behavior by participants include:
49 |
50 | * The use of sexualized language or imagery and unwelcome sexual attention or
51 | advances
52 | * Trolling, insulting/derogatory comments, and personal or political attacks
53 | * Public or private harassment
54 | * Publishing others' private information, such as a physical or electronic
55 | address, without explicit permission
56 | * Other conduct which could reasonably be considered inappropriate in a
57 | professional setting
58 |
59 | ### Our Responsibilities
60 |
61 | Project maintainers are responsible for clarifying the standards of acceptable
62 | behavior and are expected to take appropriate and fair corrective action in
63 | response to any instances of unacceptable behavior.
64 |
65 | Project maintainers have the right and responsibility to remove, edit, or
66 | reject comments, commits, code, wiki edits, issues, and other contributions
67 | that are not aligned to this Code of Conduct, or to ban temporarily or
68 | permanently any contributor for other behaviors that they deem inappropriate,
69 | threatening, offensive, or harmful.
70 |
71 | ### Scope
72 |
73 | This Code of Conduct applies both within project spaces and in public spaces
74 | when an individual is representing the project or its community. Examples of
75 | representing a project or community include using an official project e-mail
76 | address, posting via an official social media account, or acting as an appointed
77 | representative at an online or offline event. Representation of a project may be
78 | further defined and clarified by project maintainers.
79 |
80 | ### Enforcement
81 |
82 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
83 | reported by contacting the project team at [INSERT EMAIL ADDRESS]. All
84 | complaints will be reviewed and investigated and will result in a response that
85 | is deemed necessary and appropriate to the circumstances. The project team is
86 | obligated to maintain confidentiality with regard to the reporter of an incident.
87 | Further details of specific enforcement policies may be posted separately.
88 |
89 | Project maintainers who do not follow or enforce the Code of Conduct in good
90 | faith may face temporary or permanent repercussions as determined by other
91 | members of the project's leadership.
92 |
93 | ### Attribution
94 |
95 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
96 | available at [http://contributor-covenant.org/version/1/4][version]
97 |
98 | [homepage]: http://contributor-covenant.org
99 | [version]: http://contributor-covenant.org/version/1/4/
100 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Junip Dewan
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # jirax
2 | []() []() []() [](https://forthebadge.com)
3 |
4 |
5 |
6 |
7 | [](https://dev.azure.com/junipd2/jirax/_build/latest?definitionId=4&branchName=master)    [](https://github.com/prettier/prettier) [](LICENSE) [](http://makeapullrequest.com)
8 |
9 | ```
10 | ```
11 |
12 | > :star: If you are using this tool or you like it, Star on GitHub — it helps!
13 |
14 |
15 |
16 | A CLI tool for [JIRA](`https://www.atlassian.com/software/jira`) for day to day usage with JIRA.Speed up your JIRA activity with this CLI tool.
17 |
18 |
19 |
20 | ## Prerequisites
21 |
22 | You are required to have [Node.js](https://nodejs.org/) installed to run the cli tool or after installing [Node.js](https://nodejs.org/) you can make executable and run the excutable to use the tool. [Make executable](#making-executable)
23 |
24 | ## Getting Started
25 |
26 | 1. Log in to [Atlassian](https://id.atlassian.com/manage/api-tokens) and generate your API TOKEN.
27 | 2. Copy the API TOKEN
28 |
29 | ## Install Using NPM
30 |
31 | You can use directly install the package using
32 | [NPM](https://www.npmjs.com/package/jirax) or [YARN](https://yarnpkg.com/en/package/jirax)
33 |
34 | ```sh
35 | npm install -g jirax
36 | ```
37 | ```
38 | yarn add jirax
39 | ```
40 |
41 | ### use the below command to get started
42 |
43 | ```sh
44 | npx jirax
45 | ```
46 |
47 | ## By Cloning the repository
48 |
49 | Install all dependency
50 |
51 |
52 | ```sh
53 | npm install
54 | ```
55 |
56 | ### Create the symlink. This command will help you execute `jirax` commands at global level
57 |
58 |
59 | ```
60 | npm link or sudo npm link
61 | ```
62 |
63 | # Usages
64 |
65 | ## Login In Cli
66 |
67 | You need to login before using any of JIRAX features.
68 |
69 | ```sh
70 | jirax -l
71 | ```
72 |
73 | This will prompt few questions to enter your credentials please enter the credentials to use the CLI.)
74 |
75 | ```sh
76 | $ Your JIRA Host Name (eg: something.atlassian.net)
77 | $ Your JIRA User Name
78 | $ Your API Token
79 | ```
80 | It will authenticate with JIRA Server and after successfull login, your name will prompt with message
81 |
82 | 
83 |
84 | Your Login details will be stored in a JSON file located in
85 |
86 | ```sh
87 | $XDG_CONFIG_HOME or ~/.config.
88 |
89 | # access it
90 | ~/.config/configstore/jiraconfig.json
91 |
92 | ```
93 |
94 |
95 | ## JIRAX Commands
96 |
97 | Jirax CLI tool is madeup with the subcommands for various jira activities.
98 |
99 | ```sh
100 | # prints all available commands
101 | jirax --help
102 | ```
103 |
104 |
105 | #### For Subcommands options
106 |
107 | ```sh
108 | # prints all available commands for a specific subcommand
109 | jirax command --help
110 |
111 | ```
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 | ## Clear Stored Credentials
121 | In case of your API token revoked or you have revoked the previous API token then you need to clear the previous credentials using the following command and
122 | Relogin using command `jirax login`
123 |
124 | ```sh
125 | jirax logout
126 | ```
127 |
128 | ## Making executable
129 | After cloning the repository. Run this command this command will automatically create plateform specific executables.
130 |
131 | ```sh
132 | npm run build
133 | ```
134 |
135 | ## Contribution
136 |
137 | We hope that you will consider contributing to Jirax. Please read this short overview [Contribution Guidelines](https://github.com/junipdewan/jirax/blob/master/CONTRIBUTING.md) for some information about how to get started
138 |
139 | ## MIT License
140 |
141 | **jirax** is available under the **MIT license**. See the [LICENSE](https://github.com/junipdewan/jirax/blob/master/LICENSE) file for more info.
142 |
143 | Copyright (c) 2021
144 |
--------------------------------------------------------------------------------
/_config.yml:
--------------------------------------------------------------------------------
1 | theme: jekyll-theme-dinky
--------------------------------------------------------------------------------
/assets/assign_issues_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/junip/jirax/7d8f03ec44cb6cc4cd5f272c0d3822d98d59c9f5/assets/assign_issues_1.png
--------------------------------------------------------------------------------
/assets/assign_issues_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/junip/jirax/7d8f03ec44cb6cc4cd5f272c0d3822d98d59c9f5/assets/assign_issues_2.png
--------------------------------------------------------------------------------
/assets/board_subcommands.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/junip/jirax/7d8f03ec44cb6cc4cd5f272c0d3822d98d59c9f5/assets/board_subcommands.png
--------------------------------------------------------------------------------
/assets/clear_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/junip/jirax/7d8f03ec44cb6cc4cd5f272c0d3822d98d59c9f5/assets/clear_1.png
--------------------------------------------------------------------------------
/assets/clear_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/junip/jirax/7d8f03ec44cb6cc4cd5f272c0d3822d98d59c9f5/assets/clear_2.png
--------------------------------------------------------------------------------
/assets/details.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/junip/jirax/7d8f03ec44cb6cc4cd5f272c0d3822d98d59c9f5/assets/details.png
--------------------------------------------------------------------------------
/assets/login_preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/junip/jirax/7d8f03ec44cb6cc4cd5f272c0d3822d98d59c9f5/assets/login_preview.png
--------------------------------------------------------------------------------
/assets/preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/junip/jirax/7d8f03ec44cb6cc4cd5f272c0d3822d98d59c9f5/assets/preview.png
--------------------------------------------------------------------------------
/assets/task_subcommands.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/junip/jirax/7d8f03ec44cb6cc4cd5f272c0d3822d98d59c9f5/assets/task_subcommands.png
--------------------------------------------------------------------------------
/azure-pipelines.yml:
--------------------------------------------------------------------------------
1 | # Node.js
2 | # Build a general Node.js project with npm.
3 | # Add steps that analyze code, save build artifacts, deploy, and more:
4 | # https://docs.microsoft.com/azure/devops/pipelines/languages/javascript
5 |
6 | trigger:
7 | - master
8 |
9 | pool:
10 | vmImage: 'ubuntu-latest'
11 |
12 | steps:
13 | - task: NodeTool@0
14 | inputs:
15 | versionSpec: '10.x'
16 | displayName: 'Install Node.js'
17 | - task: CopyFiles@2
18 | inputs:
19 | SourceFolder: '$(System.DefaultWorkingDirectory)'
20 | Contents: |
21 | jirax-macos
22 | jirax-linux
23 | jirax-win.exe
24 | TargetFolder: '$(Build.ArtifactStagingDirectory)'
25 | - task: PublishBuildArtifacts@1
26 | inputs:
27 | PathtoPublish: '$(System.DefaultWorkingDirectory)'
28 |
29 | - script: |
30 | npm install
31 | npm run build
32 | displayName: 'npm install'
33 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jirax",
3 | "version": "2.0.1",
4 | "description": "A cli tool for daily JIRA activities",
5 | "author": "Junip Dewan ",
6 | "main": "index.js",
7 | "bin": {
8 | "jirax": "./src/index.js"
9 | },
10 | "scripts": {
11 | "test": "echo \"Error: no test specified\" && exit 1",
12 | "format": "prettier --write \"src/**/*.js\"",
13 | "build": "pkg .",
14 | "lint": "eslint"
15 | },
16 | "repository": {
17 | "type": "git",
18 | "url": "git+https://github.com/junipdewan/jirax.git"
19 | },
20 | "keywords": [
21 | "jira",
22 | "jira-cli",
23 | "jira-tool",
24 | "jira-cmd",
25 | "jirajs",
26 | "jira-in-terminal",
27 | "terminal",
28 | "console",
29 | "jira-shell",
30 | "jirax",
31 | "jira-cmd"
32 | ],
33 | "license": "MIT",
34 | "bugs": {
35 | "url": "https://github.com/junipdewan/jirax/issues"
36 | },
37 | "homepage": "https://github.com/junipdewan/jirax#readme",
38 | "devDependencies": {
39 | "eslint": "^7.32.0",
40 | "eslint-config-airbnb-base": "^14.2.1",
41 | "eslint-plugin-import": "^2.24.0",
42 | "prettier": "^1.17.1"
43 | },
44 | "pkg": {
45 | "assets": "./node_modules/figlet/fonts/Standard.flf"
46 | },
47 | "dependencies": {
48 | "axios": "^0.24.0",
49 | "chalk": "^2.4.2",
50 | "commander": "^8.2.0",
51 | "configstore": "^4.0.0",
52 | "dateformat": "^3.0.3",
53 | "figlet": "^1.2.1",
54 | "fuzzy": "^0.1.3",
55 | "inquirer": "^6.3.1",
56 | "inquirer-autocomplete-prompt": "^1.4.0",
57 | "jira-connector": "^3.1.0",
58 | "jira.js": "^2.8.0",
59 | "log-update": "^3.2.0",
60 | "minimist": "^1.2.0",
61 | "open": "^8.2.1",
62 | "ora": "^3.4.0",
63 | "pkg": "^4.4.0"
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/api/IssueApis.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Used to connect JIRA issue APIS
3 | */
4 | const open = require('open');
5 | const Configstore = require('configstore');
6 | const authService = require('../services/AuthServices');
7 | const util = require('../utility/utils');
8 | const consoleApi = require('../utility/console');
9 | const configStore = new Configstore('jiraconfig');
10 |
11 | module.exports = {
12 | /**
13 | * this method opens the issues for the given project key
14 | * @param {*} projectKey
15 | */
16 | openProjectIssues(projectKey) {
17 | const hostName = util.getHostName();
18 | const URL = `https://${hostName}/jira/software/c/projects/${projectKey}/issues`;
19 | open(URL);
20 | },
21 | // /**
22 | // * A method return array of transtions available for the specific issues
23 | // * @param options.issuekey isssue keys to which this
24 | // * @param {*} options
25 | // */
26 |
27 | assignIssue(issueKey, accountId, username) {
28 | const options = {
29 | issueKey,
30 | accountId
31 | };
32 | const spinner = util.spinner(
33 | `Assigning the issue ${issueKey} to ${username}`
34 | );
35 | spinner.start();
36 | authService.jiraConnector()
37 | .issue.assignIssue(options, (error, success) => {
38 | if (success || error) {
39 | spinner.stop();
40 | }
41 | if (error) {
42 | consoleApi.printError('Issue Cannnot be assigned');
43 | }
44 | if (success) {
45 | const message = `Issue ${consoleApi.chalkRed(
46 | issueKey
47 | )} ${consoleApi.chalkGreen(
48 | 'is assigned to'
49 | )} ${consoleApi.printbgCyan(username)}`;
50 | consoleApi.printInfo(message);
51 | }
52 | });
53 | },
54 | /**
55 | * Assign the issue to self i.e logged in user
56 | * @param issueKey
57 | * @param {*} issueKey
58 | */
59 | assignSelf(issueKey) {
60 | const accountId = configStore.get('accountId');
61 | const username = configStore.get('displayName');
62 | module.exports.assignIssue(issueKey, accountId, username);
63 | },
64 | // /**
65 | // * Get the issue statuses of project for the given issue
66 | // * @param {*} issueKey
67 | // */
68 |
69 | getStoredTranstions(issueKey, cb) {
70 | const key = issueKey.split('-')[0];
71 | const keyPresent = configStore.get(key);
72 | if (!keyPresent) {
73 | module.exports.getTranstions(issueKey, data => cb(data));
74 | } else {
75 | const transitions = configStore.get(key);
76 | return cb(transitions);
77 | }
78 | },
79 | // ------------------------------COMMENTS RELATED FUNCTIONS------------------>
80 | /**
81 | * Add comment to the issue
82 | * * { issueKey: 'SFMAC-19', comment: 'some comment'"}
83 | * @param {*} options
84 | */
85 | addComment(options) {
86 | const spinner = util.spinner('Posting your comment. Please wait');
87 | spinner.start();
88 | authService.jiraConnector()
89 | .issue.addComment(options, (error, response) => {
90 | if (response) {
91 | spinner.stop();
92 | consoleApi.printInfo('Comment added successfully');
93 | }
94 | if (error) {
95 | spinner.stop();
96 | consoleApi.printError('No issue with mentioned key found');
97 | }
98 | });
99 | },
100 |
101 | /**
102 | * Get all the comments for the specific issue
103 | * @param {*} issueKey
104 | */
105 | getComments(issueKey) {
106 | const spinner = util.spinner({
107 | text: 'Fetching data...',
108 | spinner: 'earth'
109 | });
110 | spinner.start();
111 | authService.jiraConnector()
112 | .issue.getComments({ issueKey }, (error, response) => {
113 | if (response) {
114 | spinner.stop();
115 | if (response.comments.length === 0) {
116 | consoleApi.printInfo('No Comments Found');
117 | } else {
118 | response.comments.map(comment => {
119 | console.log(
120 | `${comment.id} ${consoleApi.chalkGreen(
121 | comment.author.displayName.split(' ')[0]
122 | )} ${comment.body} \n`
123 | );
124 | });
125 | }
126 | }
127 | });
128 | },
129 | /**
130 | * * Delete the comment for the given issueKey
131 | * @param {string} [opts.issueKey] The Key of the issue. EX: JWR-3
132 | * @param {string} opts.commentId The id of the comment.
133 | * @param {*} opts
134 | */
135 |
136 | deleteComment(opts) {
137 | const spinner = util.spinner('Deleting comment .....');
138 | spinner.start();
139 | authService.jiraConnector()
140 | .issue.deleteComment(opts, (error, response) => {
141 | if (error) {
142 | spinner.stop();
143 | consoleApi.printError('Error while deleting the comment');
144 | }
145 | if (response) {
146 | spinner.stop();
147 | consoleApi.printInfo('Comment deleted');
148 | }
149 | });
150 | }
151 | };
152 |
--------------------------------------------------------------------------------
/src/api/JqlClient.js:
--------------------------------------------------------------------------------
1 | /**
2 | * JIRA Apis response with jira JQL custom quesry search
3 | */
4 | const authenticate = require('../services/AuthServices');
5 | const util = require('../utility/utils');
6 | const consoleApi = require('../utility/console');
7 |
8 | const spinner = util.spinner({ text: 'Fetching data...', spinner: 'earth' });
9 |
10 | const todoJQL = "assignee = currentUser() AND status='To Do'";
11 | const completedJQL = "assignee = currentUser() AND status='Done'";
12 | const inReviewJQL = "assignee = currentUser() AND status='In Review'";
13 |
14 | module.exports = {
15 | formatIssuesData(response) {
16 | let issues;
17 | if (response) {
18 | spinner.stop();
19 | }
20 | if (response.issues.length) {
21 | issues = response.issues.map(issue => ({
22 | key: issue.key,
23 | summary: issue.fields.summary,
24 | type: issue.fields.issuetype.name
25 | }));
26 | }
27 | return issues;
28 | },
29 |
30 | /**
31 | * Print the issue listing in a proper format
32 | * @param {*} issues
33 | */
34 | printIssues(issues) {
35 | if (issues) {
36 | issues.map(issue => {
37 | console.log(
38 | `${issue.key} ${util.setIssueColor(issue.type)} ${
39 | issue.summary
40 | } \n`
41 | );
42 | });
43 | } else {
44 | consoleApi.printInfo('No issues found');
45 | }
46 | },
47 | /**
48 | *
49 | * @param {*} param0
50 | * @param {*} callback
51 | * @return CurrentUser ToDo Issues
52 | */
53 | myOpenIssues(projectKey, callback) {
54 | spinner.start();
55 | const JQL =
56 | projectKey === true
57 | ? todoJQL
58 | : `${todoJQL} AND project = ${projectKey}`;
59 | authenticate
60 | .currentUser()
61 | .search.search({ jql: JQL }, (error, response) =>
62 | callback(module.exports.formatIssuesData(response))
63 | );
64 | },
65 | /**
66 | * @return current user issues which are in review
67 | * @param {*} param0
68 | * @param {*} callback
69 | */
70 | myInReviewIssues(projectKey, callback) {
71 | spinner.start();
72 | const JQL =
73 | projectKey === true
74 | ? inReviewJQL
75 | : `${inReviewJQL} AND project = ${projectKey}`;
76 | authenticate
77 | .currentUser()
78 | .search.search({ jql: JQL }, (error, response) =>
79 | callback(module.exports.formatIssuesData(response))
80 | );
81 | },
82 | /**
83 | * @return Current User completed issues list
84 | * @param {*} param0
85 | * @param {*} callback
86 | */
87 | myCompletedIssues(projectKey, callback) {
88 | spinner.start();
89 | const JQL =
90 | projectKey === true
91 | ? completedJQL
92 | : `${completedJQL} AND project = ${projectKey}`;
93 | authenticate
94 | .currentUser()
95 | .search.search({ jql: JQL }, (error, response) =>
96 | callback(module.exports.formatIssuesData(response))
97 | );
98 | },
99 |
100 | fetchMyOpenIssues(projectKey) {
101 | module.exports.myOpenIssues(projectKey, response => {
102 | module.exports.printIssues(response);
103 | });
104 | },
105 |
106 | fetchMyInReviewIssues(projectKey) {
107 | module.exports.myInReviewIssues(projectKey, response => {
108 | module.exports.printIssues(response);
109 | });
110 | },
111 |
112 | fetchMyCompletedIssues(projectKey) {
113 | module.exports.myCompletedIssues(projectKey, response => {
114 | module.exports.printIssues(response);
115 | });
116 | }
117 | };
118 |
--------------------------------------------------------------------------------
/src/api/board.js:
--------------------------------------------------------------------------------
1 | /**
2 | * JIRA APIS related to jira board
3 | */
4 |
5 | const fuzzy = require('fuzzy');
6 | const inquirer = require('inquirer');
7 | const autocompletePrompt = require('inquirer-autocomplete-prompt');
8 | const authService = require('../services/AuthServices');
9 | inquirer.registerPrompt('autocomplete', autocompletePrompt);
10 | const log = require('../utility/console');
11 | const util = require('../utility/utils');
12 | const spinner = util.spinner('Fetching boards.....');
13 |
14 | let names = [];
15 | module.exports = {
16 | getBoards() {
17 | spinner.start();
18 | authService.jiraConnector().board.getAllBoards({}, (err, response) => {
19 | if (response) {
20 | spinner.stop();
21 | names = response.values.map(el => el.name);
22 | inquirer
23 | .prompt([
24 | {
25 | type: 'autocomplete',
26 | name: 'name',
27 | message:
28 | 'Please search for the board you want to open',
29 | source: module.exports.searchProjects,
30 | pageSize: 6
31 | }
32 | ])
33 | .then(answers => {
34 | // assign the issue to selected user
35 | const boardId = response.values.filter(
36 | el => el.name === answers.name
37 | )?.[0]?.id;
38 | if (boardId) {
39 | util.setConfig({ defaultJiraBoard: boardId });
40 | }
41 | log.printInfo('Your default board is set');
42 | });
43 | } else {
44 | console.log('---', err);
45 | }
46 | });
47 | },
48 |
49 | searchProjects(answers, input) {
50 | input = input || '';
51 | return new Promise(resolve => {
52 | setTimeout(() => {
53 | const fuzzyResult = fuzzy.filter(input, names);
54 | resolve(fuzzyResult.map(el => el.original));
55 | }, 300);
56 | });
57 | }
58 | };
59 |
--------------------------------------------------------------------------------
/src/api/questions.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Question Prompt Module
3 | */
4 |
5 | const inquirer = require('inquirer');
6 | //const issue = require('./issue_client');
7 |
8 | const credentialQuestion = [
9 | {
10 | type: 'input',
11 | name: 'host_name',
12 | message: 'Your JIRA Host Name (eg: something.atlassian.net)',
13 | validate: value => (value.length ? true : 'Please enter Host Name')
14 | },
15 | {
16 | type: 'input',
17 | name: 'user_name',
18 | message: 'Your JIRA User Name',
19 | validate: value => (value.length ? true : 'Please enter Jira User Name')
20 | },
21 | {
22 | type: 'password',
23 | name: 'api_token',
24 | message: 'Your API Token',
25 | mask: '*',
26 | validate: value => (value.length ? true : 'Please Enter Your API Token')
27 | }
28 | ];
29 |
30 | module.exports = {
31 | // asking credential for the input from user
32 | askCredential: () => inquirer.prompt(credentialQuestion),
33 | // Confirmation prompt for the JIRA login cred removal from configStore
34 | confirmRemoval: () =>
35 | inquirer.prompt([
36 | {
37 | type: 'list',
38 | name: 'remove',
39 | message:
40 | 'Are you sure? This command will remove the login credentials from system. ' +
41 | 'You need to login again for further usages of JIRAX',
42 | choices: [
43 | { key: 'Yes', value: 'Yes' },
44 | { key: 'No', value: 'No' }
45 | ]
46 | }
47 | ])
48 | };
49 |
--------------------------------------------------------------------------------
/src/api/task.js:
--------------------------------------------------------------------------------
1 | /**
2 | * All Task Related APIs goes here
3 | */
4 |
5 | const fuzzy = require('fuzzy');
6 | const inquirer = require('inquirer');
7 | const autocompletePrompt = require('inquirer-autocomplete-prompt');
8 | const authenticate = require('../services/AuthServices');
9 | inquirer.registerPrompt('autocomplete', autocompletePrompt);
10 | const axiosConfig = require('../services/AxiosService');
11 | const authService = require('../services/AuthServices');
12 | const store = require('../Store');
13 | const log = require('../utility/console');
14 | const util = require('../utility/utils');
15 | const print = require('../operations/PrintIIssue');
16 | const statusSpinner = util.spinner('Fetching available statuses.....');
17 | const fetchingIssueSpinner = util.spinner({
18 | text: 'Loading details..',
19 | spinner: 'moon'
20 | });
21 |
22 | // Initialize
23 | module.exports = {
24 | changeIssueStatus(issueKey) {
25 | statusSpinner.start();
26 |
27 | authService
28 | .jira()
29 | .issues.getTransitions({
30 | issueIdOrKey: issueKey
31 | })
32 | .then(response => {
33 | statusSpinner.stop();
34 | let availableTranstions = response.transitions.map(t => ({
35 | name: t.name,
36 | value: t.id
37 | }));
38 |
39 | var names = availableTranstions.map(el => el.name);
40 | inquirer
41 | .prompt([
42 | {
43 | type: 'autocomplete',
44 | name: 'name',
45 | message:
46 | 'Search & select for transition you want to move the task',
47 | source: (answers, input) => {
48 | input = input || '';
49 | return new Promise(resolve => {
50 | setTimeout(() => {
51 | const fuzzyResult = fuzzy.filter(
52 | input,
53 | names
54 | );
55 | resolve(
56 | fuzzyResult.map(el => el.original)
57 | );
58 | }, 300);
59 | });
60 | },
61 | pageSize: 6
62 | }
63 | ])
64 | .then(answers => {
65 | let selectedTrans = availableTranstions.find(
66 | e => e.name === answers.name
67 | );
68 | module.exports.doTransition(issueKey, selectedTrans);
69 | });
70 | })
71 | .catch(error => {
72 | statusSpinner.stop();
73 | console.log('error', error.response.status);
74 | });
75 | },
76 |
77 | searchTransitions(answers, input) {
78 | input = input || '';
79 | return new Promise(resolve => {
80 | setTimeout(() => {
81 | const fuzzyResult = fuzzy.filter(input, names);
82 | resolve(fuzzyResult.map(el => el.original));
83 | }, 300);
84 | });
85 | },
86 |
87 | // Change issue to another status
88 | doTransition(issueKey, transition) {
89 | let transspin = util.spinner('Transitioning....');
90 | transspin.start();
91 | let params = {
92 | issueIdOrKey: issueKey,
93 | transition: {
94 | id: parseInt(transition.value),
95 | name: transition.name
96 | }
97 | };
98 | authService
99 | .jira()
100 | .issues.doTransition(params, response => {
101 | if (response) {
102 | transspin.stop();
103 | log.printInfo('Your issue changed to ' + transition.name);
104 | }
105 | })
106 | .then(response => {
107 | transspin.stop();
108 | log.printInfo('Your issue changed to ' + transition.name);
109 | })
110 | .catch(error => {
111 | console.log('----', error.response);
112 | });
113 | },
114 |
115 | fetchTaskDetails(issueKey) {
116 | fetchingIssueSpinner.start();
117 | authService
118 | .jira()
119 | .issues.getIssue({ issueIdOrKey: issueKey })
120 | .then(res => {
121 | if (res) {
122 | fetchingIssueSpinner.stop();
123 | print.printIssueDetails(res);
124 | }
125 | })
126 | .catch(error => {
127 | console.log(error.response.status);
128 | });
129 | }
130 | };
131 |
--------------------------------------------------------------------------------
/src/api/user.js:
--------------------------------------------------------------------------------
1 | /**
2 | * User Related APIs
3 | */
4 | const auth = require('../services/AuthServices');
5 |
6 | module.exports = {
7 | /**
8 | * Returns a list of users that match the search string.
9 | * Please note that this resource should be called with an issue key
10 | * when a list of assignable users is retrieved for editing
11 | *
12 | * @param {Object} opts The request options sent to the Jira API
13 | * @param {*} cb
14 | * @param {string} opts.query The username
15 | * @param {string} opts.issueKey The issue key for the issue being edited we need to find assignable users
16 | */
17 | searchAssignableUser: function(issueKey, input, cb) {
18 | let options = { issueKey: issueKey, query: input };
19 | auth.jira().userSearch.findAssignableUsers(options, function(err, res) {
20 | let users = [];
21 | if (err) {
22 | // if err then return the empty users array
23 | return cb(users);
24 | }
25 | if (res) {
26 | res.map(user => {
27 | users.push({
28 | accountId: user.accountId,
29 | name: user.displayName
30 | });
31 | });
32 | return cb(users);
33 | }
34 | });
35 | }
36 | };
37 |
--------------------------------------------------------------------------------
/src/commands/Tasks.js:
--------------------------------------------------------------------------------
1 | /** Issues Related commands */
2 | const program = require('commander');
3 | /**
4 | * 1. Fetch all the issue transitions.
5 | * 2. jirax task change KEY - Change the status of the task (transition to another status)
6 | * 3. jirax task detail KEY - get detail of the task. ( show the details of the task)
7 | * 4. jirax task assign KEY - Assign issue to another user in the same task
8 | */
9 | const task = require('../api/Task');
10 | const taskOperation = require('../operations/Task');
11 | const assignTask = require('../operations/AssignIssue');
12 |
13 | exports.loadTasksCommands = () => {
14 | const openTasksCommands = program
15 | .command('task')
16 | .description('JIRA task operations')
17 | .action(() => {
18 | // show the available issues commands
19 | openTasksCommands.help();
20 | });
21 |
22 | // change status of the issue
23 | openTasksCommands
24 | .command('move')
25 | .argument('', 'Task KEY that you want to change')
26 | .description('Change the status of the task')
27 | .action(key => {
28 | task.changeIssueStatus(key);
29 | });
30 |
31 | // show details
32 | openTasksCommands
33 | .command('details')
34 | .argument('', 'Task KEY to view')
35 | .description('Details of the Task')
36 | .action(key => {
37 | task.fetchTaskDetails(key);
38 | });
39 |
40 | // assign issue
41 | openTasksCommands
42 | .command('assign')
43 | .argument('', 'Task key that to assign')
44 | .description('Assign task to another user')
45 | .action(key => {
46 | assignTask.searchUser(key);
47 | });
48 |
49 | // open issue
50 | openTasksCommands
51 | .command('assign-me')
52 | .argument('', 'Task key to assign')
53 | .description('Assign Issue to Self(logged in user')
54 | .action(key => {
55 | taskOperation.assignToSelf(key)
56 | });
57 |
58 | // open issue
59 | openTasksCommands
60 | .command('open')
61 | .argument('', 'Task key to open in browser')
62 | .description('Opens task in browser')
63 | .action(key => {
64 | taskOperation.openTask(key);
65 | });
66 |
67 | };
68 |
--------------------------------------------------------------------------------
/src/commands/boards.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module contains `command` sub-commands related to JIRA board
3 | */
4 | /**
5 | * --- Available Commands
6 | * jirax board mine - 'Opens your default JIRA board with with your assigned tasks'
7 | * jirax board set - 'Set Your Default JIRA Board'
8 | * jirax board show - 'Opens The JIRA Board that You had Set as Default.'
9 | */
10 | const program = require('commander');
11 | const { openMyboard } = require('../operations/Boards');
12 | const board = require('../api/board');
13 | exports.loadBoardCommands = () => {
14 | const openBoardCommand = program
15 | .command('board')
16 | .description('JIRA boards activities')
17 | .action(() => {
18 | openBoardCommand.help();
19 | });
20 |
21 | /**
22 | * Opens The JIRA Board that You had Set as Default with your assigned tasks.
23 | */
24 | openBoardCommand
25 | .command('mine')
26 | .description(
27 | 'Opens your default JIRA board with with your assigned tasks'
28 | )
29 | .action(() => {
30 | openMyboard(true); // opens default with your id preselected
31 | });
32 |
33 | openBoardCommand
34 | .command('show')
35 | .description('Opens The JIRA Board that You had Set as Default.')
36 | .action(() => {
37 | openMyboard(false); // opens default
38 | });
39 |
40 | // Set Your Default JIRA Board so that you can open it directly
41 | openBoardCommand
42 | .command('set')
43 | .description('Set Your Default JIRA Board')
44 | .action(answers => {
45 | // select from the default board if present
46 | // if not then open board with selected my profile
47 | board.getBoards();
48 | });
49 | };
50 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const program = require('commander');
4 | const input = require('./Store');
5 |
6 | const { loadBoardCommands } = require('./commands/Boards');
7 | const { loadTasksCommands } = require('./commands/Tasks');
8 |
9 | program.version('1.0.0').description('CLI Tool for accessing JIRA');
10 | program
11 | .command('login')
12 | .description('Login using JIRA API Token')
13 | .action(() => {
14 | input.signUpUser();
15 | });
16 |
17 | program
18 | .command('logout')
19 | .description('Remove you JIRA credentials')
20 | .action(() => {
21 | input.removeCredentials();
22 | });
23 |
24 | // Add other sub commands here
25 | loadBoardCommands();
26 | loadTasksCommands();
27 |
28 | program.parse(process.argv);
29 |
--------------------------------------------------------------------------------
/src/operations/AssignIssue.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Operations for searching user with autocomplete and find user
3 | *
4 | */
5 | const inquirer = require('inquirer');
6 | const autocompletePrompt = require('inquirer-autocomplete-prompt');
7 |
8 | inquirer.registerPrompt('autocomplete', autocompletePrompt);
9 | const fuzzy = require('fuzzy');
10 | const user = require('../api/User');
11 | const issue = require('../api/IssueApis');
12 |
13 | let issueKey;
14 | let fetchedUsersArray;
15 |
16 | module.exports = {
17 | /**
18 | * prompt user search option to assign issue
19 | * @param {*} issue
20 | */
21 | searchUser(issue) {
22 | issueKey = issue;
23 | inquirer
24 | .prompt([
25 | {
26 | type: 'autocomplete',
27 | name: 'name',
28 | message: 'Please search for the user to assign',
29 | source: module.exports.searchUsers,
30 | pageSize: 5
31 | }
32 | ])
33 | .then(answers => {
34 | // assign the issue to selected user
35 | module.exports.findAccountAndAssign(answers.name);
36 | });
37 | },
38 |
39 | /**
40 | * Returns the list of available users for the input and assignable issueKey
41 | * @param {*} answers
42 | * @param {*} input
43 | * @returns Name of available users
44 | */
45 | searchUsers(answers, input) {
46 | input = input || '';
47 | const names = [];
48 | user.searchAssignableUser(issueKey, input, response => {
49 | fetchedUsersArray = response;
50 | response.map(el => names.push(el.name));
51 | });
52 |
53 | return new Promise(resolve => {
54 | setTimeout(() => {
55 | const fuzzyResult = fuzzy.filter(input, names);
56 | resolve(fuzzyResult.map(el => el.original));
57 | }, 2000);
58 | });
59 | },
60 | /**
61 | * Assign the issue for the selected user
62 | * @param {*} username
63 | */
64 | findAccountAndAssign(username) {
65 | const options = {
66 | /**
67 | * Each element in fetchedUsersArray array is an object, not a string.
68 | * We can pass in a function that is called on each element in the array to
69 | * extract the name to fuzzy search against. In this case, element.name
70 | */
71 | extract(el) {
72 | return el.name;
73 | }
74 | };
75 | const fuzzyResult = fuzzy.filter(username, fetchedUsersArray, options);
76 | /**
77 | * @returns ['accountId'] of the selected User
78 | */
79 | const accountId = fuzzyResult.map(el => el.original.accountId);
80 |
81 | issue.assignIssue(issueKey, accountId[0], username);
82 | }
83 | };
84 |
--------------------------------------------------------------------------------
/src/operations/PrintIIssue.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Print the response in tabular format
3 | */
4 |
5 | const consoleApi = require('../utility/console');
6 |
7 | module.exports = {
8 | /**
9 | * Print the issue details
10 | * @param {issueKey: 'SFMAC-165'}
11 | */
12 | printIssueDetails(response) {
13 |
14 | let issue = {
15 | key: response.key,
16 | description: response.fields.description,
17 | reporterName: response.fields.reporter.displayName,
18 | assigneeName: response.fields.assignee.displayName,
19 | summary: response.fields.summary,
20 | status: response.fields.status.name,
21 | issueType: response.fields.issuetype.name,
22 | priority: response.fields.priority.name
23 | };
24 |
25 | consoleApi.printInfo(`Details for ${issue.key}\n`);
26 |
27 | console.log(
28 | `## Summary: ${consoleApi.printBgGreenBright(issue.summary)}\n`
29 | );
30 |
31 | console.log(
32 | `## ${consoleApi.printBgGreen(
33 | issue.key
34 | )} ${consoleApi.printbgBlueBright(
35 | 'STATUS'
36 | )}: ${issue.status} ## ${consoleApi.printbgCyan(
37 | 'Assignee'
38 | )} - ${issue.assigneeName} \n`
39 | );
40 | console.log(
41 | `## ${consoleApi.printbgCyan('Type')}: ${
42 | issue.issueType
43 | } ## ${consoleApi.printbgCyan(
44 | 'Reporter'
45 | )} - ${issue.reporterName} \n`
46 | );
47 | console.log(
48 | `## ${consoleApi.printbgCyan('Priority')}: ${issue.priority} \n`
49 | );
50 | console.log(`## ${consoleApi.printbgBlueBright('Description')} \n`);
51 | console.log(`${issue.description}\n`);
52 | }
53 | };
54 |
--------------------------------------------------------------------------------
/src/operations/Task.js:
--------------------------------------------------------------------------------
1 | const url = require('../Url');
2 | const issue = require('../api/IssueApis');
3 |
4 | module.exports = {
5 | openTask: key => {
6 | let issueUrl = url.issueURL(key);
7 | url.openURL(issueUrl);
8 | },
9 |
10 | assignToSelf: key => {
11 | issue.assignSelf(key)
12 | }
13 | };
14 |
--------------------------------------------------------------------------------
/src/operations/boards.js:
--------------------------------------------------------------------------------
1 | const { openURL, boardURL } = require('../Url');
2 | const config = require('../utility/utils');
3 | const print = require('../utility/console');
4 |
5 | /**
6 | * Open the default set JIRA RAPID BOARD
7 | */
8 | exports.openMyboard = (myprofile = false) => {
9 | const boardId = config.getConfig('defaultJiraBoard');
10 | const accountId = myprofile ? config.getConfig('accountId') : null;
11 | if (boardId) {
12 | const url = boardURL(boardId, accountId);
13 | openURL(url);
14 | } else {
15 | print.printError('No board found! Please select a default board.');
16 | }
17 | };
18 |
--------------------------------------------------------------------------------
/src/services/AuthServices.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Authentication Module
3 | * -------------------------------------------------------------
4 | * return the jiraAuth BASE object and then we can call the apis
5 | * on the base of the main object.
6 | */
7 | const Configstore = require('configstore');
8 | const configStore = new Configstore('jiraconfig');
9 | const { Version2Client } = require('jira.js');
10 | const jiraConnector = require('jira-connector');
11 | const emailAddress = configStore.get('emailAddress');
12 | const apiToken = configStore.get('apiToken');
13 | const hostname = configStore.get('hostname');
14 |
15 | module.exports = {
16 | /**
17 | * Config Object contains the username, hostname and API token
18 | * @param {*} config
19 | */
20 | authenticate(config, callback) {
21 | const HOST_NAME = config.host_name;
22 | let client = new Version2Client({
23 | host: `https://${config.host_name}`,
24 | authentication: {
25 | basic: {
26 | email: config.user_name,
27 | apiToken: config.api_token
28 | }
29 | }
30 | });
31 |
32 | client.myself
33 | .getCurrentUser()
34 | .then(user => {
35 | if (user) {
36 | return callback({
37 | user,
38 | url: `https://${config.host_name}`,
39 | hostname: HOST_NAME,
40 | apiToken: config.api_token
41 | });
42 | }
43 | })
44 | .catch(err => {
45 | return callback({ error: 'UnAuthorized' });
46 | });
47 | },
48 |
49 | /**
50 | * Returns the recently authenticated user JIRA-Connector Object
51 | * JIRA.js
52 | */
53 | jira() {
54 | const emailAddress = configStore.get('emailAddress');
55 | const apiToken = configStore.get('apiToken');
56 | const hostUrl = configStore.get('url');
57 | if (!apiToken) {
58 | console.log('Please login using your API Token');
59 | } else {
60 | const JIRA_CONFIG = new Version2Client({
61 | host: hostUrl,
62 | authentication: {
63 | basic: {
64 | email: emailAddress,
65 | apiToken: apiToken
66 | }
67 | }
68 | });
69 | return JIRA_CONFIG;
70 | }
71 | },
72 |
73 | /**
74 | * Returns the recently authenticated jira Object
75 | * JIRA -CONNECTOR
76 | */
77 | jiraConnector() {
78 | if (!apiToken) {
79 | console.log('Please login using your API Token');
80 | } else {
81 | const jira = new jiraConnector({
82 | host: hostname,
83 | basic_auth: {
84 | email: emailAddress,
85 | api_token: apiToken
86 | },
87 | strictSSL: true
88 | });
89 | return jira;
90 | }
91 | }
92 | };
93 |
--------------------------------------------------------------------------------
/src/services/AxiosService.js:
--------------------------------------------------------------------------------
1 | /**
2 | * we generally use JIRA_CONNECTOR and JIRA.js for various JIRA APIS access.
3 | * For some cases we can use this service to call the API directly.
4 | */
5 | const Configstore = require('configstore');
6 | const configStore = new Configstore('jiraconfig');
7 | const axios = require('axios');
8 | const emailAddress = configStore.get('emailAddress');
9 | const apiToken = configStore.get('apiToken');
10 | const hostname = configStore.get('hostname');
11 |
12 | // encode email and token for basic auth as per JIRA authentications
13 | const encodeEmailToken = Buffer.from(`${emailAddress}:${apiToken}`).toString(
14 | 'base64'
15 | );
16 |
17 | const instance = axios.create({
18 | baseURL: `https://${hostname}`,
19 | headers: {
20 | Authorization: `Basic ${encodeEmailToken}`,
21 | 'Cache-Control': 'no-cache',
22 | 'Content-Type': 'application/json'
23 | }
24 | });
25 |
26 | instance.interceptors.request.use(
27 | function(config) {
28 | return config;
29 | },
30 | function(error) {
31 | return Promise.reject(error);
32 | }
33 | );
34 |
35 | axios.interceptors.response.use(
36 | function(response) {
37 | // Any status code that lie within the range of 2xx cause this function to trigger
38 | // Do something with response data
39 | return response;
40 | },
41 | function(error) {
42 | // Any status codes that falls outside the range of 2xx cause this function to trigger
43 | // Do something with response error
44 | return Promise.reject(error);
45 | }
46 | );
47 |
48 | module.exports = instance;
49 |
--------------------------------------------------------------------------------
/src/store.js:
--------------------------------------------------------------------------------
1 | /**
2 | * After taking input from user parse the data and validate
3 | * save input to configstore , remove the saved credentials
4 | * when user opted to remove the credentials
5 | */
6 |
7 | const Configstore = require('configstore');
8 | const configStore = new Configstore('jiraconfig');
9 | // apis
10 | const authService = require('./services/AuthServices');
11 | const print = require('./utility/console');
12 | const question = require('./api/Questions');
13 | const util = require('./utility/utils');
14 |
15 | const spinner = util.spinner('Authenticating...');
16 | /**
17 | * Verify the user and save the object
18 | * @config object
19 | */
20 | module.exports = {
21 | signUpUser: () => {
22 | const isLoggedIn = configStore.get('apiToken');
23 | if (!isLoggedIn) {
24 | question.askCredential().then(answers => {
25 | spinner.start();
26 | module.exports.verifyAndSave(answers);
27 | });
28 | } else {
29 | print.printInfo('You are already logged in');
30 | }
31 | },
32 |
33 | verifyAndSave: config => {
34 | // authenticate and save the inputs in config store
35 | authService.authenticate(config, data => {
36 | if (data) {
37 | spinner.stop();
38 | }
39 | if (data.error) {
40 | print.printError(
41 | 'Unauthorized - Please re-enter your credentials'
42 | );
43 | } else {
44 | module.exports.storeLoginInfo(data);
45 | }
46 | });
47 | },
48 | /**
49 | * Store user data after authentication
50 | * @param {*} data
51 | */
52 | storeLoginInfo: data => {
53 | const message = data.user.displayName.split(' ')[0];
54 | print.printFigletYellow(`Hi ${message}`);
55 | // saving the data
56 | const { hostname, user, apiToken, url } = data;
57 | const { accountId, timeZone, displayName, emailAddress } = user;
58 |
59 | configStore.set({
60 | hostname,
61 | timeZone,
62 | accountId,
63 | displayName,
64 | apiToken,
65 | url,
66 | emailAddress
67 | });
68 | print.printInfo('You have Logged in Successfully 🍺🎉🎊🚀');
69 | },
70 |
71 | /**
72 | * @warning
73 | * This method will remove the login credentials from System which is used to
74 | * authenticate with JIRA apis
75 | * Use Case -
76 | * After the revoking the API key you may need to re-loggin with new JIRAX
77 | * then you need to remove the STORED Credentials
78 | *
79 | */
80 | removeCredentials: () => {
81 | question.confirmRemoval().then(answers => {
82 | if (answers.remove === 'Yes') {
83 | configStore.clear();
84 | print.printInfo(
85 | 'You Have Successfully removed the login credentials.'
86 | );
87 | print.printInfo('Use `jirax --login` command for login');
88 | }
89 | });
90 | }
91 | };
92 |
--------------------------------------------------------------------------------
/src/url.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module contains the utlity methods related to the URL
3 | * mostly used in opening URL with some base and secondary value.
4 | */
5 | const open = require('open');
6 | const util = require('./utility/utils');
7 | const BOARD_URL = `${util.getBaseUrl()}/secure/RapidBoard.jspa?rapidView=`;
8 | const ISSUE_URL = `${util.getBaseUrl()}/browse`;
9 |
10 | /**
11 | * Opens the given URL
12 | * @param {*} url
13 | */
14 | exports.openURL = async url => {
15 | await open(url);
16 | };
17 |
18 | /**
19 | * @return {string} boardURL
20 | * https://yourcompany.atlassian.net/secure/RapidBoard.jspa?rapidView=${boardId}
21 | * @param {*} brandId
22 | */
23 | exports.boardURL = (boardId, accountId = null) =>
24 | accountId
25 | ? `${BOARD_URL}${boardId}&assignee=${accountId}`
26 | : `${BOARD_URL}${boardId}`;
27 |
28 | /**
29 | * https://yourcompany.atlassian.net/browse/Key
30 | * @param {*} issueKey
31 | * @returns
32 | */
33 |
34 | exports.issueURL = issueKey => {
35 | return `${ISSUE_URL}/${issueKey}`;
36 | };
37 |
--------------------------------------------------------------------------------
/src/utility/console.js:
--------------------------------------------------------------------------------
1 | const chalk = require('chalk');
2 | const figlet = require('figlet');
3 |
4 | module.exports = {
5 | printFigletRed: message =>
6 | console.log(
7 | chalk.red(figlet.textSync(message, { horizontalLayout: 'full' }))
8 | ),
9 |
10 | printFigletYellow: message =>
11 | console.log(
12 | chalk.yellow(figlet.textSync(message, { horizontalLayout: 'full' }))
13 | ),
14 |
15 | printKeyColor: message => chalk.hex('#5e6c84').bold(`${message}`),
16 |
17 | chalkRed: message => chalk.red.bold(`${message}`),
18 |
19 | chalkGreen: message => chalk.green.bold(`${message}`),
20 |
21 | printError: message => console.log(chalk.red.bold(message)),
22 |
23 | printInfo: message => console.log(chalk.green.bold(message)),
24 |
25 | printBgRed: message => chalk.black.bgRed.bold(`${message}`),
26 |
27 | printBgGreen: message => chalk.black.bgGreen.bold(`${message}`),
28 |
29 | printBgBlue: message => chalk.black.bgBlue.bold(`${message}`),
30 |
31 | printBgYellow: message => chalk.black.bgYellow.bold(`${message}`),
32 |
33 | bgMagentaBright: message => chalk.white.bgMagentaBright.bold(`${message}`),
34 |
35 | bgRedBright: message => chalk.white.bgRedBright.bold(`${message}`),
36 |
37 | printBgGreenBright: message =>
38 | chalk.hex('#2c3331').bgGreenBright.bold(`${message}`),
39 |
40 | printbgCyan: message => chalk.black.bgCyan.bold(`${message}`),
41 |
42 | printbgBlueBright: message => chalk.white.bgBlueBright.bold(`${message}`)
43 | };
44 |
--------------------------------------------------------------------------------
/src/utility/utils.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Utils for the common funcional Usages
3 | */
4 | const datetimeformat = require('dateformat');
5 | const ora = require('ora');
6 | const Configstore = require('configstore');
7 |
8 | // apis
9 | const consoleApi = require('./console');
10 |
11 | const jiraconfig = new Configstore('jiraconfig');
12 |
13 | module.exports = {
14 | /**
15 | * @returns `https://yourcompanyjira.net`
16 | */
17 | getBaseUrl() {
18 | const hostname = jiraconfig.get('hostname');
19 | const url = `https://${hostname}`;
20 | return url;
21 | },
22 |
23 | /**
24 | * @returns `yourcompanyjira.net`
25 | */
26 | getHostName() {
27 | const hostname = jiraconfig.get('hostname');
28 | return hostname;
29 | },
30 |
31 | getEncodedString() {
32 | const encodedString64 = jiraconfig.get('');
33 | return encodedString64;
34 | },
35 |
36 | /**
37 | * Returns the value that is stored in the configStore
38 | * {
39 | "hostname": "yourhostname",
40 | "encodedString": "username:password:base64",
41 | ......values you have saved
42 | }%
43 | *
44 | * @param {string} key
45 | * @returns {string}
46 | */
47 | getConfig: key => jiraconfig.get(key),
48 | /**
49 | * property = { key: value }
50 | * @param {Object} property
51 | * @returns null
52 | */
53 | setConfig: property => jiraconfig.set(property),
54 | /**
55 | * @returns Date & Time
56 | * @format 10/May/19 12:11 PM
57 | * @param {*} datetime
58 | */
59 | formatDate(datetime) {
60 | return datetimeformat(datetime, '');
61 | },
62 |
63 | /**
64 | * Retruns the bgcolor for text on basis of issueType
65 | * @param {} key issueType
66 | */
67 | setIssueColor(key) {
68 | switch (key) {
69 | case 'Bug':
70 | return consoleApi.bgRedBright(key);
71 | case 'Improvement':
72 | return consoleApi.printBgGreenBright(key);
73 | case 'New Feature':
74 | return consoleApi.printBgGreenBright(key);
75 | case 'Epic':
76 | return consoleApi.bgMagentaBright(key);
77 | case 'Story':
78 | return consoleApi.printBgGreenBright(key);
79 | case 'Sub-task':
80 | return consoleApi.printbgBlueBright(key);
81 | case 'Task':
82 | return consoleApi.printbgBlueBright(key);
83 | default:
84 | return consoleApi.printbgBlueBright(key);
85 | }
86 | },
87 | /**
88 | * returns the ora object
89 | * we can use to start and stop the loader using the object
90 | * @param {*} options
91 | */
92 | spinner(options) {
93 | let modifiedOptions;
94 | if (typeof options === 'string') {
95 | modifiedOptions = { text: options };
96 | } else {
97 | modifiedOptions = options;
98 | }
99 | const spinnerParams = {
100 | ...modifiedOptions,
101 | color: 'blue',
102 | spinner: 'point'
103 | };
104 | return ora(spinnerParams);
105 | }
106 | };
107 |
--------------------------------------------------------------------------------