├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── config.yml │ ├── feature-request---new-table.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml └── workflows │ ├── add-issue-to-project.yml │ ├── golangci-lint.yml │ ├── registry-publish.yml │ ├── stale.yml │ ├── steampipe-anywhere.yml │ └── sync-labels.yml ├── .gitignore ├── .goreleaser.yml ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.md ├── config └── jira.spc ├── docs ├── LICENSE ├── index.md └── tables │ ├── jira_advanced_setting.md │ ├── jira_backlog_issue.md │ ├── jira_board.md │ ├── jira_component.md │ ├── jira_dashboard.md │ ├── jira_epic.md │ ├── jira_global_setting.md │ ├── jira_group.md │ ├── jira_issue.md │ ├── jira_issue_comment.md │ ├── jira_issue_type.md │ ├── jira_issue_worklog.md │ ├── jira_priority.md │ ├── jira_project.md │ ├── jira_project_role.md │ ├── jira_sprint.md │ ├── jira_user.md │ └── jira_workflow.md ├── go.mod ├── go.sum ├── jira ├── common_columns.go ├── connection_config.go ├── errors.go ├── plugin.go ├── table_jira_advanced_setting.go ├── table_jira_backlog_issue.go ├── table_jira_board.go ├── table_jira_component.go ├── table_jira_dashboard.go ├── table_jira_epic.go ├── table_jira_global_setting.go ├── table_jira_group.go ├── table_jira_issue.go ├── table_jira_issue_comment.go ├── table_jira_issue_type.go ├── table_jira_issue_worklog.go ├── table_jira_priority.go ├── table_jira_project.go ├── table_jira_project_role.go ├── table_jira_sprint.go ├── table_jira_user.go ├── table_jira_workflow.go └── utils.go └── main.go /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug, steampipe 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **Steampipe version (`steampipe -v`)** 14 | Example: v0.3.0 15 | 16 | **Plugin version (`steampipe plugin list`)** 17 | Example: v0.5.0 18 | 19 | **To reproduce** 20 | Steps to reproduce the behavior (please include relevant code and/or commands). 21 | 22 | **Expected behavior** 23 | A clear and concise description of what you expected to happen. 24 | 25 | **Additional context** 26 | Add any other context about the problem here. 27 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Questions 4 | url: https://turbot.com/community/join 5 | about: GitHub issues in this repository are only intended for bug reports and feature requests. Other issues will be closed. Please ask and answer questions through the Steampipe Slack community. 6 | - name: Steampipe CLI Bug Reports and Feature Requests 7 | url: https://github.com/turbot/steampipe/issues/new/choose 8 | about: Steampipe CLI has its own codebase. Bug reports and feature requests for those pieces of functionality should be directed to that repository. -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request---new-table.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request - New table 3 | about: Suggest a new table for this project 4 | title: Add table jira__ 5 | labels: enhancement, new table, steampipe 6 | assignees: "" 7 | --- 8 | 9 | **References** 10 | Add any related links that will help us understand the resource, including vendor documentation, related GitHub issues, and Go SDK documentation. 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement, steampipe 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/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Example query results 2 |
3 | Results 4 | 5 | ``` 6 | Add example SQL query results here (please include the input queries as well) 7 | ``` 8 |
9 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "gomod" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | pull-request-branch-name: 13 | separator: "-" 14 | assignees: 15 | - "misraved" 16 | - "madhushreeray30" 17 | labels: 18 | - "dependencies" 19 | -------------------------------------------------------------------------------- /.github/workflows/add-issue-to-project.yml: -------------------------------------------------------------------------------- 1 | name: Assign Issue to Project 2 | 3 | on: 4 | issues: 5 | types: [opened] 6 | 7 | jobs: 8 | add-to-project: 9 | uses: turbot/steampipe-workflows/.github/workflows/assign-issue-to-project.yml@main 10 | with: 11 | issue_number: ${{ github.event.issue.number }} 12 | repository: ${{ github.repository }} 13 | secrets: inherit 14 | -------------------------------------------------------------------------------- /.github/workflows/golangci-lint.yml: -------------------------------------------------------------------------------- 1 | name: golangci-lint 2 | on: 3 | push: 4 | tags: 5 | - v* 6 | branches: 7 | - main 8 | pull_request: 9 | 10 | jobs: 11 | golangci_lint_workflow: 12 | uses: turbot/steampipe-workflows/.github/workflows/golangci-lint.yml@main 13 | -------------------------------------------------------------------------------- /.github/workflows/registry-publish.yml: -------------------------------------------------------------------------------- 1 | name: Build and Deploy OCI Image 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | jobs: 9 | registry_publish_workflow_ghcr: 10 | uses: turbot/steampipe-workflows/.github/workflows/registry-publish-ghcr.yml@main 11 | secrets: inherit 12 | with: 13 | releaseTimeout: 60m 14 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: Stale Issues and PRs 2 | on: 3 | schedule: 4 | - cron: "30 23 * * *" 5 | workflow_dispatch: 6 | inputs: 7 | dryRun: 8 | description: Set to true for a dry run 9 | required: false 10 | default: "false" 11 | type: string 12 | 13 | jobs: 14 | stale_workflow: 15 | uses: turbot/steampipe-workflows/.github/workflows/stale.yml@main 16 | with: 17 | dryRun: ${{ github.event.inputs.dryRun }} 18 | -------------------------------------------------------------------------------- /.github/workflows/steampipe-anywhere.yml: -------------------------------------------------------------------------------- 1 | name: Release Steampipe Anywhere Components 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | 9 | jobs: 10 | anywhere_publish_workflow: 11 | uses: turbot/steampipe-workflows/.github/workflows/steampipe-anywhere.yml@main 12 | secrets: inherit 13 | -------------------------------------------------------------------------------- /.github/workflows/sync-labels.yml: -------------------------------------------------------------------------------- 1 | name: Sync Labels 2 | on: 3 | schedule: 4 | - cron: "30 22 * * 1" 5 | workflow_dispatch: 6 | 7 | jobs: 8 | sync_labels_workflow: 9 | uses: turbot/steampipe-workflows/.github/workflows/sync-labels.yml@main 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | # This is an example goreleaser.yaml file with some sane defaults. 2 | # Make sure to check the documentation at http://goreleaser.com 3 | before: 4 | hooks: 5 | - go mod tidy 6 | builds: 7 | - env: 8 | - CGO_ENABLED=0 9 | - GO111MODULE=on 10 | - GOPRIVATE=github.com/turbot 11 | goos: 12 | - linux 13 | - darwin 14 | 15 | goarch: 16 | - amd64 17 | - arm64 18 | 19 | id: "steampipe" 20 | binary: "{{ .ProjectName }}.plugin" 21 | flags: 22 | - -tags=netgo 23 | 24 | archives: 25 | - format: gz 26 | name_template: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}" 27 | files: 28 | - none* 29 | checksum: 30 | name_template: "{{ .ProjectName }}_{{ .Version }}_SHA256SUMS" 31 | algorithm: sha256 32 | changelog: 33 | sort: asc 34 | filters: 35 | exclude: 36 | - "^docs:" 37 | - "^test:" 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | STEAMPIPE_INSTALL_DIR ?= ~/.steampipe 2 | BUILD_TAGS = netgo 3 | install: 4 | go build -o $(STEAMPIPE_INSTALL_DIR)/plugins/hub.steampipe.io/plugins/turbot/jira@latest/steampipe-plugin-jira.plugin -tags "${BUILD_TAGS}" *.go -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![image](https://hub.steampipe.io/images/plugins/turbot/jira-social-graphic.png) 2 | 3 | # Jira Plugin for Steampipe 4 | 5 | Use SQL to query infrastructure including servers, networks, facilities and more from Jira. 6 | 7 | - **[Get started →](https://hub.steampipe.io/plugins/turbot/jira)** 8 | - Documentation: [Table definitions & examples](https://hub.steampipe.io/plugins/turbot/jira/tables) 9 | - Community: [Join #steampipe on Slack →](https://turbot.com/community/join) 10 | - Get involved: [Issues](https://github.com/turbot/steampipe-plugin-jira/issues) 11 | 12 | ## Quick start 13 | 14 | ### Install 15 | 16 | Download and install the latest Jira plugin: 17 | 18 | ```bash 19 | steampipe plugin install jira 20 | ``` 21 | 22 | Configure your [credentials](https://hub.steampipe.io/plugins/turbot/jira#credentials) and [config file](https://hub.steampipe.io/plugins/turbot/jira#configuration). 23 | 24 | Configure your account details in `~/.steampipe/config/jira.spc`: 25 | 26 | ```hcl 27 | connection "jira" { 28 | plugin = "jira" 29 | 30 | # Authentication information 31 | base_url = "https://your-domain.atlassian.net/" 32 | username = "abcd@xyz.com" 33 | token = "8WqcdT0rvIZpCjtDqReF48B1" 34 | } 35 | ``` 36 | 37 | For [self-hosted Jira instances](https://github.com/andygrunwald/go-jira/#bearer---personal-access-tokens-self-hosted-jira), please use the `personal_access_token` field instead of `token`. This access token can only be used to query `jira_backlog_issue`, `jira_board`, `jira_issue` and `jira_sprint` tables. 38 | 39 | ```hcl 40 | connection "jira" { 41 | plugin = jira 42 | 43 | # Authentication information 44 | base_url = "https://your-domain.atlassian.net/" 45 | personal_access_token = "MDU0MDMx7cE25TQ3OujDfy/vkv/eeSXXoh/zXY1ex9cp" 46 | } 47 | ``` 48 | 49 | Or through environment variables: 50 | 51 | ```sh 52 | export JIRA_URL=https://your-domain.atlassian.net/ 53 | export JIRA_USER=abcd@xyz.com 54 | export JIRA_TOKEN=8WqcdT0rvIZpCjtDqReF48B1 55 | export JIRA_PERSONAL_ACCESS_TOKEN="MDU0MDMx7cE25TQ3OujDfy/vkv/eeSXXoh/zXY1ex9cp" 56 | ``` 57 | 58 | Run steampipe: 59 | 60 | ```shell 61 | steampipe query 62 | ``` 63 | 64 | List users in your Jira account: 65 | 66 | ```sql 67 | select 68 | display_name, 69 | account_type as type, 70 | active as status, 71 | account_id 72 | from 73 | jira_user; 74 | ``` 75 | 76 | ``` 77 | +-------------------------------+-----------+--------+-----------------------------+ 78 | | display_name | type | status | account_id | 79 | +-------------------------------+-----------+--------+-----------------------------+ 80 | | Confluence Analytics (System) | app | true | 557058:cbc04d7be567aa5332c6 | 81 | | John Smyth | atlassian | true | 1f2e1d34e0e56a001ea44fc1 | 82 | +-------------------------------+-----------+--------+-----------------------------+ 83 | ``` 84 | 85 | ## Engines 86 | 87 | This plugin is available for the following engines: 88 | 89 | | Engine | Description 90 | |---------------|------------------------------------------ 91 | | [Steampipe](https://steampipe.io/docs) | The Steampipe CLI exposes APIs and services as a high-performance relational database, giving you the ability to write SQL-based queries to explore dynamic data. Mods extend Steampipe's capabilities with dashboards, reports, and controls built with simple HCL. The Steampipe CLI is a turnkey solution that includes its own Postgres database, plugin management, and mod support. 92 | | [Postgres FDW](https://steampipe.io/docs/steampipe_postgres/overview) | Steampipe Postgres FDWs are native Postgres Foreign Data Wrappers that translate APIs to foreign tables. Unlike Steampipe CLI, which ships with its own Postgres server instance, the Steampipe Postgres FDWs can be installed in any supported Postgres database version. 93 | | [SQLite Extension](https://steampipe.io/docs/steampipe_sqlite/overview) | Steampipe SQLite Extensions provide SQLite virtual tables that translate your queries into API calls, transparently fetching information from your API or service as you request it. 94 | | [Export](https://steampipe.io/docs/steampipe_export/overview) | Steampipe Plugin Exporters provide a flexible mechanism for exporting information from cloud services and APIs. Each exporter is a stand-alone binary that allows you to extract data using Steampipe plugins without a database. 95 | | [Turbot Pipes](https://turbot.com/pipes/docs) | Turbot Pipes is the only intelligence, automation & security platform built specifically for DevOps. Pipes provide hosted Steampipe database instances, shared dashboards, snapshots, and more. 96 | 97 | ## Developing 98 | 99 | Prerequisites: 100 | 101 | - [Steampipe](https://steampipe.io/downloads) 102 | - [Golang](https://golang.org/doc/install) 103 | 104 | Clone: 105 | 106 | ```sh 107 | git clone https://github.com/turbot/steampipe-plugin-jira.git 108 | cd steampipe-plugin-jira 109 | ``` 110 | 111 | Build, which automatically installs the new version to your `~/.steampipe/plugins` directory: 112 | 113 | ``` 114 | make 115 | ``` 116 | 117 | Configure the plugin: 118 | 119 | ``` 120 | cp config/* ~/.steampipe/config 121 | vi ~/.steampipe/config/jira.spc 122 | ``` 123 | 124 | Try it! 125 | 126 | ``` 127 | steampipe query 128 | > .inspect jira 129 | ``` 130 | 131 | Further reading: 132 | 133 | - [Writing plugins](https://steampipe.io/docs/develop/writing-plugins) 134 | - [Writing your first table](https://steampipe.io/docs/develop/writing-your-first-table) 135 | 136 | ## Open Source & Contributing 137 | 138 | This repository is published under the [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0) (source code) and [CC BY-NC-ND](https://creativecommons.org/licenses/by-nc-nd/2.0/) (docs) licenses. Please see our [code of conduct](https://github.com/turbot/.github/blob/main/CODE_OF_CONDUCT.md). We look forward to collaborating with you! 139 | 140 | [Steampipe](https://steampipe.io) is a product produced from this open source software, exclusively by [Turbot HQ, Inc](https://turbot.com). It is distributed under our commercial terms. Others are allowed to make their own distribution of the software, but cannot use any of the Turbot trademarks, cloud services, etc. You can learn more in our [Open Source FAQ](https://turbot.com/open-source). 141 | 142 | ## Get Involved 143 | 144 | **[Join #steampipe on Slack →](https://turbot.com/community/join)** 145 | 146 | Want to help but don't know where to start? Pick up one of the `help wanted` issues: 147 | 148 | - [Steampipe](https://github.com/turbot/steampipe/labels/help%20wanted) 149 | - [Jira Plugin](https://github.com/turbot/steampipe-plugin-jira/labels/help%20wanted) 150 | -------------------------------------------------------------------------------- /config/jira.spc: -------------------------------------------------------------------------------- 1 | connection "jira" { 2 | plugin = "jira" 3 | 4 | # The baseUrl of your Jira Instance API 5 | # Can also be set with the JIRA_URL environment variable. 6 | # base_url = "https://your-domain.atlassian.net/" 7 | 8 | # The user name to access the jira cloud instance 9 | # Can also be set with the `JIRA_USER` environment variable. 10 | # username = "abcd@xyz.com" 11 | 12 | # Access Token for which to use for the API 13 | # Can also be set with the `JIRA_TOKEN` environment variable. 14 | # You should leave it empty if you are using a Personal Access Token (PAT) 15 | # token = "8WqcdT0rvIZpCjtDqReF48B1" 16 | 17 | # Personal Access Tokens are a safe alternative to using username and password for authentication. 18 | # This token is used in self-hosted Jira instances. 19 | # Can also be set with the `JIRA_PERSONAL_ACCESS_TOKEN` environment variable. 20 | # Personal Access Token can only be used to query jira_backlog_issue, jira_board, jira_issue and jira_sprint tables. 21 | # personal_access_token = "MDU0MDMx7cE25TQ3OujDfy/vkv/eeSXXoh/zXY1ex9cp" 22 | } 23 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | organization: Turbot 3 | category: ["software development"] 4 | icon_url: "/images/plugins/turbot/jira.svg" 5 | brand_color: "#2684FF" 6 | display_name: "Jira" 7 | short_name: "jira" 8 | description: "Steampipe plugin for querying sprints, issues, epics and more from Jira." 9 | og_description: "Query Jira with SQL! Open source CLI. No DB required." 10 | og_image: "/images/plugins/turbot/jira-social-graphic.png" 11 | engines: ["steampipe", "sqlite", "postgres", "export"] 12 | --- 13 | 14 | # Jira + Steampipe 15 | 16 | [Jira](https://www.atlassian.com/software/jira) provides on-demand cloud computing platforms and APIs to plan, 17 | track, and release great software. 18 | 19 | [Steampipe](https://steampipe.io) is an open-source zero-ETL engine to instantly query cloud APIs using SQL. 20 | 21 | List users in your Jira account: 22 | 23 | ```sql 24 | select 25 | display_name, 26 | account_type as type, 27 | active as status, 28 | account_id 29 | from 30 | jira_user; 31 | ``` 32 | 33 | ``` 34 | +-------------------------------+-----------+--------+-----------------------------+ 35 | | display_name | type | status | account_id | 36 | +-------------------------------+-----------+--------+-----------------------------+ 37 | | Confluence Analytics (System) | app | true | 557058:cbc04d7be567aa5332c6 | 38 | | John Smyth | atlassian | true | 1f2e1d34e0e56a001ea44fc1 | 39 | +-------------------------------+-----------+--------+-----------------------------+ 40 | ``` 41 | 42 | ## Documentation 43 | 44 | - **[Table definitions & examples →](/plugins/turbot/jira/tables)** 45 | 46 | ## Get started 47 | 48 | ### Install 49 | 50 | Download and install the latest Jira plugin: 51 | 52 | ```bash 53 | steampipe plugin install jira 54 | ``` 55 | 56 | ### Credentials 57 | 58 | | Item | Description | 59 | | :---------- | :------------------------------------------------------------------------------------------------------------------------------------- | 60 | | Credentials | Jira requires an [API token](https://id.atlassian.com/manage-profile/security/api-tokens), site base url and username for all requests. | 61 | | Radius | Each connection represents a single Jira site. | 62 | 63 | 64 | 65 | ### Configuration 66 | 67 | Installing the latest jira plugin will create a config file (`~/.steampipe/config/jira.spc`) with a single connection named `jira`: 68 | 69 | ```hcl 70 | connection "jira" { 71 | plugin = "jira" 72 | 73 | # The baseUrl of your Jira Instance API 74 | # Can also be set with the JIRA_URL environment variable. 75 | # base_url = "https://your-domain.atlassian.net/" 76 | 77 | # The user name to access the jira cloud instance 78 | # Can also be set with the `JIRA_USER` environment variable. 79 | # username = "abcd@xyz.com" 80 | 81 | # Access Token for which to use for the API 82 | # Can also be set with the `JIRA_TOKEN` environment variable. 83 | # You should leave it empty if you are using a Personal Access Token (PAT) 84 | # token = "8WqcdT0rvIZpCjtDqReF48B1" 85 | 86 | # Personal Access Tokens are a safe alternative to using username and password for authentication. 87 | # This token is used in self-hosted Jira instances. 88 | # Can also be set with the `JIRA_PERSONAL_ACCESS_TOKEN` environment variable. 89 | # Personal Access Token can only be used to query jira_backlog_issue, jira_board, jira_issue and jira_sprint tables. 90 | # personal_access_token = "MDU0MDMx7cE25TQ3OujDfy/vkv/eeSXXoh/zXY1ex9cp" 91 | } 92 | ``` 93 | 94 | - `base_url` - The site url of your attlassian jira subscription. 95 | - `personal_access_token` - [API PAT](https://confluence.atlassian.com/enterprise/using-personal-access-tokens-1026032365.html) for self hosted Jira instances. This access token can only be used to query `jira_backlog_issue`, `jira_board`, `jira_issue` and `jira_sprint` tables. 96 | - `token` - [API token](https://id.atlassian.com/manage-profile/security/api-tokens) for user's Atlassian account. 97 | - `username` - Email address of agent user who have permission to access the API. 98 | 99 | Alternatively, you can also use the standard Jira environment variables to obtain credentials **only if other arguments (`base_url`, `username` and `token` or `personal_access_token`) are not specified** in the connection: 100 | 101 | ```sh 102 | export JIRA_URL=https://your-domain.atlassian.net/ 103 | export JIRA_USER=abcd@xyz.com 104 | export JIRA_TOKEN=8WqcdT0rvIZpCjtDqReF48B1 105 | export JIRA_PERSONAL_ACCESS_TOKEN="MDU0MDMx7cE25TQ3OujDfy/vkv/eeSXXoh/zXY1ex9cp" 106 | ``` 107 | 108 | 109 | -------------------------------------------------------------------------------- /docs/tables/jira_advanced_setting.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Steampipe Table: jira_advanced_setting - Query Jira Advanced Settings using SQL" 3 | description: "Allows users to query Advanced Settings in Jira, specifically the key and value pairs of settings, providing insights into system configurations and potential modifications." 4 | --- 5 | 6 | # Table: jira_advanced_setting - Query Jira Advanced Settings using SQL 7 | 8 | Jira Advanced Settings is a feature within Atlassian's Jira that allows you to customize and configure the system to suit your organization's needs. It provides a way to set up and manage key-value pairs for various settings, including index path, attachment size, and more. Jira Advanced Settings helps you stay informed about the current configurations and take appropriate actions when modifications are needed. 9 | 10 | ## Table Usage Guide 11 | 12 | The `jira_advanced_setting` table provides insights into the advanced settings within Jira. As a system administrator, explore setting-specific details through this table, including the key and value pairs of each setting. Utilize it to uncover information about system configurations, such as attachment size, index path, and the possibility of potential modifications. 13 | 14 | ## Examples 15 | 16 | ### Basic info 17 | Explore the advanced settings in your Jira instance to understand their types and associated keys. This can be useful in assessing the current configuration and identifying areas for optimization or troubleshooting. 18 | 19 | ```sql+postgres 20 | select 21 | id, 22 | name, 23 | key, 24 | type 25 | from 26 | jira_advanced_setting; 27 | ``` 28 | 29 | ```sql+sqlite 30 | select 31 | id, 32 | name, 33 | key, 34 | type 35 | from 36 | jira_advanced_setting; 37 | ``` 38 | 39 | ### list advanced settings that supports string type value 40 | Explore advanced settings within Jira that support string type values. This can be useful for configuring and customizing your Jira environment to best suit your needs. 41 | 42 | ```sql+postgres 43 | select 44 | id, 45 | name, 46 | key, 47 | type 48 | from 49 | jira_advanced_setting 50 | where 51 | type = 'string'; 52 | ``` 53 | 54 | ```sql+sqlite 55 | select 56 | id, 57 | name, 58 | key, 59 | type 60 | from 61 | jira_advanced_setting 62 | where 63 | type = 'string'; 64 | ``` -------------------------------------------------------------------------------- /docs/tables/jira_backlog_issue.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Steampipe Table: jira_backlog_issue - Query Jira Backlog Issues using SQL" 3 | description: "Allows users to query Jira Backlog Issues, providing an overview of all issues currently in the backlog of a Jira project." 4 | --- 5 | 6 | # Table: jira_backlog_issue - Query Jira Backlog Issues using SQL 7 | 8 | Jira is a project management tool used for issue tracking, bug tracking, and agile project management. A Jira Backlog Issue refers to a task or a bug that has been identified but is not currently being worked on. These issues are stored in the backlog, a list of tasks or bugs that need to be addressed but have not yet been prioritized for action. 9 | 10 | ## Table Usage Guide 11 | 12 | The `jira_backlog_issue` table provides insights into the backlog issues within a Jira project. As a project manager or a software developer, you can use this table to explore details of each issue, including its status, priority, and assignee. This can help you prioritize tasks, manage project workflows, and ensure timely resolution of bugs and tasks. 13 | 14 | ## Examples 15 | 16 | ### Basic info 17 | Explore which projects have been created, who initiated them, their current status, and a brief summary. This information can be useful to gain an overview of ongoing projects and their progress. 18 | 19 | ```sql+postgres 20 | select 21 | key, 22 | project_key, 23 | created, 24 | creator_display_name, 25 | status, 26 | summary 27 | from 28 | jira_backlog_issue; 29 | ``` 30 | 31 | ```sql+sqlite 32 | select 33 | key, 34 | project_key, 35 | created, 36 | creator_display_name, 37 | status, 38 | summary 39 | from 40 | jira_backlog_issue; 41 | ``` 42 | 43 | ### List backlog issues for a specific project 44 | Explore the status and details of pending tasks within a specific project to manage workload and track progress effectively. This can help in prioritizing tasks and assigning them to the right team members. 45 | 46 | ```sql+postgres 47 | select 48 | id, 49 | key, 50 | project_key, 51 | created, 52 | creator_display_name, 53 | assignee_display_name, 54 | status, 55 | summary 56 | from 57 | jira_backlog_issue 58 | where 59 | project_key = 'TEST1'; 60 | ``` 61 | 62 | ```sql+sqlite 63 | select 64 | id, 65 | key, 66 | project_key, 67 | created, 68 | creator_display_name, 69 | assignee_display_name, 70 | status, 71 | summary 72 | from 73 | jira_backlog_issue 74 | where 75 | project_key = 'TEST1'; 76 | ``` 77 | 78 | ### List backlog issues assigned to a specific user 79 | Explore which backlog issues are assigned to a specific user to manage and prioritize their workload efficiently. This is useful in tracking project progress and ensuring tasks are evenly distributed among team members. 80 | 81 | ```sql+postgres 82 | select 83 | id, 84 | key, 85 | summary, 86 | project_key, 87 | status, 88 | assignee_display_name, 89 | assignee_account_id 90 | from 91 | jira_backlog_issue 92 | where 93 | assignee_display_name = 'sayan'; 94 | ``` 95 | 96 | ```sql+sqlite 97 | select 98 | id, 99 | key, 100 | summary, 101 | project_key, 102 | status, 103 | assignee_display_name, 104 | assignee_account_id 105 | from 106 | jira_backlog_issue 107 | where 108 | assignee_display_name = 'sayan'; 109 | ``` 110 | 111 | ### List backlog issues due in 30 days 112 | Explore which backlog issues are due within the next 30 days to better manage your project timeline and delegate tasks effectively. This can assist in prioritizing work and ensuring that deadlines are met. 113 | 114 | ```sql+postgres 115 | select 116 | id, 117 | key, 118 | summary, 119 | project_key, 120 | status, 121 | assignee_display_name, 122 | assignee_account_id, 123 | due_date 124 | from 125 | jira_backlog_issue 126 | where 127 | due_date > current_date 128 | and due_date <= (current_date + interval '30' day); 129 | ``` 130 | 131 | ```sql+sqlite 132 | select 133 | id, 134 | key, 135 | summary, 136 | project_key, 137 | status, 138 | assignee_display_name, 139 | assignee_account_id, 140 | due_date 141 | from 142 | jira_backlog_issue 143 | where 144 | due_date > date('now') 145 | and due_date <= date('now', '+30 day'); 146 | ``` -------------------------------------------------------------------------------- /docs/tables/jira_board.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Steampipe Table: jira_board - Query Jira Boards using SQL" 3 | description: "Allows users to query Jira Boards, providing detailed insights into board configurations, types, and associated projects." 4 | --- 5 | 6 | # Table: jira_board - Query Jira Boards using SQL 7 | 8 | Jira Boards is a feature within Atlassian's Jira Software that allows teams to visualize their work. Boards can be customized to fit the unique workflow of any team, making it easier to manage tasks and projects. They provide a visual and interactive interface to track the progress of work. 9 | 10 | ## Table Usage Guide 11 | 12 | The `jira_board` table provides insights into Jira Boards within Atlassian's Jira Software. As a project manager or a team lead, you can explore board-specific details through this table, including board configurations, types, and associated projects. Utilize it to uncover information about boards, such as their associated projects, the types of boards, and their configurations. 13 | 14 | ## Examples 15 | 16 | ### Basic info 17 | Explore the types of boards in your Jira project and identify any associated filters. This can help in understanding the organization and management of tasks within your project. 18 | 19 | ```sql+postgres 20 | select 21 | id, 22 | name, 23 | type, 24 | filter_id 25 | from 26 | jira_board; 27 | ``` 28 | 29 | ```sql+sqlite 30 | select 31 | id, 32 | name, 33 | type, 34 | filter_id 35 | from 36 | jira_board; 37 | ``` 38 | 39 | ### List all scrum boards 40 | Explore which project management boards are organized using the Scrum methodology. This can help you assess the prevalence and usage of this agile framework within your organization. 41 | 42 | ```sql+postgres 43 | select 44 | id, 45 | name, 46 | type, 47 | filter_id 48 | from 49 | jira_board 50 | where 51 | type = 'scrum'; 52 | ``` 53 | 54 | ```sql+sqlite 55 | select 56 | id, 57 | name, 58 | type, 59 | filter_id 60 | from 61 | jira_board 62 | where 63 | type = 'scrum'; 64 | ``` 65 | 66 | ### List sprints in a board 67 | Explore the various sprints associated with a specific board to manage project timelines effectively. This can help in tracking progress and identifying any bottlenecks in the project workflow. 68 | 69 | ```sql+postgres 70 | select 71 | s.board_id, 72 | b.name as board_name, 73 | b.type as board_type, 74 | s.id as sprint_id, 75 | s.name as sprint_name, 76 | start_date, 77 | end_date 78 | from 79 | jira_sprint as s, 80 | jira_board as b 81 | where 82 | s.board_id = b.id; 83 | ``` 84 | 85 | ```sql+sqlite 86 | select 87 | s.board_id, 88 | b.name as board_name, 89 | b.type as board_type, 90 | s.id as sprint_id, 91 | s.name as sprint_name, 92 | start_date, 93 | end_date 94 | from 95 | jira_sprint as s 96 | join 97 | jira_board as b 98 | on 99 | s.board_id = b.id; 100 | ``` -------------------------------------------------------------------------------- /docs/tables/jira_component.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Steampipe Table: jira_component - Query Jira Components using SQL" 3 | description: "Allows users to query Jira Components, specifically to retrieve details about individual components within a Jira project, providing insights into component name, description, lead details, and project keys." 4 | --- 5 | 6 | # Table: jira_component - Query Jira Components using SQL 7 | 8 | A Jira Component is a subsection of a project. They are used to group issues within a project into smaller parts. You can set a default assignee for a component, which will override the project's default assignee. 9 | 10 | ## Table Usage Guide 11 | 12 | The `jira_component` table provides insights into the components within a Jira project. As a Project Manager or Developer, explore component-specific details through this table, including component name, description, lead details, and project keys. Utilize it to manage and organize issues within a project, making project management more efficient and streamlined. 13 | 14 | ## Examples 15 | 16 | ### Basic info 17 | Explore which components of a project have the most issues, helping to identify areas that may need additional resources or attention. 18 | 19 | ```sql+postgres 20 | select 21 | id, 22 | name, 23 | project, 24 | issue_count 25 | from 26 | jira_component; 27 | ``` 28 | 29 | ```sql+sqlite 30 | select 31 | id, 32 | name, 33 | project, 34 | issue_count 35 | from 36 | jira_component; 37 | ``` 38 | 39 | ### List components having issues 40 | Determine the areas in which components are experiencing issues, allowing you to assess and address problem areas within your projects effectively. 41 | 42 | ```sql+postgres 43 | select 44 | id, 45 | name, 46 | project, 47 | issue_count 48 | from 49 | jira_component 50 | where 51 | issue_count > 0; 52 | ``` 53 | 54 | ```sql+sqlite 55 | select 56 | id, 57 | name, 58 | project, 59 | issue_count 60 | from 61 | jira_component 62 | where 63 | issue_count > 0; 64 | ``` 65 | 66 | ### List components with no leads 67 | Determine the areas in your project where components lack assigned leads. This can help in identifying potential bottlenecks and ensuring responsibilities are properly delegated. 68 | 69 | ```sql+postgres 70 | select 71 | id, 72 | name, 73 | project, 74 | issue_count, 75 | lead_display_name 76 | from 77 | jira_component 78 | where 79 | lead_display_name = ''; 80 | ``` 81 | 82 | ```sql+sqlite 83 | select 84 | id, 85 | name, 86 | project, 87 | issue_count, 88 | lead_display_name 89 | from 90 | jira_component 91 | where 92 | lead_display_name = ''; 93 | ``` -------------------------------------------------------------------------------- /docs/tables/jira_dashboard.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Steampipe Table: jira_dashboard - Query Jira Dashboards using SQL" 3 | description: "Allows users to query Jira Dashboards, providing insights into the various dashboards available in a Jira Software instance." 4 | --- 5 | 6 | # Table: jira_dashboard - Query Jira Dashboards using SQL 7 | 8 | Jira Software is a project management tool developed by Atlassian. It provides a platform for planning, tracking, and releasing software, and is widely used by agile teams. A key feature of Jira Software are dashboards, which provide a customizable and flexible view of a project's progress and status. 9 | 10 | ## Table Usage Guide 11 | 12 | The `jira_dashboard` table provides insights into the various dashboards available within a Jira Software instance. As a project manager or team lead, explore dashboard-specific details through this table, including the owner, viewability, and associated projects. Utilize it to uncover information about dashboards, such as those that are shared with everyone, the ones owned by a specific user, and the projects associated with each dashboard. 13 | 14 | ## Examples 15 | 16 | ### Basic info 17 | Gain insights into your favorite Jira dashboards and their respective owners. This can help you understand who is responsible for the dashboards you frequently use. 18 | 19 | ```sql+postgres 20 | select 21 | id, 22 | name, 23 | is_favourite, 24 | owner_account_id, 25 | owner_display_name 26 | from 27 | jira_dashboard; 28 | ``` 29 | 30 | ```sql+sqlite 31 | select 32 | id, 33 | name, 34 | is_favourite, 35 | owner_account_id, 36 | owner_display_name 37 | from 38 | jira_dashboard; 39 | ``` 40 | 41 | ### Get share permissions details 42 | Explore which Jira dashboards have specific share permissions. This can help you understand how information is being disseminated, ensuring the right teams have access to the right data. 43 | 44 | ```sql+postgres 45 | select 46 | id, 47 | name, 48 | owner_display_name, 49 | popularity, 50 | jsonb_pretty(share_permissions) as share_permissions 51 | from 52 | jira_dashboard; 53 | ``` 54 | 55 | ```sql+sqlite 56 | select 57 | id, 58 | name, 59 | owner_display_name, 60 | popularity, 61 | share_permissions 62 | from 63 | jira_dashboard; 64 | ``` -------------------------------------------------------------------------------- /docs/tables/jira_epic.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Steampipe Table: jira_epic - Query Jira Epics using SQL" 3 | description: "Allows users to query Jira Epics, providing insights into the progress, status, and details of each epic within a Jira project." 4 | --- 5 | 6 | # Table: jira_epic - Query Jira Epics using SQL 7 | 8 | Jira is a project management tool developed by Atlassian. It provides a platform for planning, tracking, and managing agile software development projects. An Epic in Jira is a large user story that can be broken down into several smaller stories. 9 | 10 | ## Table Usage Guide 11 | 12 | The `jira_epic` table provides insights into Epics within Jira. As a project manager or a team lead, explore Epic-specific details through this table, including progress, status, and associated tasks. Utilize it to uncover information about Epics, such as those that are overdue, the relationship between tasks and Epics, and the overall progress of a project. 13 | 14 | ## Examples 15 | 16 | ### Basic info 17 | Explore the status and summary of various tasks within a project management tool to understand their progression and key details. This can help in monitoring progress and identifying any bottlenecks or issues that need to be addressed. 18 | 19 | ```sql+postgres 20 | select 21 | id, 22 | name, 23 | key, 24 | done as status, 25 | summary 26 | from 27 | jira_epic; 28 | ``` 29 | 30 | ```sql+sqlite 31 | select 32 | id, 33 | name, 34 | key, 35 | done as status, 36 | summary 37 | from 38 | jira_epic; 39 | ``` 40 | 41 | ### List issues in epic 42 | Explore which tasks are associated with which project milestones by identifying instances where issue status, creation date, and assignee are linked with a specific project epic. This can help in assessing the distribution and management of tasks across different project stages. 43 | 44 | ```sql+postgres 45 | select 46 | i.id as issue_id, 47 | i.status as issue_status, 48 | i.created as issue_created, 49 | i.creator_display_name, 50 | i.assignee_display_name, 51 | e.id as epic_id, 52 | e.name as epic_name, 53 | i.summary as issue_summary 54 | from 55 | jira_epic as e, 56 | jira_issue as i 57 | where 58 | i.epic_key = e.key; 59 | ``` 60 | 61 | ```sql+sqlite 62 | select 63 | i.id as issue_id, 64 | i.status as issue_status, 65 | i.created as issue_created, 66 | i.creator_display_name, 67 | i.assignee_display_name, 68 | e.id as epic_id, 69 | e.name as epic_name, 70 | i.summary as issue_summary 71 | from 72 | jira_epic as e, 73 | jira_issue as i 74 | where 75 | i.epic_key = e.key; 76 | ``` -------------------------------------------------------------------------------- /docs/tables/jira_global_setting.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Steampipe Table: jira_global_setting - Query Jira Global Settings using SQL" 3 | description: "Allows users to query Jira Global Settings, providing insights into system wide settings and configurations." 4 | --- 5 | 6 | # Table: jira_global_setting - Query Jira Global Settings using SQL 7 | 8 | Jira Global Settings is a set of system-wide configurations in the Jira tool that allows administrators to control various aspects such as user permissions, security settings, and other system configurations. It provides a centralized way to manage and control the overall behavior and functionalities of the Jira system. Jira Global Settings help to maintain the system's integrity, security, and performance by managing the settings at a global level. 9 | 10 | ## Table Usage Guide 11 | 12 | The `jira_global_setting` table provides insights into global settings within Jira. As a Jira administrator, explore system-wide details through this table, including security settings, user permissions, and other system configurations. Utilize it to uncover information about the overall behavior and functionalities of the Jira system, as well as to maintain the system's integrity, security, and performance. -------------------------------------------------------------------------------- /docs/tables/jira_group.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Steampipe Table: jira_group - Query Jira Groups using SQL" 3 | description: "Allows users to query Jira Groups, providing insights into the various user groups within a Jira instance." 4 | --- 5 | 6 | # Table: jira_group - Query Jira Groups using SQL 7 | 8 | Jira Groups are a collection of users within an instance of Jira, a popular project management tool. Groups offer an efficient way to manage a collection of users. They can be used to set permissions, restrict access, and manage notifications across a Jira instance. 9 | 10 | ## Table Usage Guide 11 | 12 | The `jira_group` table provides insights into the user groups within a Jira instance. As a project manager or system administrator, you can explore group-specific details through this table, including group names, users within each group, and associated permissions. Utilize it to manage access control, set permissions, and streamline user management within your Jira instance. 13 | 14 | ## Examples 15 | 16 | ### Basic info 17 | Explore the different groups present in your Jira setup, allowing you to manage and organize users more effectively. This can be particularly useful when you need to assign tasks to specific groups or manage permissions. 18 | 19 | ```sql+postgres 20 | select 21 | name, 22 | id 23 | from 24 | jira_group; 25 | ``` 26 | 27 | ```sql+sqlite 28 | select 29 | name, 30 | id 31 | from 32 | jira_group; 33 | ``` 34 | 35 | ### Get associated users 36 | Discover the segments that are associated with specific users in a group. This can help in managing user access and permissions effectively. 37 | 38 | ```sql+postgres 39 | select 40 | name as group_name, 41 | u.display_name as user_name, 42 | user_id, 43 | u.email_address as user_email 44 | from 45 | jira_group as g, 46 | jsonb_array_elements_text(member_ids) as user_id, 47 | jira_user as u 48 | where 49 | user_id = u.account_id 50 | order by 51 | group_name, 52 | user_name; 53 | ``` 54 | 55 | ```sql+sqlite 56 | select 57 | g.name as group_name, 58 | u.display_name as user_name, 59 | user_id.value as user_id, 60 | u.email_address as user_email 61 | from 62 | jira_group as g, 63 | json_each(g.member_ids) as user_id, 64 | jira_user as u 65 | where 66 | user_id.value = u.account_id 67 | order by 68 | group_name, 69 | user_name; 70 | ``` -------------------------------------------------------------------------------- /docs/tables/jira_issue.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Steampipe Table: jira_issue - Query Jira Issues using SQL" 3 | description: "Allows users to query Jira Issues, specifically providing insights into issue details such as status, assignee, reporter, project, and more." 4 | --- 5 | 6 | # Table: jira_issue - Query Jira Issues using SQL 7 | 8 | Jira is a project management tool developed by Atlassian, widely used for issue tracking, bug tracking, and agile project management. It allows teams to manage, plan, track, and release software, ensuring transparency and team collaboration. Jira's issues, the core units of Jira, are used to track individual pieces of work that need to be completed. 9 | 10 | ## Table Usage Guide 11 | 12 | The `jira_issue` table provides insights into Jira issues within a project. As a project manager or software developer, explore issue-specific details through this table, including status, assignee, reporter, and associated metadata. Utilize it to uncover information about issues, such as those unassigned, those in progress, and to verify project timelines. 13 | 14 | ## Examples 15 | 16 | ### Basic info 17 | Discover the segments that detail the creation and status of a project. This is useful to gain insights into project timelines, creators, and their current progress. 18 | 19 | ```sql+postgres 20 | select 21 | key, 22 | project_key, 23 | created, 24 | creator_display_name, 25 | status, 26 | summary 27 | from 28 | jira_issue; 29 | ``` 30 | 31 | ```sql+sqlite 32 | select 33 | key, 34 | project_key, 35 | created, 36 | creator_display_name, 37 | status, 38 | summary 39 | from 40 | jira_issue; 41 | ``` 42 | 43 | ### List issues for a specific project 44 | Explore the status and details of issues related to a specific project. This can aid in understanding the project's progress, identifying any roadblocks, and planning further actions effectively. 45 | 46 | ```sql+postgres 47 | select 48 | id, 49 | key, 50 | project_key, 51 | created, 52 | creator_display_name, 53 | assignee_display_name, 54 | status, 55 | summary 56 | from 57 | jira_issue 58 | where 59 | project_key = 'TEST'; 60 | ``` 61 | 62 | ```sql+sqlite 63 | select 64 | id, 65 | key, 66 | project_key, 67 | created, 68 | creator_display_name, 69 | assignee_display_name, 70 | status, 71 | summary 72 | from 73 | jira_issue 74 | where 75 | project_key = 'TEST'; 76 | ``` 77 | 78 | ### List all issues assigned to a specific user 79 | Explore which tasks are allocated to a particular individual, allowing you to gain insights into their workload and responsibilities. This is particularly useful for project management and task distribution. 80 | 81 | ```sql+postgres 82 | select 83 | id, 84 | key, 85 | summary, 86 | project_key, 87 | status, 88 | assignee_display_name, 89 | assignee_account_id 90 | from 91 | jira_issue 92 | where 93 | assignee_display_name = 'Lalit Bhardwaj'; 94 | ``` 95 | 96 | ```sql+sqlite 97 | select 98 | id, 99 | key, 100 | summary, 101 | project_key, 102 | status, 103 | assignee_display_name, 104 | assignee_account_id 105 | from 106 | jira_issue 107 | where 108 | assignee_display_name = 'Lalit Bhardwaj'; 109 | ``` 110 | 111 | ### List issues due in the next week 112 | Explore upcoming tasks by identifying issues scheduled for completion within the next week. This can help in prioritizing work and managing team assignments effectively. 113 | 114 | ```sql+postgres 115 | select 116 | id, 117 | key, 118 | summary, 119 | project_key, 120 | status, 121 | assignee_display_name, 122 | assignee_account_id, 123 | duedate 124 | from 125 | jira_issue 126 | where 127 | duedate > current_date 128 | and duedate <= (current_date + interval '7' day); 129 | ``` 130 | 131 | ```sql+sqlite 132 | select 133 | id, 134 | key, 135 | summary, 136 | project_key, 137 | status, 138 | assignee_display_name, 139 | assignee_account_id, 140 | duedate 141 | from 142 | jira_issue 143 | where 144 | duedate > date('now') 145 | and duedate <= date('now', '+7 day'); 146 | ``` 147 | 148 | #### Get linked issue details for the issues 149 | The query enables deep analysis of linked issues within Jira. Each issue can have multiple links to other issues, that might represent dependencies, blockers, duplicates, or other relationships critical for project management and bug tracking. 150 | 151 | ```sql+postgres 152 | select 153 | ji.id, 154 | ji.title, 155 | ji.project_key, 156 | ji.status, 157 | il.issue_link_id, 158 | il.issue_link_self, 159 | il.issue_link_type_name, 160 | il.inward_issue_id, 161 | il.inward_issue_key, 162 | il.inward_issue_status_name, 163 | il.inward_issue_summary, 164 | il.inward_issue_priority_name 165 | from 166 | jira_issue ji, 167 | lateral jsonb_array_elements(ji.fields -> 'issuelinks') as il_data, 168 | lateral ( 169 | select 170 | il_data ->> 'id' as issue_link_id, 171 | il_data ->> 'self' as issue_link_self, 172 | il_data -> 'type' ->> 'name' as issue_link_type_name, 173 | il_data -> 'inwardIssue' ->> 'id' as inward_issue_id, 174 | il_data -> 'inwardIssue' ->> 'key' as inward_issue_key, 175 | il_data -> 'inwardIssue' -> 'fields' -> 'status' ->> 'name' as inward_issue_status_name, 176 | il_data -> 'inwardIssue' -> 'fields' ->> 'summary' as inward_issue_summary, 177 | il_data -> 'inwardIssue' -> 'fields' -> 'priority' ->> 'name' as inward_issue_priority_name 178 | ) as il; 179 | ``` 180 | 181 | ```sql+sqlite 182 | select 183 | ji.id, 184 | ji.title, 185 | ji.project_key, 186 | ji.status, 187 | json_extract(il_data.value, '$.id') as issue_link_id, 188 | json_extract(il_data.value, '$.self') as issue_link_self, 189 | json_extract(il_data.value, '$.type.name') as issue_link_type_name, 190 | json_extract(il_data.value, '$.inwardIssue.id') as inward_issue_id, 191 | json_extract(il_data.value, '$.inwardIssue.key') as inward_issue_key, 192 | json_extract(il_data.value, '$.inwardIssue.fields.status.name') as inward_issue_status_name, 193 | json_extract(il_data.value, '$.inwardIssue.fields.summary') as inward_issue_summary, 194 | json_extract(il_data.value, '$.inwardIssue.fields.priority.name') as inward_issue_priority_name 195 | from 196 | jira_issue ji, 197 | json_each(json_extract(ji.fields, '$.issuelinks')) as il_data; 198 | ``` 199 | 200 | 201 | 202 | ### Get issues belonging to a sprint 203 | 1. "Explore which tasks are part of a particular sprint, allowing you to manage and prioritize your team's workflow effectively." 204 | 2. "Identify all tasks that have been completed, providing a clear overview of your team's accomplishments and productivity." 205 | 3. "Determine the tasks that are currently awaiting support, helping you to allocate resources and address bottlenecks in your workflow." 206 | 4. "Review the different status categories within a specific project, offering insights into the project's progress and potential areas for improvement. 207 | 208 | ```sql+postgres 209 | select 210 | id, 211 | key, 212 | summary, 213 | project_key, 214 | status, 215 | assignee_display_name, 216 | assignee_account_id, 217 | duedate 218 | from 219 | jira_issue 220 | where 221 | sprint_ids @> '2'; 222 | ``` 223 | 224 | ```sql+sqlite 225 | Error: SQLite does not support array contains operator '@>'. 226 | ``` 227 | 228 | #### List all issues in status category 'Done' 229 | 230 | ```sql+postgres 231 | select 232 | id, 233 | key, 234 | summary, 235 | status, 236 | status_category, 237 | assignee_display_name 238 | from 239 | jira_issue 240 | where 241 | status_category = 'Done'; 242 | ``` 243 | 244 | ```sql+sqlite 245 | select 246 | id, 247 | key, 248 | summary, 249 | status, 250 | status_category, 251 | assignee_display_name 252 | from 253 | jira_issue 254 | where 255 | status_category = 'Done'; 256 | ``` 257 | 258 | #### List all issues in status Waiting for Support 259 | 260 | ```sql+postgres 261 | select 262 | id, 263 | key, 264 | summary, 265 | status, 266 | status_category, 267 | assignee_display_name 268 | from 269 | jira_issue 270 | where 271 | status = 'Waiting for support'; 272 | ``` 273 | 274 | ```sql+sqlite 275 | select 276 | id, 277 | key, 278 | summary, 279 | status, 280 | status_category, 281 | assignee_display_name 282 | from 283 | jira_issue 284 | where 285 | status = 'Waiting for support'; 286 | ``` 287 | 288 | #### List all possible status for each status_category for a speficic project 289 | 290 | ```sql+postgres 291 | select distinct 292 | project_key, 293 | status_category, 294 | status 295 | from 296 | jira_issue 297 | where 298 | project_key = 'PROJECT-KEY' 299 | order by 300 | status_category; 301 | ``` 302 | 303 | ```sql+sqlite 304 | select distinct 305 | project_key, 306 | status_category, 307 | status 308 | from 309 | jira_issue 310 | where 311 | project_key = 'PROJECT-KEY' 312 | order by 313 | status_category; 314 | ``` 315 | -------------------------------------------------------------------------------- /docs/tables/jira_issue_comment.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Steampipe Table: jira_issue_comment - Query Jira Issue Comments using SQL" 3 | description: "Allows users to query Jira Issue Comments, providing insights into the details of comments made on issues within the Jira platform." 4 | --- 5 | 6 | # Table: jira_issue_comment - Query Jira Issue Comments using SQL 7 | 8 | Jira is a project management tool that aids in issue tracking and agile project management. It is widely used by software development teams for planning, tracking progress, and releasing software. Issue comments in Jira serve as a communication medium on the platform, allowing users to discuss, provide updates, and track changes regarding specific issues. 9 | 10 | ## Table Usage Guide 11 | 12 | The `jira_issue_comment` table provides insights into the comments made on issues within the Jira platform. As a project manager or a team member, you can explore comment-specific details through this table, including the author of the comment, creation date, and the issue the comment is associated with. Utilize it to track communication, understand the context of discussions, and monitor the progress of issues. 13 | 14 | *Important Note:* 15 | - You *MUST* specify the `issue_id` in the WHERE or JOIN clause in order to query this table. 16 | 17 | ## Examples 18 | 19 | ### Basic info 20 | Explore the comments on different issues in Jira by identifying their unique identifiers and authors. This can be useful in understanding who is actively participating in discussions and contributing to issue resolutions. 21 | 22 | ```sql+postgres 23 | select 24 | id, 25 | self, 26 | issue_id, 27 | author 28 | from 29 | jira_issue_comment 30 | where 31 | issue_id = '12345'; 32 | ``` 33 | 34 | ```sql+sqlite 35 | select 36 | id, 37 | self, 38 | issue_id, 39 | author 40 | from 41 | jira_issue_comment 42 | where 43 | issue_id = '12345'; 44 | ``` 45 | 46 | ### List comments that are hidden in Service Desk for a issue 47 | Discover the segments that contain hidden comments in the Service Desk to gain insights into user feedback or issues that may not be publicly visible. This can be useful in understanding customer concerns and improving service quality. 48 | 49 | ```sql+postgres 50 | select 51 | id, 52 | self, 53 | issue_id, 54 | body, 55 | created, 56 | jsd_public 57 | from 58 | jira_issue_comment 59 | where 60 | jsd_public 61 | and issue_id = '12345'; 62 | ``` 63 | 64 | ```sql+sqlite 65 | select 66 | id, 67 | self, 68 | issue_id, 69 | body, 70 | created, 71 | jsd_public 72 | from 73 | jira_issue_comment 74 | where 75 | jsd_public 76 | and issue_id = '12345'; 77 | ``` 78 | 79 | ### List comments posted in the last 5 days for a particular issues 80 | Explore recent feedback or updates on a specific issue by identifying comments posted in the last five days. This can help in tracking the progress of issue resolution and understanding the current discussion around it. 81 | 82 | ```sql+postgres 83 | select 84 | id, 85 | issue_id, 86 | body, 87 | created 88 | from 89 | jira_issue_comment 90 | where 91 | created >= now() - interval '5' day 92 | and issue_id = '10021'; 93 | ``` 94 | 95 | ```sql+sqlite 96 | select 97 | id, 98 | issue_id, 99 | body, 100 | created 101 | from 102 | jira_issue_comment 103 | where 104 | created >= datetime('now', '-5 days') 105 | and issue_id = '10021'; 106 | ``` 107 | 108 | ### List comments that were updated in last 2 hours for a issue 109 | Explore recent activity by identifying comments that have been updated in the past two hours. This is useful for staying informed about ongoing discussions or changes in your Jira issues. 110 | 111 | ```sql+postgres 112 | select 113 | id, 114 | issue_id, 115 | body, 116 | created, 117 | updated 118 | from 119 | jira_issue_comment 120 | where 121 | issue_id = '12345' 122 | and updated >= now() - interval '2' hour; 123 | ``` 124 | 125 | ```sql+sqlite 126 | select 127 | id, 128 | issue_id, 129 | body, 130 | created, 131 | updated 132 | from 133 | jira_issue_comment 134 | where 135 | issue_id = '12345' 136 | and updated >= datetime('now', '-2 hours'); 137 | ``` 138 | 139 | ### Get author information of a particular issue comment 140 | Explore the identity of the individual who commented on a specific issue. This can be beneficial for understanding who is contributing to the discussion or if any particular individual's input requires further attention. 141 | 142 | ```sql+postgres 143 | select 144 | id, 145 | issue_id, 146 | author ->> 'accountId' as author_account_id, 147 | author ->> 'accountType' as author_account_type, 148 | author ->> 'displayName' as author_name, 149 | author ->> 'emailAddress' as author_email_address, 150 | author ->> 'timeZone' as author_time_zone 151 | from 152 | jira_issue_comment 153 | where 154 | id = '10015' 155 | and issue_id = '12345'; 156 | ``` 157 | 158 | ```sql+sqlite 159 | select 160 | id, 161 | issue_id, 162 | json_extract(author, '$.accountId') as author_account_id, 163 | json_extract(author, '$.accountType') as author_account_type, 164 | json_extract(author, '$.displayName') as author_name, 165 | json_extract(author, '$.emailAddress') as author_email_address, 166 | json_extract(author, '$.timeZone') as author_time_zone 167 | from 168 | jira_issue_comment 169 | where 170 | id = '10015' 171 | and issue_id = '12345'; 172 | ``` -------------------------------------------------------------------------------- /docs/tables/jira_issue_type.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Steampipe Table: jira_issue_type - Query Jira Issue Types using SQL" 3 | description: "Allows users to query Jira Issue Types, providing detailed information about issue types in a Jira project, including their name, description, and avatar URL." 4 | --- 5 | 6 | # Table: jira_issue_type - Query Jira Issue Types using SQL 7 | 8 | Jira Issue Types are a way to categorize different types of work items in a Jira project. They help in distinguishing different types of tasks, bugs, stories, epics, and more, enabling teams to organize, track, and manage their work efficiently. Each issue type can be customized to suit the specific needs of the project or team. 9 | 10 | ## Table Usage Guide 11 | 12 | The `jira_issue_type` table provides insights into Jira Issue Types within a project. As a project manager or a team lead, explore issue type details through this table, including their descriptions, names, and avatar URLs. Utilize it to get a comprehensive view of the different issue types in your project, aiding in better project management and task organization. 13 | 14 | ## Examples 15 | 16 | ### Basic info 17 | Explore the different types of issues in your Jira project. This helps you to understand the variety of tasks or problems that your team handles, providing clarity on the project's complexity and scope. 18 | 19 | ```sql+postgres 20 | select 21 | id, 22 | name, 23 | description, 24 | avatar_id 25 | from 26 | jira_issue_type; 27 | ``` 28 | 29 | ```sql+sqlite 30 | select 31 | id, 32 | name, 33 | description, 34 | avatar_id 35 | from 36 | jira_issue_type; 37 | ``` 38 | 39 | ### List issue types for a specific project 40 | Determine the types of issues associated with a specific project. This allows for a better understanding of the project's scope and potential challenges. 41 | 42 | ```sql+postgres 43 | select 44 | id, 45 | name, 46 | description, 47 | avatar_id, 48 | scope 49 | from 50 | jira_issue_type 51 | where 52 | scope -> 'project' ->> 'id' = '10000'; 53 | ``` 54 | 55 | ```sql+sqlite 56 | select 57 | id, 58 | name, 59 | description, 60 | avatar_id, 61 | scope 62 | from 63 | jira_issue_type 64 | where 65 | json_extract(json_extract(scope, '$.project'), '$.id') = '10000'; 66 | ``` 67 | 68 | ### List issue types associated with sub-task creation 69 | Explore the types of issues that are associated with the creation of sub-tasks in Jira. This can help you understand the different categories of problems that typically require the generation of sub-tasks. 70 | 71 | ```sql+postgres 72 | select 73 | id, 74 | name, 75 | description, 76 | avatar_id, 77 | subtask 78 | from 79 | jira_issue_type 80 | where 81 | subtask; 82 | ``` 83 | 84 | ```sql+sqlite 85 | select 86 | id, 87 | name, 88 | description, 89 | avatar_id, 90 | subtask 91 | from 92 | jira_issue_type 93 | where 94 | subtask = 1; 95 | ``` 96 | 97 | ### List issue types with hierarchy level 0 (Base) 98 | Explore which issue types in a Jira project are at the base level of the hierarchy. This can be beneficial in understanding the structure of your project and identifying potential areas for reorganization. 99 | 100 | ```sql+postgres 101 | select 102 | id, 103 | name, 104 | description, 105 | avatar_id, 106 | hierarchy_level 107 | from 108 | jira_issue_type 109 | where 110 | hierarchy_level = '0'; 111 | ``` 112 | 113 | ```sql+sqlite 114 | select 115 | id, 116 | name, 117 | description, 118 | avatar_id, 119 | hierarchy_level 120 | from 121 | jira_issue_type 122 | where 123 | hierarchy_level = '0'; 124 | ``` -------------------------------------------------------------------------------- /docs/tables/jira_issue_worklog.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Steampipe Table: jira_issue_worklog - Query Jira Issue Worklogs using SQL" 3 | description: "Allows users to query Jira Issue Worklogs, specifically the work done and the time spent on each issue, providing insights into project progress and individual contributions." 4 | --- 5 | 6 | # Table: jira_issue_worklog - Query Jira Issue Worklogs using SQL 7 | 8 | Jira is a project management tool that enables teams to plan, track, and manage their projects and tasks. Issue Worklogs in Jira represent the work done and the time spent on each issue. This information is crucial for tracking project progress, understanding individual contributions, and managing overall project timelines. 9 | 10 | ## Table Usage Guide 11 | 12 | The `jira_issue_worklog` table provides insights into the work done and the time spent on each issue in Jira. As a project manager or team leader, explore issue-specific details through this table, including the time spent, the work done, and the associated metadata. Utilize it to track project progress, understand individual contributions, and manage project timelines effectively. 13 | 14 | ## Examples 15 | 16 | ### Basic info 17 | Explore the work logs of different issues in Jira to understand who has made contributions and the nature of their input. This could be useful for tracking team progress, identifying bottlenecks, and understanding individual work patterns. 18 | 19 | ```sql+postgres 20 | select 21 | id, 22 | self, 23 | issue_id, 24 | comment, 25 | author 26 | from 27 | jira_issue_worklog; 28 | ``` 29 | 30 | ```sql+sqlite 31 | select 32 | id, 33 | self, 34 | issue_id, 35 | comment, 36 | author 37 | from 38 | jira_issue_worklog; 39 | ``` 40 | 41 | ### Get time logged for issues 42 | Explore which issues have had the most time logged on them. This can help prioritize resources by identifying which issues are consuming more time. 43 | 44 | ```sql+postgres 45 | select 46 | issue_id, 47 | sum(time_spent_seconds) as total_time_spent_seconds 48 | from 49 | jira_issue_worklog 50 | group by 51 | issue_id; 52 | ``` 53 | 54 | ```sql+sqlite 55 | select 56 | issue_id, 57 | sum(time_spent_seconds) as total_time_spent_seconds 58 | from 59 | jira_issue_worklog 60 | group by 61 | issue_id; 62 | ``` 63 | 64 | ### Show the latest worklogs for issues from the past 5 days 65 | Explore the recent workload by identifying issues that have been active in the past 5 days. This can help in assessing the work distribution and identifying potential bottlenecks in real-time. 66 | 67 | ```sql+postgres 68 | select 69 | id, 70 | issue_id, 71 | time_spent, 72 | created 73 | from 74 | jira_issue_worklog 75 | where 76 | created >= now() - interval '5' day; 77 | ``` 78 | 79 | ```sql+sqlite 80 | select 81 | id, 82 | issue_id, 83 | time_spent, 84 | created 85 | from 86 | jira_issue_worklog 87 | where 88 | created >= datetime('now', '-5 days'); 89 | ``` 90 | 91 | ### Retrieve issues and their worklogs updated in the last 10 days 92 | Analyze the settings to understand the recent workload changes and their impact on project priorities. This query is useful for tracking the progress of projects and tasks that have been updated or modified in the last 10 days. 93 | 94 | ```sql+postgres 95 | select distinct 96 | w.issue_id, 97 | w.id, 98 | w.time_spent, 99 | w.updated as worklog_updated_at, 100 | i.duedate, 101 | i.priority, 102 | i.project_name, 103 | i.key 104 | from 105 | jira_issue_worklog as w, 106 | jira_issue as i 107 | where 108 | i.id like trim(w.issue_id) 109 | and 110 | w.updated >= now() - interval '10' day; 111 | ``` 112 | 113 | ```sql+sqlite 114 | select distinct 115 | w.issue_id, 116 | w.id, 117 | w.time_spent, 118 | w.updated as worklog_updated_at, 119 | i.duedate, 120 | i.priority, 121 | i.project_name, 122 | i.key 123 | from 124 | jira_issue_worklog as w, 125 | jira_issue as i 126 | where 127 | i.id like trim(w.issue_id) 128 | and 129 | w.updated >= datetime('now', '-10 days'); 130 | ``` 131 | 132 | ### Get author information of worklogs 133 | Discover the segments that allow you to gain insights into the authors of worklogs, such as their account type, name, email address, and time zone. This is particularly useful for understanding who is contributing to specific issues and their geographical location. 134 | 135 | ```sql+postgres 136 | select 137 | id, 138 | issue_id, 139 | author ->> 'accountId' as author_account_id, 140 | author ->> 'accountType' as author_account_type, 141 | author ->> 'displayName' as author_name, 142 | author ->> 'emailAddress' as author_email_address, 143 | author ->> 'timeZone' as author_time_zone 144 | from 145 | jira_issue_worklog; 146 | ``` 147 | 148 | ```sql+sqlite 149 | select 150 | id, 151 | issue_id, 152 | json_extract(author, '$.accountId') as author_account_id, 153 | json_extract(author, '$.accountType') as author_account_type, 154 | json_extract(author, '$.displayName') as author_name, 155 | json_extract(author, '$.emailAddress') as author_email_address, 156 | json_extract(author, '$.timeZone') as author_time_zone 157 | from 158 | jira_issue_worklog; 159 | ``` -------------------------------------------------------------------------------- /docs/tables/jira_priority.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Steampipe Table: jira_priority - Query Jira Priorities using SQL" 3 | description: "Allows users to query Jira Priorities, specifically providing details about the priority levels assigned to different issues in a Jira project." 4 | --- 5 | 6 | # Table: jira_priority - Query Jira Priorities using SQL 7 | 8 | Jira is a project management tool used for issue tracking, bug tracking, and agile project management. The priority of an issue in Jira signifies its importance in relation to other issues. It is an attribute that can be set by the user when creating or editing issues. 9 | 10 | ## Table Usage Guide 11 | 12 | The `jira_priority` table provides insights into the priority levels assigned to different issues within a Jira project. As a project manager or agile team member, explore priority-specific details through this table, including descriptions, icons, and associated metadata. Utilize it to uncover information about priorities, such as their relative importance, to help in effective issue management and resolution. 13 | 14 | ## Examples 15 | 16 | ### Basic info 17 | Explore the priorities set in your Jira project management tool. This can help you understand how tasks are being prioritized, assisting in better project management and resource allocation. 18 | 19 | ```sql+postgres 20 | select 21 | name, 22 | id, 23 | description 24 | from 25 | jira_priority; 26 | ``` 27 | 28 | ```sql+sqlite 29 | select 30 | name, 31 | id, 32 | description 33 | from 34 | jira_priority; 35 | ``` 36 | 37 | ### List issues with high priority 38 | Discover the segments that have been assigned high priority issues in order to prioritize your team's workflow and address critical tasks more efficiently. 39 | 40 | ```sql+postgres 41 | select 42 | id as issue_no, 43 | description as issue_description, 44 | assignee_display_name as assigned_to 45 | from 46 | jira_issue 47 | where 48 | priority = 'High'; 49 | ``` 50 | 51 | ```sql+sqlite 52 | select 53 | id as issue_no, 54 | description as issue_description, 55 | assignee_display_name as assigned_to 56 | from 57 | jira_issue 58 | where 59 | priority = 'High'; 60 | ``` 61 | 62 | ### Count of issues per priority 63 | Determine the distribution of issues across different priority levels to better understand where the majority of concerns lie. This can help in prioritizing resources and efforts for issue resolution. 64 | 65 | ```sql+postgres 66 | select 67 | p.name as priority, 68 | count(i.id) as issue_count 69 | from 70 | jira_priority as p 71 | left join jira_issue as i on i.priority = p.name 72 | group by p.name; 73 | ``` 74 | 75 | ```sql+sqlite 76 | select 77 | p.name as priority, 78 | count(i.id) as issue_count 79 | from 80 | jira_priority as p 81 | left join jira_issue as i on i.priority = p.name 82 | group by p.name; 83 | ``` -------------------------------------------------------------------------------- /docs/tables/jira_project.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Steampipe Table: jira_project - Query Jira Projects using SQL" 3 | description: "Allows users to query Jira Projects, offering detailed insights into project-specific information such as project key, project type, project lead, and project category." 4 | --- 5 | 6 | # Table: jira_project - Query Jira Projects using SQL 7 | 8 | Jira is a popular project management tool used by software development teams to plan, track, and release software. It offers a range of features including bug tracking, issue tracking, and project management functionalities. Jira Projects are the primary containers in Jira where issues are created and tracked. 9 | 10 | ## Table Usage Guide 11 | 12 | The `jira_project` table provides insights into Projects within Jira. As a Project Manager or a Scrum Master, you can explore project-specific details through this table, including project key, project type, project lead, and project category. Use it to uncover information about projects, such as the project's status, the lead responsible for the project, and the category the project belongs to. 13 | 14 | ## Examples 15 | 16 | ### Basic info 17 | Explore the different projects within your Jira environment, gaining insights into key aspects like the project's name, ID, key, lead display name, category, and description. This can be useful for understanding the scope and management of your projects. 18 | 19 | ```sql+postgres 20 | select 21 | name, 22 | id, 23 | key, 24 | lead_display_name, 25 | project_category, 26 | description 27 | from 28 | jira_project; 29 | ``` 30 | 31 | ```sql+sqlite 32 | select 33 | name, 34 | id, 35 | key, 36 | lead_display_name, 37 | project_category, 38 | description 39 | from 40 | jira_project; 41 | ``` 42 | 43 | ### List all issues in a project 44 | Explore the status and details of all issues within a specific project. This can be useful for project management, allowing you to assess the workload and track the progress of tasks. 45 | 46 | ```sql+postgres 47 | select 48 | id, 49 | key, 50 | project_id, 51 | project_key, 52 | created, 53 | creator_display_name, 54 | assignee_display_name, 55 | status, 56 | summary 57 | from 58 | jira_issue 59 | where 60 | project_key = 'TEST'; 61 | ``` 62 | 63 | ```sql+sqlite 64 | select 65 | id, 66 | key, 67 | project_id, 68 | project_key, 69 | created, 70 | creator_display_name, 71 | assignee_display_name, 72 | status, 73 | summary 74 | from 75 | jira_issue 76 | where 77 | project_key = 'TEST'; 78 | ``` -------------------------------------------------------------------------------- /docs/tables/jira_project_role.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Steampipe Table: jira_project_role - Query Jira Project Roles using SQL" 3 | description: "Allows users to query Jira Project Roles, providing insights into role details, permissions, and associated users and groups." 4 | --- 5 | 6 | # Table: jira_project_role - Query Jira Project Roles using SQL 7 | 8 | Jira Project Roles are a flexible way to associate users and groups with projects. They allow project administrators to manage project role membership. Project roles can be used in permission schemes, issue security levels, notification schemes, and comment visibility. 9 | 10 | ## Table Usage Guide 11 | 12 | The `jira_project_role` table provides insights into project roles within Jira. As a project administrator, explore role-specific details through this table, including permissions and associated users and groups. Utilize it to manage project role membership, and to set up permission schemes, issue security levels, and notification schemes. 13 | 14 | **Important Notes** 15 | - Project roles are somewhat similar to groups, the main difference being that group membership is global whereas project role membership is project-specific. Additionally, group membership can only be altered by Jira administrators, whereas project role membership can be altered by project administrators. 16 | 17 | ## Examples 18 | 19 | ### Basic info 20 | Explore the different roles within your Jira project. This can help in understanding the distribution of responsibilities and in managing team members more effectively. 21 | 22 | ```sql+postgres 23 | select 24 | id, 25 | name, 26 | description 27 | from 28 | jira_project_role; 29 | ``` 30 | 31 | ```sql+sqlite 32 | select 33 | id, 34 | name, 35 | description 36 | from 37 | jira_project_role; 38 | ``` 39 | 40 | ### Get actor details 41 | Explore the details of different actors within your Jira project roles. This query is useful for gaining insights into the identities and account IDs of actors, aiding in project management and team coordination. 42 | 43 | ```sql+postgres 44 | select 45 | id, 46 | name, 47 | jsonb_pretty(actor_account_ids) as actor_account_ids, 48 | jsonb_pretty(actor_names) as actor_names 49 | from 50 | jira_project_role; 51 | ``` 52 | 53 | ```sql+sqlite 54 | select 55 | id, 56 | name, 57 | actor_account_ids, 58 | actor_names 59 | from 60 | jira_project_role; 61 | ``` 62 | 63 | ### Get actor details joined with user table 64 | This query is used to identify the details of actors from the user table in a Jira project. It can be useful in understanding the roles and statuses of different actors in the project, which can aid project management and team coordination. 65 | 66 | ```sql+postgres 67 | select 68 | id, 69 | name, 70 | actor_id, 71 | actor.display_name, 72 | actor.account_type, 73 | actor.active as actor_status 74 | from 75 | jira_project_role as role, 76 | jsonb_array_elements_text(actor_account_ids) as actor_id, 77 | jira_user as actor 78 | where 79 | actor_id = actor.account_id; 80 | ``` 81 | 82 | ```sql+sqlite 83 | select 84 | role.id, 85 | role.name, 86 | actor_id.value as actor_id, 87 | actor.display_name, 88 | actor.account_type, 89 | actor.active as actor_status 90 | from 91 | jira_project_role as role, 92 | json_each(role.actor_account_ids) as actor_id, 93 | jira_user as actor 94 | where 95 | actor_id.value = actor.account_id; 96 | ``` -------------------------------------------------------------------------------- /docs/tables/jira_sprint.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Steampipe Table: jira_sprint - Query Jira Sprints using SQL" 3 | description: "Allows users to query Jira Sprints, providing insights into the progress, status, and details of each sprint within a Jira project." 4 | --- 5 | 6 | # Table: jira_sprint - Query Jira Sprints using SQL 7 | 8 | Jira Sprints are a key component of the agile project management offered by Jira. They represent a set timeframe during which specific work has to be completed and made ready for review. Sprints are used in the Scrum framework and they help teams to better manage and organize their work, by breaking down larger projects into manageable, time-boxed periods. 9 | 10 | ## Table Usage Guide 11 | 12 | The `jira_sprint` table provides insights into the sprints within a Jira project. As a project manager or a team lead, you can explore details about each sprint, including its progress, status, and associated issues. Use this table to track the progress of ongoing sprints, plan for future sprints, and review the performance of past sprints. 13 | 14 | ## Examples 15 | 16 | ### Basic info 17 | Explore which projects are currently active, along with their respective timelines. This can assist in understanding the progress and schedules of different projects. 18 | 19 | ```sql+postgres 20 | select 21 | id, 22 | name, 23 | board_id, 24 | state, 25 | start_date, 26 | end_date, 27 | complete_date 28 | from 29 | jira_sprint; 30 | ``` 31 | 32 | ```sql+sqlite 33 | select 34 | id, 35 | name, 36 | board_id, 37 | state, 38 | start_date, 39 | end_date, 40 | complete_date 41 | from 42 | jira_sprint; 43 | ``` 44 | 45 | ### List sprints due in the next week 46 | Explore which sprints are due in the coming week. This can help in planning and prioritizing tasks accordingly. 47 | 48 | ```sql+postgres 49 | select 50 | id, 51 | name, 52 | board_id, 53 | state, 54 | start_date, 55 | end_date 56 | from 57 | jira_sprint 58 | where 59 | end_date > current_date 60 | and end_date <= (current_date + interval '7' day); 61 | ``` 62 | 63 | ```sql+sqlite 64 | select 65 | id, 66 | name, 67 | board_id, 68 | state, 69 | start_date, 70 | end_date 71 | from 72 | jira_sprint 73 | where 74 | end_date > date('now') 75 | and end_date <= date('now', '+7 day'); 76 | ``` 77 | 78 | ### List active sprints 79 | Explore which sprints are currently active in your Jira project. This query helps in gaining insights into ongoing tasks and managing project timelines effectively. 80 | 81 | ```sql+postgres 82 | select 83 | id, 84 | name, 85 | board_id, 86 | start_date, 87 | end_date 88 | from 89 | jira_sprint 90 | where 91 | state = 'active'; 92 | ``` 93 | 94 | ```sql+sqlite 95 | select 96 | id, 97 | name, 98 | board_id, 99 | start_date, 100 | end_date 101 | from 102 | jira_sprint 103 | where 104 | state = 'active'; 105 | ``` 106 | 107 | ### List issues in a sprints 108 | Discover the segments that require attention by identifying active tasks within a particular project phase, helping to allocate resources more effectively and manage project timelines. 109 | 110 | ```sql+postgres 111 | select 112 | id, 113 | key, 114 | summary, 115 | project_key, 116 | status, 117 | assignee_display_name, 118 | assignee_account_id, 119 | duedate 120 | from 121 | jira_issue 122 | where 123 | sprint_ids @> '2'; 124 | ``` 125 | 126 | ```sql+sqlite 127 | Error: SQLite does not support array contains operator '@>'. 128 | ``` 129 | 130 | ### Count of issues by sprint 131 | This query is useful for understanding the distribution of tasks across different sprints in a project. It provides an overview of workload allocation, helping to identify if certain sprints are overloaded with issues. 132 | ```sql+postgres 133 | select 134 | b.name as board_name, 135 | s.name as sprint_name, 136 | count(sprint_id) as num_issues 137 | from 138 | jira_board as b 139 | join jira_sprint as s on s.board_id = b.id 140 | left join jira_issue as i on true 141 | left join jsonb_array_elements(i.sprint_ids) as sprint_id on sprint_id ::bigint = s.id 142 | group by 143 | board_name, 144 | sprint_name 145 | order by 146 | board_name, 147 | sprint_name; 148 | ``` 149 | 150 | ```sql+sqlite 151 | select 152 | b.name as board_name, 153 | s.name as sprint_name, 154 | count(sprint_id) as num_issues 155 | from 156 | jira_board as b 157 | join jira_sprint as s on s.board_id = b.id 158 | left join jira_issue as i 159 | left join json_each(i.sprint_ids) as sprint_id on sprint_id.value = s.id 160 | group by 161 | board_name, 162 | sprint_name 163 | order by 164 | board_name, 165 | sprint_name; 166 | ``` -------------------------------------------------------------------------------- /docs/tables/jira_user.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Steampipe Table: jira_user - Query Jira Users using SQL" 3 | description: "Allows users to query Jira Users, specifically user account details, providing insights into user activities and account statuses." 4 | --- 5 | 6 | # Table: jira_user - Query Jira Users using SQL 7 | 8 | Jira is a popular project management tool developed by Atlassian. It is primarily used for issue tracking, bug tracking, and project management. Jira allows teams to plan, track, and manage agile software development projects. 9 | 10 | ## Table Usage Guide 11 | 12 | The `jira_user` table provides insights into user accounts within Jira. As a project manager or system administrator, explore user-specific details through this table, including account statuses, email addresses, and associated metadata. Utilize it to uncover information about users, such as their account statuses, their last login details, and their group memberships. 13 | 14 | ## Examples 15 | 16 | ### Basic info 17 | Explore which Jira users are active and understand their respective account types. This can help in managing user roles and access within your Jira environment. 18 | 19 | ```sql+postgres 20 | select 21 | display_name, 22 | account_type as type, 23 | active as status, 24 | account_id 25 | from 26 | jira_user; 27 | ``` 28 | 29 | ```sql+sqlite 30 | select 31 | display_name, 32 | account_type as type, 33 | active as status, 34 | account_id 35 | from 36 | jira_user; 37 | ``` 38 | 39 | ### Get associated names for a particular user 40 | Explore the group affiliations of a specific user to understand their role and access permissions within the system. 41 | 42 | ```sql+postgres 43 | select 44 | display_name, 45 | active as status, 46 | account_id, 47 | jsonb_pretty(group_names) as group_names 48 | from 49 | jira_user 50 | where 51 | display_name = 'Confluence Analytics (System)'; 52 | ``` 53 | 54 | ```sql+sqlite 55 | select 56 | display_name, 57 | active as status, 58 | account_id, 59 | group_names 60 | from 61 | jira_user 62 | where 63 | display_name = 'Confluence Analytics (System)'; 64 | ``` -------------------------------------------------------------------------------- /docs/tables/jira_workflow.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Steampipe Table: jira_workflow - Query Jira Workflows using SQL" 3 | description: "Allows users to query Jira Workflows, providing insights into the steps, transitions, and status categories of each workflow." 4 | --- 5 | 6 | # Table: jira_workflow - Query Jira Workflows using SQL 7 | 8 | Jira Workflows is a feature within Atlassian's Jira software that enables teams to manage and track the lifecycle of tasks and issues. It provides a visual representation of the process an issue goes through from creation to completion, allowing teams to customize and control how their work flows. Jira Workflows helps in determining the steps an issue needs to go through to reach resolution, setting permissions for who can move issues between steps, and automating these transitions. 9 | 10 | ## Table Usage Guide 11 | 12 | The `jira_workflow` table provides a detailed view of Jira Workflows within a Jira software instance. As a project manager or a team lead, leverage this table to gain insights into the steps, transitions, and status categories of each workflow. Utilize it to manage and optimize your team's work process, understand the lifecycle of tasks, and identify bottlenecks in your project's workflow. 13 | 14 | ## Examples 15 | 16 | ### Basic info 17 | Analyze the settings to understand the default workflows in your Jira system, enabling you to better manage your project processes and prioritize tasks. This is particularly useful for project managers who need to assess the elements within their workflows and identify areas for improvement or customization. 18 | 19 | ```sql+postgres 20 | select 21 | name, 22 | entity_id, 23 | description, 24 | is_default 25 | from 26 | jira_workflow; 27 | ``` 28 | 29 | ```sql+sqlite 30 | select 31 | name, 32 | entity_id, 33 | description, 34 | is_default 35 | from 36 | jira_workflow; 37 | ``` 38 | 39 | ### List workflows that are not default 40 | Uncover the details of workflows in Jira that have been customized and are not set as default. This can be beneficial for administrators to understand the unique workflows in their system and make necessary adjustments or improvements. 41 | 42 | ```sql+postgres 43 | select 44 | name, 45 | entity_id, 46 | description, 47 | is_default 48 | from 49 | jira_workflow 50 | where 51 | not is_default; 52 | ``` 53 | 54 | ```sql+sqlite 55 | select 56 | name, 57 | entity_id, 58 | description, 59 | is_default 60 | from 61 | jira_workflow 62 | where 63 | is_default = 0; 64 | ``` 65 | 66 | ### List workflows that are not associated with entity id 67 | Discover workflows that lack an associated entity ID, which could indicate incomplete or misconfigured processes within your Jira workflow system. This can be useful to identify and rectify potential issues, ensuring smoother operations. 68 | 69 | ```sql+postgres 70 | select 71 | name, 72 | entity_id, 73 | description, 74 | is_default 75 | from 76 | jira_workflow 77 | where 78 | entity_id = ''; 79 | ``` 80 | 81 | ```sql+sqlite 82 | select 83 | name, 84 | entity_id, 85 | description, 86 | is_default 87 | from 88 | jira_workflow 89 | where 90 | entity_id = ''; 91 | ``` -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/turbot/steampipe-plugin-jira/v2 2 | 3 | go 1.24 4 | 5 | toolchain go1.24.1 6 | 7 | require ( 8 | github.com/andygrunwald/go-jira v1.16.0 9 | github.com/andygrunwald/go-jira/v2 v2.0.0-20230325080157-2e11dffbdb9a 10 | github.com/turbot/steampipe-plugin-sdk/v5 v5.13.1 11 | ) 12 | 13 | require ( 14 | cloud.google.com/go v0.112.1 // indirect 15 | cloud.google.com/go/compute/metadata v0.3.0 // indirect 16 | cloud.google.com/go/iam v1.1.6 // indirect 17 | cloud.google.com/go/storage v1.38.0 // indirect 18 | github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect 19 | github.com/agext/levenshtein v1.2.3 // indirect 20 | github.com/allegro/bigcache/v3 v3.1.0 // indirect 21 | github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect 22 | github.com/aws/aws-sdk-go v1.44.183 // indirect 23 | github.com/beorn7/perks v1.0.1 // indirect 24 | github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect 25 | github.com/btubbs/datetime v0.1.1 // indirect 26 | github.com/cenkalti/backoff/v4 v4.3.0 // indirect 27 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 28 | github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 // indirect 29 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 30 | github.com/dgraph-io/ristretto v0.2.0 // indirect 31 | github.com/dustin/go-humanize v1.0.1 // indirect 32 | github.com/eko/gocache/lib/v4 v4.1.6 // indirect 33 | github.com/eko/gocache/store/bigcache/v4 v4.2.1 // indirect 34 | github.com/eko/gocache/store/ristretto/v4 v4.2.1 // indirect 35 | github.com/fatih/color v1.17.0 // indirect 36 | github.com/fatih/structs v1.1.0 // indirect 37 | github.com/felixge/httpsnoop v1.0.4 // indirect 38 | github.com/fsnotify/fsnotify v1.7.0 // indirect 39 | github.com/gertd/go-pluralize v0.2.1 // indirect 40 | github.com/ghodss/yaml v1.0.0 // indirect 41 | github.com/go-logr/logr v1.4.1 // indirect 42 | github.com/go-logr/stdr v1.2.2 // indirect 43 | github.com/golang-jwt/jwt/v4 v4.5.2 // indirect 44 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 45 | github.com/golang/mock v1.6.0 // indirect 46 | github.com/golang/protobuf v1.5.4 // indirect 47 | github.com/google/go-cmp v0.6.0 // indirect 48 | github.com/google/go-querystring v1.1.0 // indirect 49 | github.com/google/s2a-go v0.1.7 // indirect 50 | github.com/google/uuid v1.6.0 // indirect 51 | github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect 52 | github.com/googleapis/gax-go/v2 v2.12.3 // indirect 53 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 // indirect 54 | github.com/hashicorp/go-cleanhttp v0.5.2 // indirect 55 | github.com/hashicorp/go-getter v1.7.9 // indirect 56 | github.com/hashicorp/go-hclog v1.6.3 // indirect 57 | github.com/hashicorp/go-plugin v1.6.1 // indirect 58 | github.com/hashicorp/go-safetemp v1.0.0 // indirect 59 | github.com/hashicorp/go-version v1.7.0 // indirect 60 | github.com/hashicorp/hcl/v2 v2.20.1 // indirect 61 | github.com/hashicorp/yamux v0.1.1 // indirect 62 | github.com/iancoleman/strcase v0.3.0 // indirect 63 | github.com/jmespath/go-jmespath v0.4.0 // indirect 64 | github.com/klauspost/compress v1.17.2 // indirect 65 | github.com/mattn/go-colorable v0.1.13 // indirect 66 | github.com/mattn/go-isatty v0.0.20 // indirect 67 | github.com/mattn/go-runewidth v0.0.15 // indirect 68 | github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect 69 | github.com/mitchellh/go-homedir v1.1.0 // indirect 70 | github.com/mitchellh/go-testing-interface v1.14.1 // indirect 71 | github.com/mitchellh/go-wordwrap v1.0.0 // indirect 72 | github.com/mitchellh/mapstructure v1.5.0 // indirect 73 | github.com/oklog/run v1.0.0 // indirect 74 | github.com/olekukonko/tablewriter v0.0.5 // indirect 75 | github.com/pkg/errors v0.9.1 // indirect 76 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 77 | github.com/prometheus/client_golang v1.14.0 // indirect 78 | github.com/prometheus/client_model v0.3.0 // indirect 79 | github.com/prometheus/common v0.37.0 // indirect 80 | github.com/prometheus/procfs v0.8.0 // indirect 81 | github.com/rivo/uniseg v0.2.0 // indirect 82 | github.com/sethvargo/go-retry v0.2.4 // indirect 83 | github.com/stevenle/topsort v0.2.0 // indirect 84 | github.com/stretchr/testify v1.10.0 // indirect 85 | github.com/tkrajina/go-reflector v0.5.6 // indirect 86 | github.com/trivago/tgo v1.0.7 // indirect 87 | github.com/turbot/go-kit v1.1.0 // indirect 88 | github.com/ulikunitz/xz v0.5.15 // indirect 89 | github.com/zclconf/go-cty v1.14.4 // indirect 90 | go.opencensus.io v0.24.0 // indirect 91 | go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect 92 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect 93 | go.opentelemetry.io/otel v1.26.0 // indirect 94 | go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.26.0 // indirect 95 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 // indirect 96 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 // indirect 97 | go.opentelemetry.io/otel/metric v1.26.0 // indirect 98 | go.opentelemetry.io/otel/sdk v1.26.0 // indirect 99 | go.opentelemetry.io/otel/sdk/metric v1.26.0 // indirect 100 | go.opentelemetry.io/otel/trace v1.26.0 // indirect 101 | go.opentelemetry.io/proto/otlp v1.2.0 // indirect 102 | golang.org/x/crypto v0.36.0 // indirect 103 | golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect 104 | golang.org/x/mod v0.19.0 // indirect 105 | golang.org/x/net v0.38.0 // indirect 106 | golang.org/x/oauth2 v0.27.0 // indirect 107 | golang.org/x/sync v0.12.0 // indirect 108 | golang.org/x/sys v0.31.0 // indirect 109 | golang.org/x/text v0.23.0 // indirect 110 | golang.org/x/time v0.5.0 // indirect 111 | golang.org/x/tools v0.23.0 // indirect 112 | google.golang.org/api v0.171.0 // indirect 113 | google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect 114 | google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 // indirect 115 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 // indirect 116 | google.golang.org/grpc v1.66.0 // indirect 117 | google.golang.org/protobuf v1.34.2 // indirect 118 | gopkg.in/yaml.v2 v2.4.0 // indirect 119 | ) 120 | -------------------------------------------------------------------------------- /jira/common_columns.go: -------------------------------------------------------------------------------- 1 | package jira 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/turbot/steampipe-plugin-sdk/v5/grpc/proto" 7 | "github.com/turbot/steampipe-plugin-sdk/v5/memoize" 8 | "github.com/turbot/steampipe-plugin-sdk/v5/plugin" 9 | "github.com/turbot/steampipe-plugin-sdk/v5/plugin/transform" 10 | ) 11 | 12 | // Login ID would be same per connection. 13 | // API key and API Token is specific to a User 14 | // A user can have multiple organizations, workspaces, boards, etc... 15 | func commonColumns(c []*plugin.Column) []*plugin.Column { 16 | return append([]*plugin.Column{ 17 | { 18 | Name: "login_id", 19 | Type: proto.ColumnType_STRING, 20 | Description: "The unique identifier of the user login.", 21 | Hydrate: getLoginId, 22 | Transform: transform.FromValue(), 23 | }, 24 | }, c...) 25 | } 26 | 27 | // if the caching is required other than per connection, build a cache key for the call and use it in Memoize. 28 | var getLoginIdMemoized = plugin.HydrateFunc(getLoginIdUncached).Memoize(memoize.WithCacheKeyFunction(getLoginIdCacheKey)) 29 | 30 | // declare a wrapper hydrate function to call the memoized function 31 | // - this is required when a memoized function is used for a column definition 32 | func getLoginId(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { 33 | return getLoginIdMemoized(ctx, d, h) 34 | } 35 | 36 | // Build a cache key for the call to getLoginIdCacheKey. 37 | func getLoginIdCacheKey(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { 38 | key := "getLoginId" 39 | return key, nil 40 | } 41 | 42 | func getLoginIdUncached(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { 43 | // Create client 44 | client, err := connect(ctx, d) 45 | if err != nil { 46 | plugin.Logger(ctx).Error("getLoginIdUncached", "connection_error", err) 47 | return nil, err 48 | } 49 | 50 | currentSession, _, err := client.User.GetSelf() 51 | if err != nil { 52 | plugin.Logger(ctx).Error("getLoginIdUncached", "api_error", err) 53 | return nil, err 54 | } 55 | 56 | return currentSession.AccountID, nil 57 | } 58 | -------------------------------------------------------------------------------- /jira/connection_config.go: -------------------------------------------------------------------------------- 1 | package jira 2 | 3 | import ( 4 | "github.com/turbot/steampipe-plugin-sdk/v5/plugin" 5 | ) 6 | 7 | type jiraConfig struct { 8 | BaseUrl *string `hcl:"base_url"` 9 | Username *string `hcl:"username"` 10 | Token *string `hcl:"token"` 11 | PersonalAccessToken *string `hcl:"personal_access_token"` 12 | } 13 | 14 | func ConfigInstance() interface{} { 15 | return &jiraConfig{} 16 | } 17 | 18 | // GetConfig :: retrieve and cast connection config from query data 19 | func GetConfig(connection *plugin.Connection) jiraConfig { 20 | if connection == nil || connection.Config == nil { 21 | return jiraConfig{} 22 | } 23 | config, _ := connection.Config.(jiraConfig) 24 | return config 25 | } 26 | -------------------------------------------------------------------------------- /jira/errors.go: -------------------------------------------------------------------------------- 1 | package jira 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | 7 | "github.com/turbot/steampipe-plugin-sdk/v5/plugin" 8 | ) 9 | 10 | func isNotFoundError(err error) bool { 11 | return strings.Contains(err.Error(), "404") 12 | } 13 | 14 | func isBadRequestError(err error) bool { 15 | return strings.Contains(err.Error(), "400") 16 | } 17 | 18 | func shouldRetryError(retryErrors []string) plugin.ErrorPredicateWithContext { 19 | return func(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData, err error) bool { 20 | 21 | if strings.Contains(err.Error(), "429") { 22 | plugin.Logger(ctx).Debug("jira_errors.shouldRetryError", "rate_limit_error", err) 23 | return true 24 | } 25 | return false 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /jira/plugin.go: -------------------------------------------------------------------------------- 1 | package jira 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/turbot/steampipe-plugin-sdk/v5/plugin" 7 | "github.com/turbot/steampipe-plugin-sdk/v5/plugin/transform" 8 | ) 9 | 10 | const pluginName = "steampipe-plugin-jira" 11 | 12 | // Plugin creates this (jira) plugin 13 | func Plugin(ctx context.Context) *plugin.Plugin { 14 | p := &plugin.Plugin{ 15 | Name: pluginName, 16 | DefaultTransform: transform.FromCamel(), 17 | DefaultRetryConfig: &plugin.RetryConfig{ShouldRetryErrorFunc: shouldRetryError([]string{"429"})}, 18 | ConnectionConfigSchema: &plugin.ConnectionConfigSchema{ 19 | NewInstance: ConfigInstance, 20 | }, 21 | ConnectionKeyColumns: []plugin.ConnectionKeyColumn{ 22 | { 23 | Name: "login_id", 24 | Hydrate: getLoginId, 25 | }, 26 | }, 27 | TableMap: map[string]*plugin.Table{ 28 | "jira_advanced_setting": tableAdvancedSetting(ctx), 29 | "jira_backlog_issue": tableBacklogIssue(ctx), 30 | "jira_board": tableBoard(ctx), 31 | "jira_component": tableComponent(ctx), 32 | "jira_dashboard": tableDashboard(ctx), 33 | "jira_epic": tableEpic(ctx), 34 | "jira_global_setting": tableGlobalSetting(ctx), 35 | "jira_group": tableGroup(ctx), 36 | "jira_issue": tableIssue(ctx), 37 | "jira_issue_comment": tableIssueComment(ctx), 38 | "jira_issue_type": tableIssueType(ctx), 39 | "jira_issue_worklog": tableIssueWorklog(ctx), 40 | "jira_priority": tablePriority(ctx), 41 | "jira_project": tableProject(ctx), 42 | "jira_project_role": tableProjectRole(ctx), 43 | "jira_sprint": tableSprint(ctx), 44 | "jira_user": tableUser(ctx), 45 | "jira_workflow": tableWorkflow(ctx), 46 | }, 47 | } 48 | 49 | return p 50 | } 51 | -------------------------------------------------------------------------------- /jira/table_jira_advanced_setting.go: -------------------------------------------------------------------------------- 1 | package jira 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | 8 | "github.com/turbot/steampipe-plugin-sdk/v5/grpc/proto" 9 | "github.com/turbot/steampipe-plugin-sdk/v5/plugin/transform" 10 | 11 | "github.com/turbot/steampipe-plugin-sdk/v5/plugin" 12 | ) 13 | 14 | //// TABLE DEFINITION 15 | 16 | func tableAdvancedSetting(_ context.Context) *plugin.Table { 17 | return &plugin.Table{ 18 | Name: "jira_advanced_setting", 19 | Description: "The application properties that are accessible on the Advanced Settings page.", 20 | Get: &plugin.GetConfig{ 21 | KeyColumns: plugin.SingleColumn("id"), 22 | Hydrate: getAdvancedSettingProperty, 23 | }, 24 | List: &plugin.ListConfig{ 25 | Hydrate: listAdvancedSettings, 26 | }, 27 | Columns: commonColumns([]*plugin.Column{ 28 | // top fields 29 | { 30 | Name: "id", 31 | Description: "The ID of the application property.", 32 | Type: proto.ColumnType_STRING, 33 | Transform: transform.FromGo(), 34 | }, 35 | { 36 | Name: "name", 37 | Description: "The name of the application property.", 38 | Type: proto.ColumnType_STRING, 39 | }, 40 | { 41 | Name: "description", 42 | Description: "The description of the application property.", 43 | Type: proto.ColumnType_STRING, 44 | Transform: transform.FromField("Description"), 45 | }, 46 | 47 | // other important fields 48 | { 49 | Name: "key", 50 | Description: "The key of the application property.", 51 | Type: proto.ColumnType_STRING, 52 | }, 53 | { 54 | Name: "type", 55 | Description: "The data type of the application property.", 56 | Type: proto.ColumnType_STRING, 57 | }, 58 | { 59 | Name: "value", 60 | Description: "The new value.", 61 | Type: proto.ColumnType_STRING, 62 | }, 63 | 64 | // JSON fields 65 | { 66 | Name: "allowed_values", 67 | Description: "The allowed values, if applicable.", 68 | Type: proto.ColumnType_JSON, 69 | }, 70 | 71 | // Standard columns 72 | { 73 | Name: "title", 74 | Description: ColumnDescriptionTitle, 75 | Type: proto.ColumnType_STRING, 76 | Transform: transform.FromField("Name"), 77 | }, 78 | }), 79 | } 80 | } 81 | 82 | //// LIST FUNCTION 83 | 84 | func listAdvancedSettings(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { 85 | client, err := connect(ctx, d) 86 | if err != nil { 87 | plugin.Logger(ctx).Error("jira_advanced_setting.listAdvancedSettings", "connection_error", err) 88 | return nil, err 89 | } 90 | 91 | req, err := client.NewRequest("GET", "/rest/api/3/application-properties/advanced-settings", nil) 92 | if err != nil { 93 | plugin.Logger(ctx).Error("jira_advanced_setting.listAdvancedSettings", "get_request_error", err) 94 | return nil, err 95 | } 96 | 97 | listAdvancedSettings := new([]AdvancedApplicationProperty) 98 | res, err := client.Do(req, listAdvancedSettings) 99 | body, _ := io.ReadAll(res.Body) 100 | plugin.Logger(ctx).Debug("jira_advanced_setting.listAdvancedSettings", "res_body", string(body)) 101 | 102 | if err != nil { 103 | if isNotFoundError(err) { 104 | return nil, nil 105 | } 106 | plugin.Logger(ctx).Error("jira_advanced_setting.listAdvancedSettings", "api_error", err) 107 | return nil, err 108 | } 109 | 110 | for _, listAdvancedSettings := range *listAdvancedSettings { 111 | d.StreamListItem(ctx, listAdvancedSettings) 112 | } 113 | return nil, err 114 | } 115 | 116 | //// HYDRATE FUNCTIONS 117 | 118 | func getAdvancedSettingProperty(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { 119 | ID := d.EqualsQuals["id"].GetStringValue() 120 | 121 | client, err := connect(ctx, d) 122 | if err != nil { 123 | plugin.Logger(ctx).Error("jira_advanced_setting.getAdvancedSettingProperty", "connection_error", err) 124 | return nil, err 125 | } 126 | 127 | apiEndpoint := fmt.Sprintf("/rest/api/3/application-properties?key=%s", ID) 128 | 129 | req, err := client.NewRequest("GET", apiEndpoint, nil) 130 | if err != nil { 131 | plugin.Logger(ctx).Error("jira_advanced_setting.getAdvancedSettingProperty", "get_request_error", err) 132 | return nil, err 133 | } 134 | 135 | result := new(AdvancedApplicationProperty) 136 | 137 | res, err := client.Do(req, result) 138 | body, _ := io.ReadAll(res.Body) 139 | plugin.Logger(ctx).Debug("jira_advanced_setting.getAdvancedSettingProperty", "res_body", string(body)) 140 | 141 | if err != nil { 142 | if isBadRequestError(err) || isNotFoundError(err) { 143 | return nil, nil 144 | } 145 | plugin.Logger(ctx).Error("jira_advanced_setting.getAdvancedSettingProperty", "api_error", err) 146 | return nil, err 147 | } 148 | 149 | return result, nil 150 | } 151 | 152 | type AdvancedApplicationProperty struct { 153 | ID string `json:"id"` 154 | Key string `json:"key"` 155 | Value string `json:"value"` 156 | Name string `json:"name"` 157 | Description string `json:"desc"` 158 | Type string `json:"type"` 159 | AllowedValues []string `json:"allowedValues"` 160 | } 161 | -------------------------------------------------------------------------------- /jira/table_jira_backlog_issue.go: -------------------------------------------------------------------------------- 1 | package jira 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | 8 | "github.com/andygrunwald/go-jira" 9 | "github.com/turbot/steampipe-plugin-sdk/v5/grpc/proto" 10 | "github.com/turbot/steampipe-plugin-sdk/v5/plugin/transform" 11 | 12 | "github.com/turbot/steampipe-plugin-sdk/v5/plugin" 13 | ) 14 | 15 | //// TABLE DEFINITION 16 | 17 | func tableBacklogIssue(_ context.Context) *plugin.Table { 18 | return &plugin.Table{ 19 | Name: "jira_backlog_issue", 20 | Description: "The backlog contains incomplete issues that are not assigned to any future or active sprint.", 21 | List: &plugin.ListConfig{ 22 | ParentHydrate: listBoards, 23 | Hydrate: listBacklogIssues, 24 | }, 25 | Columns: commonColumns([]*plugin.Column{ 26 | // top fields 27 | { 28 | Name: "id", 29 | Description: "The ID of the issue.", 30 | Type: proto.ColumnType_STRING, 31 | Transform: transform.FromGo(), 32 | }, 33 | { 34 | Name: "key", 35 | Description: "The key of the issue.", 36 | Type: proto.ColumnType_STRING, 37 | }, 38 | { 39 | Name: "self", 40 | Description: "The URL of the issue details.", 41 | Type: proto.ColumnType_STRING, 42 | }, 43 | { 44 | Name: "board_name", 45 | Description: "The name of the board the issue belongs to.", 46 | Type: proto.ColumnType_STRING, 47 | Transform: transform.FromField("BoardName"), 48 | }, 49 | { 50 | Name: "board_id", 51 | Description: "The ID of the board the issue belongs to.", 52 | Type: proto.ColumnType_INT, 53 | Transform: transform.FromField("BoardId"), 54 | }, 55 | { 56 | Name: "project_key", 57 | Description: "A friendly key that identifies the project.", 58 | Type: proto.ColumnType_STRING, 59 | Transform: transform.FromField("Fields.Project.Key"), 60 | }, 61 | { 62 | Name: "project_id", 63 | Description: "A friendly key that identifies the project.", 64 | Type: proto.ColumnType_STRING, 65 | Transform: transform.FromField("Fields.Project.ID"), 66 | }, 67 | { 68 | Name: "status", 69 | Description: "The status of the issue. Eg: To Do, In Progress, Done.", 70 | Type: proto.ColumnType_STRING, 71 | Transform: transform.FromField("Fields.Status.Name"), 72 | }, 73 | 74 | // other important fields 75 | { 76 | Name: "assignee_account_id", 77 | Description: "Account Id the user/application that the issue is assigned to work.", 78 | Type: proto.ColumnType_STRING, 79 | Transform: transform.FromField("Fields.Assignee.AccountID"), 80 | }, 81 | { 82 | Name: "assignee_display_name", 83 | Description: "Display name the user/application that the issue is assigned to work.", 84 | Type: proto.ColumnType_STRING, 85 | Transform: transform.FromField("Fields.Assignee.DisplayName"), 86 | }, 87 | { 88 | Name: "created", 89 | Description: "Time when the issue was created.", 90 | Type: proto.ColumnType_TIMESTAMP, 91 | Transform: transform.FromField("Fields.Created").Transform(convertJiraTime), 92 | }, 93 | { 94 | Name: "creator_account_id", 95 | Description: "Account Id of the user/application that created the issue.", 96 | Type: proto.ColumnType_STRING, 97 | Transform: transform.FromField("Fields.Creator.AccountID"), 98 | }, 99 | { 100 | Name: "creator_display_name", 101 | Description: "Display name of the user/application that created the issue.", 102 | Type: proto.ColumnType_STRING, 103 | Transform: transform.FromField("Fields.Creator.DisplayName"), 104 | }, 105 | { 106 | Name: "description", 107 | Description: "Description of the issue.", 108 | Type: proto.ColumnType_STRING, 109 | Transform: transform.FromField("Fields.Description"), 110 | }, 111 | { 112 | Name: "due_date", 113 | Description: "Time by which the issue is expected to be completed.", 114 | Type: proto.ColumnType_TIMESTAMP, 115 | Transform: transform.FromField("Fields.Duedate").NullIfZero().Transform(convertJiraDate), 116 | }, 117 | { 118 | Name: "epic_key", 119 | Description: "The key of the epic to which issue belongs.", 120 | Type: proto.ColumnType_STRING, 121 | Transform: transform.FromP(extractBacklogIssueRequiredField, "epic"), 122 | }, 123 | { 124 | Name: "priority", 125 | Description: "Priority assigned to the issue.", 126 | Type: proto.ColumnType_STRING, 127 | Transform: transform.FromField("Fields.Priority.Name"), 128 | }, 129 | { 130 | Name: "project_name", 131 | Description: "Name of the project to that issue belongs.", 132 | Type: proto.ColumnType_STRING, 133 | Transform: transform.FromField("Fields.Project.Name"), 134 | }, 135 | { 136 | Name: "reporter_account_id", 137 | Description: "Account Id of the user/application issue is reported.", 138 | Type: proto.ColumnType_STRING, 139 | Transform: transform.FromField("Fields.Reporter.AccountID"), 140 | }, 141 | { 142 | Name: "reporter_display_name", 143 | Description: "Display name of the user/application issue is reported.", 144 | Type: proto.ColumnType_STRING, 145 | Transform: transform.FromField("Fields.Reporter.DisplayName"), 146 | }, 147 | { 148 | Name: "summary", 149 | Description: "Details of the user/application that created the issue.", 150 | Type: proto.ColumnType_STRING, 151 | Transform: transform.FromField("Fields.Summary"), 152 | }, 153 | { 154 | Name: "type", 155 | Description: "The name of the issue type.", 156 | Type: proto.ColumnType_STRING, 157 | Transform: transform.FromField("Fields.Type.Name"), 158 | }, 159 | { 160 | Name: "updated", 161 | Description: "Time when the issue was last updated.", 162 | Type: proto.ColumnType_TIMESTAMP, 163 | Transform: transform.FromField("Fields.Updated").Transform(convertJiraTime), 164 | }, 165 | 166 | // JSON fields 167 | { 168 | Name: "components", 169 | Description: "List of components associated with the issue.", 170 | Type: proto.ColumnType_JSON, 171 | Transform: transform.FromField("Fields.Components").Transform(extractComponentIds), 172 | }, 173 | { 174 | Name: "fields", 175 | Description: "Json object containing important subfields of the issue.", 176 | Type: proto.ColumnType_JSON, 177 | }, 178 | { 179 | Name: "labels", 180 | Description: "A list of labels applied to the issue.", 181 | Type: proto.ColumnType_JSON, 182 | Transform: transform.FromField("Fields.Labels"), 183 | }, 184 | 185 | // Standard columns 186 | { 187 | Name: "tags", 188 | Type: proto.ColumnType_JSON, 189 | Description: "A map of label names associated with this issue, in Steampipe standard format.", 190 | Transform: transform.From(getBacklogIssueTags), 191 | }, 192 | { 193 | Name: "title", 194 | Description: ColumnDescriptionTitle, 195 | Type: proto.ColumnType_STRING, 196 | Transform: transform.FromField("Key"), 197 | }, 198 | }), 199 | } 200 | } 201 | 202 | //// LIST FUNCTION 203 | 204 | func listBacklogIssues(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { 205 | client, err := connect(ctx, d) 206 | if err != nil { 207 | plugin.Logger(ctx).Error("jira_backlog_issue.listBacklogIssues", "connection_error", err) 208 | return nil, err 209 | } 210 | 211 | board := h.Item.(jira.Board) 212 | 213 | last := 0 214 | 215 | // If the requested number of items is less than the paging max limit 216 | // set the limit to that instead 217 | queryLimit := d.QueryContext.Limit 218 | var maxResults int = 1000 219 | if d.QueryContext.Limit != nil { 220 | if *queryLimit < 1000 { 221 | maxResults = int(*queryLimit) 222 | } 223 | } 224 | 225 | var epicKey string 226 | for { 227 | apiEndpoint := fmt.Sprintf( 228 | "/rest/agile/1.0/board/%d/backlog?startAt=%d&maxResults=%d&expand=names", 229 | board.ID, 230 | last, 231 | maxResults, 232 | ) 233 | 234 | req, err := client.NewRequest("GET", apiEndpoint, nil) 235 | if err != nil { 236 | plugin.Logger(ctx).Error("jira_backlog_issue.listBacklogIssues", "get_request_error", err) 237 | return nil, err 238 | } 239 | 240 | listIssuesResult := new(ListIssuesResult) 241 | res, err := client.Do(req, listIssuesResult) 242 | body, _ := io.ReadAll(res.Body) 243 | plugin.Logger(ctx).Debug("jira_backlog_issue.listBacklogIssues", "res_body", string(body)) 244 | 245 | if err != nil { 246 | if isNotFoundError(err) || isBadRequestError(err) { 247 | return nil, nil 248 | } 249 | plugin.Logger(ctx).Error("jira_backlog_issue.listBacklogIssues", "api_error", err) 250 | return nil, err 251 | } 252 | 253 | epicKey = getFieldKey(ctx, d, listIssuesResult.Names, "Epic Link") 254 | 255 | keys := map[string]string{ 256 | "epic": epicKey, 257 | } 258 | 259 | for _, issue := range listIssuesResult.Issues { 260 | d.StreamListItem(ctx, BacklogIssueInfo{issue, board.ID, board.Name, keys}) 261 | // Context may get cancelled due to manual cancellation or if the limit has been reached 262 | if d.RowsRemaining(ctx) == 0 { 263 | return nil, nil 264 | } 265 | } 266 | 267 | last = listIssuesResult.StartAt + len(listIssuesResult.Issues) 268 | if last >= listIssuesResult.Total { 269 | return nil, nil 270 | } 271 | } 272 | } 273 | 274 | //// TRANSFORM FUNCTION 275 | 276 | func getBacklogIssueTags(_ context.Context, d *transform.TransformData) (interface{}, error) { 277 | issue := d.HydrateItem.(BacklogIssueInfo) 278 | 279 | tags := make(map[string]bool) 280 | if issue.Fields != nil && issue.Fields.Labels != nil { 281 | for _, i := range issue.Fields.Labels { 282 | tags[i] = true 283 | } 284 | } 285 | return tags, nil 286 | } 287 | 288 | func extractBacklogIssueRequiredField(_ context.Context, d *transform.TransformData) (interface{}, error) { 289 | issueInfo := d.HydrateItem.(BacklogIssueInfo) 290 | m := issueInfo.Fields.Unknowns 291 | param := d.Param.(string) 292 | return m[issueInfo.Keys[param]], nil 293 | } 294 | 295 | //// Required Structs 296 | 297 | type BacklogIssueInfo struct { 298 | jira.Issue 299 | BoardId int 300 | BoardName string 301 | Keys map[string]string 302 | } 303 | -------------------------------------------------------------------------------- /jira/table_jira_board.go: -------------------------------------------------------------------------------- 1 | package jira 2 | 3 | import ( 4 | "context" 5 | "io" 6 | 7 | "github.com/andygrunwald/go-jira" 8 | "github.com/turbot/steampipe-plugin-sdk/v5/grpc/proto" 9 | "github.com/turbot/steampipe-plugin-sdk/v5/plugin/transform" 10 | 11 | "github.com/turbot/steampipe-plugin-sdk/v5/plugin" 12 | ) 13 | 14 | //// TABLE DEFINITION 15 | 16 | func tableBoard(_ context.Context) *plugin.Table { 17 | return &plugin.Table{ 18 | Name: "jira_board", 19 | Description: "A board displays issues from one or more projects, giving you a flexible way of viewing, managing, and reporting on work in progress.", 20 | Get: &plugin.GetConfig{ 21 | KeyColumns: plugin.SingleColumn("id"), 22 | Hydrate: getBoard, 23 | }, 24 | List: &plugin.ListConfig{ 25 | Hydrate: listBoards, 26 | }, 27 | Columns: commonColumns([]*plugin.Column{ 28 | { 29 | Name: "id", 30 | Description: "The ID of the board.", 31 | Type: proto.ColumnType_INT, 32 | Transform: transform.FromGo(), 33 | }, 34 | { 35 | Name: "name", 36 | Description: "The name of the board.", 37 | Type: proto.ColumnType_STRING, 38 | }, 39 | { 40 | Name: "self", 41 | Description: "The URL of the board details.", 42 | Type: proto.ColumnType_STRING, 43 | }, 44 | { 45 | Name: "type", 46 | Description: "The board type of the board. Valid values are simple, scrum and kanban.", 47 | Type: proto.ColumnType_STRING, 48 | }, 49 | { 50 | Name: "filter_id", 51 | Description: "Filter id of the board.", 52 | Type: proto.ColumnType_INT, 53 | Hydrate: getBoardConfiguration, 54 | Transform: transform.FromField("Filter.ID"), 55 | }, 56 | { 57 | Name: "sub_query", 58 | Description: "JQL subquery used by the given board - (Kanban only).", 59 | Type: proto.ColumnType_STRING, 60 | Hydrate: getBoardConfiguration, 61 | Transform: transform.FromField("SubQuery.Query"), 62 | }, 63 | 64 | // Standard columns 65 | { 66 | Name: "title", 67 | Description: ColumnDescriptionTitle, 68 | Type: proto.ColumnType_STRING, 69 | Transform: transform.FromField("Name"), 70 | }, 71 | }), 72 | } 73 | } 74 | 75 | //// LIST FUNCTION 76 | 77 | func listBoards(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { 78 | client, err := connect(ctx, d) 79 | if err != nil { 80 | plugin.Logger(ctx).Error("jira_board.listBoards", "connection_error", err) 81 | return nil, err 82 | } 83 | 84 | last := 0 85 | // If the requested number of items is less than the paging max limit 86 | // set the limit to that instead 87 | queryLimit := d.QueryContext.Limit 88 | var maxResults int = 1000 89 | if d.QueryContext.Limit != nil { 90 | if *queryLimit < 1000 { 91 | maxResults = int(*queryLimit) 92 | } 93 | } 94 | 95 | for { 96 | opt := jira.SearchOptions{ 97 | MaxResults: maxResults, 98 | StartAt: last, 99 | } 100 | 101 | boardList, res, err := client.Board.GetAllBoardsWithContext(ctx, &jira.BoardListOptions{ 102 | SearchOptions: opt, 103 | }) 104 | body, _ := io.ReadAll(res.Body) 105 | plugin.Logger(ctx).Debug("jira_board.listBoards", "res_body", string(body)) 106 | if err != nil { 107 | plugin.Logger(ctx).Error("jira_board.listBoards", "api_error", err) 108 | return nil, err 109 | } 110 | 111 | for _, board := range boardList.Values { 112 | d.StreamListItem(ctx, board) 113 | // Context may get cancelled due to manual cancellation or if the limit has been reached 114 | if d.RowsRemaining(ctx) == 0 { 115 | return nil, nil 116 | } 117 | } 118 | 119 | last = boardList.StartAt + len(boardList.Values) 120 | if last >= boardList.Total { 121 | return nil, nil 122 | } 123 | } 124 | } 125 | 126 | //// HYDRATE FUNCTIONS 127 | 128 | func getBoard(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { 129 | boardId := d.EqualsQuals["id"].GetInt64Value() 130 | if boardId == 0 { 131 | return nil, nil 132 | } 133 | client, err := connect(ctx, d) 134 | if err != nil { 135 | plugin.Logger(ctx).Error("jira_board.getBoard", "connection_error", err) 136 | return nil, err 137 | } 138 | 139 | board, res, err := client.Board.GetBoard(int(boardId)) 140 | body, _ := io.ReadAll(res.Body) 141 | plugin.Logger(ctx).Debug("jira_board.getBoard", "res_body", string(body)) 142 | if err != nil { 143 | if isNotFoundError(err) { 144 | return nil, nil 145 | } 146 | plugin.Logger(ctx).Error("jira_board.getBoard", "api_error", err) 147 | return nil, err 148 | } 149 | 150 | return *board, err 151 | } 152 | 153 | func getBoardConfiguration(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { 154 | board := h.Item.(jira.Board) 155 | 156 | client, err := connect(ctx, d) 157 | if err != nil { 158 | plugin.Logger(ctx).Error("jira_board.getBoardConfiguration", "connection_error", err) 159 | return nil, err 160 | } 161 | 162 | boardConfiguration, _, err := client.Board.GetBoardConfiguration(board.ID) 163 | if err != nil { 164 | plugin.Logger(ctx).Error("jira_board.getBoardConfiguration", "api_error", err) 165 | return nil, err 166 | } 167 | 168 | return boardConfiguration, err 169 | } 170 | -------------------------------------------------------------------------------- /jira/table_jira_component.go: -------------------------------------------------------------------------------- 1 | package jira 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | 8 | "github.com/andygrunwald/go-jira" 9 | "github.com/turbot/steampipe-plugin-sdk/v5/grpc/proto" 10 | "github.com/turbot/steampipe-plugin-sdk/v5/plugin/transform" 11 | 12 | "github.com/turbot/steampipe-plugin-sdk/v5/plugin" 13 | ) 14 | 15 | //// TABLE DEFINITION 16 | 17 | func tableComponent(_ context.Context) *plugin.Table { 18 | return &plugin.Table{ 19 | Name: "jira_component", 20 | Description: "This resource represents project components. Use it to get, create, update, and delete project components. Also get components for project and get a count of issues by component.", 21 | Get: &plugin.GetConfig{ 22 | KeyColumns: plugin.SingleColumn("id"), 23 | Hydrate: getComponent, 24 | }, 25 | List: &plugin.ListConfig{ 26 | ParentHydrate: listProjects, 27 | Hydrate: listComponents, 28 | }, 29 | Columns: commonColumns([]*plugin.Column{ 30 | // top fields 31 | { 32 | Name: "id", 33 | Description: "The unique identifier for the component.", 34 | Type: proto.ColumnType_STRING, 35 | }, 36 | { 37 | Name: "name", 38 | Description: "The name for the component.", 39 | Type: proto.ColumnType_STRING, 40 | }, 41 | { 42 | Name: "description", 43 | Description: "The description for the component.", 44 | Type: proto.ColumnType_STRING, 45 | }, 46 | { 47 | Name: "self", 48 | Description: "The URL for this count of the issues contained in the component.", 49 | Type: proto.ColumnType_STRING, 50 | }, 51 | { 52 | Name: "project", 53 | Description: "The key of the project to which the component is assigned.", 54 | Type: proto.ColumnType_STRING, 55 | }, 56 | 57 | // other important fields 58 | { 59 | Name: "assignee_account_id", 60 | Description: "The account id of the user associated with assigneeType, if any.", 61 | Type: proto.ColumnType_STRING, 62 | Transform: transform.FromField("Assignee.AccountID"), 63 | }, 64 | { 65 | Name: "assignee_display_name", 66 | Description: "The display name of the user associated with assigneeType, if any.", 67 | Type: proto.ColumnType_STRING, 68 | Transform: transform.FromField("Assignee.DisplayName"), 69 | }, 70 | { 71 | Name: "assignee_type", 72 | Description: "The nominal user type used to determine the assignee for issues created with this component.", 73 | Type: proto.ColumnType_STRING, 74 | }, 75 | { 76 | Name: "is_assignee_type_valid", 77 | Description: "Whether a user is associated with assigneeType.", 78 | Type: proto.ColumnType_BOOL, 79 | }, 80 | { 81 | Name: "issue_count", 82 | Description: "The count of issues for the component.", 83 | Type: proto.ColumnType_INT, 84 | }, 85 | { 86 | Name: "lead_account_id", 87 | Description: "The account id for the component's lead user.", 88 | Type: proto.ColumnType_STRING, 89 | Transform: transform.FromField("Lead.AccountID"), 90 | }, 91 | { 92 | Name: "lead_display_name", 93 | Description: "The display name for the component's lead user.", 94 | Type: proto.ColumnType_STRING, 95 | Transform: transform.FromField("Lead.DisplayName"), 96 | }, 97 | { 98 | Name: "project_id", 99 | Description: "The ID of the project the component belongs to.", 100 | Type: proto.ColumnType_INT, 101 | }, 102 | { 103 | Name: "real_assignee_account_id", 104 | Description: "The account id of the user assigned to issues created with this component, when assigneeType does not identify a valid assignee.", 105 | Type: proto.ColumnType_STRING, 106 | Transform: transform.FromField("RealAssignee.AccountID"), 107 | }, 108 | { 109 | Name: "real_assignee_display_name", 110 | Description: "The display name of the user assigned to issues created with this component, when assigneeType does not identify a valid assignee.", 111 | Type: proto.ColumnType_STRING, 112 | Transform: transform.FromField("RealAssignee.DisplayName"), 113 | }, 114 | { 115 | Name: "real_assignee_type", 116 | Description: "The type of the assignee that is assigned to issues created with this component, when an assignee cannot be set from the assigneeType.", 117 | Type: proto.ColumnType_STRING, 118 | }, 119 | 120 | // Standard columns 121 | { 122 | Name: "title", 123 | Description: ColumnDescriptionTitle, 124 | Type: proto.ColumnType_STRING, 125 | Transform: transform.FromField("Name"), 126 | }, 127 | }), 128 | } 129 | } 130 | 131 | //// LIST FUNCTION 132 | 133 | func listComponents(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { 134 | project := h.Item.(Project) 135 | 136 | client, err := connect(ctx, d) 137 | if err != nil { 138 | plugin.Logger(ctx).Error("jira_component.listComponents", "connection_error", err) 139 | return nil, err 140 | } 141 | 142 | last := 0 143 | // If the requested number of items is less than the paging max limit 144 | // set the limit to that instead 145 | queryLimit := d.QueryContext.Limit 146 | var maxResults int = 1000 147 | if d.QueryContext.Limit != nil { 148 | if *queryLimit < 1000 { 149 | maxResults = int(*queryLimit) 150 | } 151 | } 152 | 153 | for { 154 | apiEndpoint := fmt.Sprintf("/rest/api/3/project/%s/component?startAt=%d&maxResults=%d", project.ID, last, maxResults) 155 | 156 | req, err := client.NewRequest("GET", apiEndpoint, nil) 157 | if err != nil { 158 | plugin.Logger(ctx).Error("jira_component.listComponents", "get_request_error", err) 159 | return nil, err 160 | } 161 | 162 | listResult := new(ListComponentResult) 163 | res, err := client.Do(req, listResult) 164 | body, _ := io.ReadAll(res.Body) 165 | plugin.Logger(ctx).Debug("jira_component.listComponents", "res_body", string(body)) 166 | 167 | if err != nil { 168 | if isNotFoundError(err) { 169 | return nil, nil 170 | } 171 | plugin.Logger(ctx).Error("jira_component.listComponents", "api_error", err) 172 | return nil, err 173 | } 174 | 175 | for _, component := range listResult.Values { 176 | d.StreamListItem(ctx, component) 177 | 178 | // Context may get cancelled due to manual cancellation or if the limit has been reached 179 | if d.RowsRemaining(ctx) == 0 { 180 | return nil, nil 181 | } 182 | } 183 | 184 | last = listResult.StartAt + len(listResult.Values) 185 | if listResult.IsLast { 186 | return nil, nil 187 | } 188 | } 189 | } 190 | 191 | //// HYDRATE FUNCTIONS 192 | 193 | func getComponent(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { 194 | componentId := d.EqualsQuals["id"].GetStringValue() 195 | 196 | client, err := connect(ctx, d) 197 | if err != nil { 198 | plugin.Logger(ctx).Error("jira_component.getComponent", "connection_error", err) 199 | return nil, err 200 | } 201 | 202 | apiEndpoint := fmt.Sprintf("/rest/api/3/component/%s", componentId) 203 | 204 | req, err := client.NewRequest("GET", apiEndpoint, nil) 205 | if err != nil { 206 | plugin.Logger(ctx).Error("jira_component.getComponent", "get_request_error", err) 207 | return nil, err 208 | } 209 | 210 | result := new(Component) 211 | 212 | res, err := client.Do(req, result) 213 | body, _ := io.ReadAll(res.Body) 214 | plugin.Logger(ctx).Debug("jira_component.getComponent", "res_body", string(body)) 215 | 216 | if err != nil { 217 | if isNotFoundError(err) { 218 | return nil, nil 219 | } 220 | plugin.Logger(ctx).Error("jira_component.getComponent", "api_error", err) 221 | return nil, err 222 | } 223 | 224 | return result, nil 225 | } 226 | 227 | type ListComponentResult struct { 228 | Self string `json:"self"` 229 | NextPage string `json:"nextPage"` 230 | MaxResults int `json:"maxResults"` 231 | StartAt int `json:"startAt"` 232 | Total int `json:"total"` 233 | IsLast bool `json:"isLast"` 234 | Values []Component `json:"values"` 235 | } 236 | 237 | type Component struct { 238 | IssueCount int64 `json:"issueCount"` 239 | RealAssignee jira.User `json:"realAssignee"` 240 | IsAssigneeTypeValid bool `json:"isAssigneeTypeValid"` 241 | RealAssigneeType string `json:"realAssigneeType"` 242 | Description string `json:"description"` 243 | Project string `json:"project"` 244 | Self string `json:"self"` 245 | AssigneeType string `json:"assigneeType"` 246 | Lead jira.User `json:"lead"` 247 | Assignee jira.User `json:"assignee"` 248 | ProjectId int64 `json:"projectId"` 249 | Name string `json:"name"` 250 | Id string `json:"id"` 251 | } 252 | -------------------------------------------------------------------------------- /jira/table_jira_dashboard.go: -------------------------------------------------------------------------------- 1 | package jira 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | 8 | "github.com/andygrunwald/go-jira" 9 | "github.com/turbot/steampipe-plugin-sdk/v5/grpc/proto" 10 | "github.com/turbot/steampipe-plugin-sdk/v5/plugin/transform" 11 | 12 | "github.com/turbot/steampipe-plugin-sdk/v5/plugin" 13 | ) 14 | 15 | //// TABLE DEFINITION 16 | 17 | func tableDashboard(_ context.Context) *plugin.Table { 18 | return &plugin.Table{ 19 | Name: "jira_dashboard", 20 | Description: "Your dashboard is the main display you see when you log in to Jira.", 21 | Get: &plugin.GetConfig{ 22 | KeyColumns: plugin.SingleColumn("id"), 23 | Hydrate: getDashboard, 24 | }, 25 | List: &plugin.ListConfig{ 26 | Hydrate: listDashboards, 27 | }, 28 | Columns: commonColumns([]*plugin.Column{ 29 | { 30 | Name: "id", 31 | Description: "The ID of the dashboard.", 32 | Type: proto.ColumnType_STRING, 33 | }, 34 | { 35 | Name: "name", 36 | Description: "The name of the dashboard.", 37 | Type: proto.ColumnType_STRING, 38 | }, 39 | { 40 | Name: "self", 41 | Description: "The URL of the dashboard details.", 42 | Type: proto.ColumnType_STRING, 43 | }, 44 | { 45 | Name: "is_favourite", 46 | Description: "Indicates if the dashboard is selected as a favorite by the user.", 47 | Type: proto.ColumnType_BOOL, 48 | }, 49 | { 50 | Name: "owner_account_id", 51 | Description: "The user info of owner of the dashboard.", 52 | Type: proto.ColumnType_STRING, 53 | Transform: transform.FromField("Owner.AccountID"), 54 | }, 55 | { 56 | Name: "owner_display_name", 57 | Description: "The user info of owner of the dashboard.", 58 | Type: proto.ColumnType_STRING, 59 | Transform: transform.FromField("Owner.DisplayName"), 60 | }, 61 | { 62 | Name: "popularity", 63 | Description: "The number of users who have this dashboard as a favorite.", 64 | Type: proto.ColumnType_INT, 65 | }, 66 | { 67 | Name: "rank", 68 | Description: "The rank of this dashboard.", 69 | Type: proto.ColumnType_INT, 70 | }, 71 | { 72 | Name: "view", 73 | Description: "The URL of the dashboard.", 74 | Type: proto.ColumnType_STRING, 75 | }, 76 | { 77 | Name: "edit_permissions", 78 | Description: "The details of any edit share permissions for the dashboard.", 79 | Type: proto.ColumnType_JSON, 80 | }, 81 | { 82 | Name: "share_permissions", 83 | Description: "The details of any view share permissions for the dashboard.", 84 | Type: proto.ColumnType_JSON, 85 | }, 86 | 87 | // Standard columns 88 | { 89 | Name: "title", 90 | Description: ColumnDescriptionTitle, 91 | Type: proto.ColumnType_STRING, 92 | Transform: transform.FromField("Name"), 93 | }, 94 | }), 95 | } 96 | } 97 | 98 | //// LIST FUNCTION 99 | 100 | func listDashboards(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { 101 | client, err := connect(ctx, d) 102 | if err != nil { 103 | plugin.Logger(ctx).Error("jira_dashboard.listDashboards", "connection_error", err) 104 | return nil, err 105 | } 106 | 107 | last := 0 108 | // If the requested number of items is less than the paging max limit 109 | // set the limit to that instead 110 | queryLimit := d.QueryContext.Limit 111 | var maxResults int = 1000 112 | if d.QueryContext.Limit != nil { 113 | if *queryLimit < 1000 { 114 | maxResults = int(*queryLimit) 115 | } 116 | } 117 | 118 | for { 119 | apiEndpoint := fmt.Sprintf( 120 | "/rest/api/3/dashboard?startAt=%d&maxResults=%d", 121 | last, 122 | maxResults, 123 | ) 124 | 125 | req, err := client.NewRequest("GET", apiEndpoint, nil) 126 | if err != nil { 127 | plugin.Logger(ctx).Error("jira_dashboard.listDashboards", "get_request_error", err) 128 | return nil, err 129 | } 130 | 131 | listResult := new(ListResult) 132 | res, err := client.Do(req, listResult) 133 | body, _ := io.ReadAll(res.Body) 134 | plugin.Logger(ctx).Debug("jira_dashboard.listDashboards", "res_body", string(body)) 135 | 136 | if err != nil { 137 | plugin.Logger(ctx).Error("jira_dashboard.listDashboards", "api_error", err) 138 | return nil, err 139 | } 140 | 141 | for _, dashboard := range listResult.Values { 142 | d.StreamListItem(ctx, dashboard) 143 | // Context may get cancelled due to manual cancellation or if the limit has been reached 144 | if d.RowsRemaining(ctx) == 0 { 145 | return nil, nil 146 | } 147 | } 148 | 149 | last = listResult.StartAt + len(listResult.Values) 150 | if last >= listResult.Total { 151 | return nil, nil 152 | } 153 | } 154 | } 155 | 156 | //// HDRATE FUNCTIONS 157 | 158 | func getDashboard(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { 159 | dashboardId := d.EqualsQuals["id"].GetStringValue() 160 | 161 | if dashboardId == "" { 162 | return nil, nil 163 | } 164 | 165 | dashboard := new(Dashboard) 166 | client, err := connect(ctx, d) 167 | if err != nil { 168 | plugin.Logger(ctx).Error("jira_dashboard.getDashboard", "connection_error", err) 169 | return nil, err 170 | } 171 | apiEndpoint := fmt.Sprintf("/rest/api/3/dashboard/%s", dashboardId) 172 | req, err := client.NewRequest("GET", apiEndpoint, nil) 173 | if err != nil { 174 | plugin.Logger(ctx).Error("jira_dashboard.getDashboard", "get_request_error", err) 175 | return nil, err 176 | } 177 | 178 | res, err := client.Do(req, dashboard) 179 | body, _ := io.ReadAll(res.Body) 180 | plugin.Logger(ctx).Debug("jira_dashboard.getDashboard", "res_body", string(body)) 181 | 182 | if err != nil { 183 | if isNotFoundError(err) { 184 | return nil, nil 185 | } 186 | plugin.Logger(ctx).Error("jira_dashboard.getDashboard", "api_error", err) 187 | return nil, err 188 | } 189 | 190 | return dashboard, nil 191 | } 192 | 193 | //// Custom Structs 194 | 195 | type ListResult struct { 196 | MaxResults int `json:"maxResults"` 197 | StartAt int `json:"startAt"` 198 | Total int `json:"total"` 199 | IsLast bool `json:"isLast"` 200 | Values []Dashboard `json:"dashboards"` 201 | } 202 | 203 | type Dashboard struct { 204 | Id string `json:"id"` 205 | IsFavourite bool `json:"isFavourite"` 206 | Name string `json:"name"` 207 | Owner jira.User `json:"owner"` 208 | Popularity int64 `json:"popularity"` 209 | Rank int32 `json:"rank"` 210 | Self string `json:"self"` 211 | SharePermissions []SharePermission `json:"sharePermissions"` 212 | EditPermissions []SharePermission `json:"editPermissions"` 213 | View string `json:"view"` 214 | } 215 | 216 | type SharePermission struct { 217 | Id int64 `json:"id"` 218 | Type string `json:"type"` 219 | } 220 | 221 | type Owner struct { 222 | Id int64 `json:"id"` 223 | Type string `json:"type"` 224 | } 225 | -------------------------------------------------------------------------------- /jira/table_jira_epic.go: -------------------------------------------------------------------------------- 1 | package jira 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | 8 | "github.com/turbot/steampipe-plugin-sdk/v5/grpc/proto" 9 | "github.com/turbot/steampipe-plugin-sdk/v5/plugin/transform" 10 | 11 | "github.com/turbot/steampipe-plugin-sdk/v5/plugin" 12 | ) 13 | 14 | //// TABLE DEFINITION 15 | 16 | func tableEpic(_ context.Context) *plugin.Table { 17 | return &plugin.Table{ 18 | Name: "jira_epic", 19 | Description: "An epic is essentially a large user story that can be broken down into a number of smaller stories. An epic can span more than one project.", 20 | Get: &plugin.GetConfig{ 21 | KeyColumns: plugin.AnyColumn([]string{"id", "key"}), 22 | Hydrate: getEpic, 23 | }, 24 | List: &plugin.ListConfig{ 25 | Hydrate: listEpics, 26 | }, 27 | Columns: commonColumns([]*plugin.Column{ 28 | { 29 | Name: "id", 30 | Description: "The id of the epic.", 31 | Type: proto.ColumnType_INT, 32 | }, 33 | { 34 | Name: "name", 35 | Description: "The name of the epic.", 36 | Type: proto.ColumnType_STRING, 37 | }, 38 | { 39 | Name: "key", 40 | Description: "The key of the epic.", 41 | Type: proto.ColumnType_STRING, 42 | }, 43 | { 44 | Name: "done", 45 | Description: "Indicates the status of the epic.", 46 | Type: proto.ColumnType_BOOL, 47 | }, 48 | { 49 | Name: "self", 50 | Description: "The URL of the epic details.", 51 | Type: proto.ColumnType_STRING, 52 | }, 53 | { 54 | Name: "summary", 55 | Description: "Description of the epic.", 56 | Type: proto.ColumnType_STRING, 57 | }, 58 | { 59 | Name: "color", 60 | Description: "Label colour details for the epic.", 61 | Type: proto.ColumnType_JSON, 62 | }, 63 | 64 | // Standard columns 65 | { 66 | Name: "title", 67 | Description: ColumnDescriptionTitle, 68 | Type: proto.ColumnType_STRING, 69 | Transform: transform.FromField("Name"), 70 | }, 71 | }), 72 | } 73 | } 74 | 75 | //// LIST FUNCTION 76 | 77 | func listEpics(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { 78 | client, err := connect(ctx, d) 79 | if err != nil { 80 | plugin.Logger(ctx).Error("jira_epic.listEpics", "connection_error", err) 81 | return nil, err 82 | } 83 | 84 | last := 0 85 | // If the requested number of items is less than the paging max limit 86 | // set the limit to that instead 87 | queryLimit := d.QueryContext.Limit 88 | var maxResults int = 100 89 | if d.QueryContext.Limit != nil { 90 | if *queryLimit < 100 { 91 | maxResults = int(*queryLimit) 92 | } 93 | } 94 | for { 95 | apiEndpoint := fmt.Sprintf( 96 | "/rest/agile/1.0/epic/search?startAt=%d&maxResults=%d", 97 | last, 98 | maxResults, 99 | ) 100 | 101 | req, err := client.NewRequest("GET", apiEndpoint, nil) 102 | if err != nil { 103 | plugin.Logger(ctx).Error("jira_epic.listEpics", "get_request_error", err) 104 | return nil, err 105 | } 106 | 107 | listResult := new(ListEpicResult) 108 | res, err := client.Do(req, listResult) 109 | body, _ := io.ReadAll(res.Body) 110 | plugin.Logger(ctx).Debug("jira_epic.listEpics", "res_body", string(body)) 111 | 112 | if err != nil { 113 | plugin.Logger(ctx).Error("jira_epic.listEpics", "api_error", err) 114 | return nil, err 115 | } 116 | 117 | for _, epic := range listResult.Values { 118 | d.StreamListItem(ctx, epic) 119 | // Context may get cancelled due to manual cancellation or if the limit has been reached 120 | if d.RowsRemaining(ctx) == 0 { 121 | return nil, nil 122 | } 123 | } 124 | 125 | last = listResult.StartAt + len(listResult.Values) 126 | if listResult.IsLast { 127 | return nil, nil 128 | } 129 | } 130 | } 131 | 132 | //// HYDRATE FUNCTION 133 | 134 | func getEpic(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { 135 | logger := plugin.Logger(ctx) 136 | logger.Debug("getEpic") 137 | 138 | epicId := d.EqualsQuals["id"].GetInt64Value() 139 | epicKey := d.EqualsQuals["key"].GetStringValue() 140 | var apiEndpoint string 141 | 142 | if epicKey != "" { 143 | apiEndpoint = fmt.Sprintf("/rest/agile/1.0/epic/%s", epicKey) 144 | } else { 145 | apiEndpoint = fmt.Sprintf("/rest/agile/1.0/epic/%d", epicId) 146 | } 147 | 148 | client, err := connect(ctx, d) 149 | if err != nil { 150 | plugin.Logger(ctx).Error("jira_epic.getEpic", "connection_error", err) 151 | return nil, err 152 | } 153 | 154 | req, err := client.NewRequest("GET", apiEndpoint, nil) 155 | if err != nil { 156 | plugin.Logger(ctx).Error("jira_epic.getEpic", "get_request_error", err) 157 | return nil, err 158 | } 159 | 160 | epic := new(Epic) 161 | res, err := client.Do(req, epic) 162 | body, _ := io.ReadAll(res.Body) 163 | plugin.Logger(ctx).Debug("jira_epic.getEpic", "res_body", string(body)) 164 | 165 | if err != nil { 166 | if isNotFoundError(err) || isBadRequestError(err) { 167 | return nil, nil 168 | } 169 | plugin.Logger(ctx).Error("jira_epic.getEpic", "api_error", err) 170 | return nil, err 171 | } 172 | 173 | return epic, err 174 | } 175 | 176 | //// Custom Structs 177 | 178 | type ListEpicResult struct { 179 | MaxResults int `json:"maxResults"` 180 | StartAt int `json:"startAt"` 181 | Total int `json:"total"` 182 | IsLast bool `json:"isLast"` 183 | Values []Epic `json:"values"` 184 | } 185 | 186 | type Epic struct { 187 | Color Color `json:"color"` 188 | Done bool `json:"done"` 189 | Id int64 `json:"id"` 190 | Key string `json:"key"` 191 | Name string `json:"name"` 192 | Self string `json:"self"` 193 | Summary string `json:"summary"` 194 | } 195 | 196 | type Color struct { 197 | Key string `json:"key"` 198 | } 199 | -------------------------------------------------------------------------------- /jira/table_jira_global_setting.go: -------------------------------------------------------------------------------- 1 | package jira 2 | 3 | import ( 4 | "context" 5 | "io" 6 | 7 | "github.com/turbot/steampipe-plugin-sdk/v5/grpc/proto" 8 | 9 | "github.com/turbot/steampipe-plugin-sdk/v5/plugin" 10 | ) 11 | 12 | //// TABLE DEFINITION 13 | 14 | func tableGlobalSetting(_ context.Context) *plugin.Table { 15 | return &plugin.Table{ 16 | Name: "jira_global_setting", 17 | Description: "Returns the global settings in Jira.", 18 | List: &plugin.ListConfig{ 19 | Hydrate: listGlobalSettings, 20 | }, 21 | Columns: commonColumns([]*plugin.Column{ 22 | // top fields 23 | { 24 | Name: "voting_enabled", 25 | Description: "Whether the ability for users to vote on issues is enabled.", 26 | Type: proto.ColumnType_BOOL, 27 | }, 28 | { 29 | Name: "watching_enabled", 30 | Description: "Whether the ability for users to watch issues is enabled.", 31 | Type: proto.ColumnType_BOOL, 32 | }, 33 | { 34 | Name: "unassigned_issues_allowed", 35 | Description: "Whether the ability to create unassigned issues is enabled.", 36 | Type: proto.ColumnType_BOOL, 37 | }, 38 | { 39 | Name: "sub_tasks_enabled", 40 | Description: "Whether the ability to create subtasks for issues is enabled.", 41 | Type: proto.ColumnType_BOOL, 42 | }, 43 | { 44 | Name: "issue_linking_enabled", 45 | Description: "Whether the ability to link issues is enabled.", 46 | Type: proto.ColumnType_BOOL, 47 | }, 48 | { 49 | Name: "time_tracking_enabled", 50 | Description: "Whether the ability to track time is enabled.", 51 | Type: proto.ColumnType_BOOL, 52 | }, 53 | { 54 | Name: "attachments_enabled", 55 | Description: "Whether the ability to add attachments to issues is enabled.", 56 | Type: proto.ColumnType_BOOL, 57 | }, 58 | 59 | // JSON fields 60 | { 61 | Name: "time_tracking_configuration", 62 | Description: "The configuration of time tracking.", 63 | Type: proto.ColumnType_JSON, 64 | }, 65 | }), 66 | } 67 | } 68 | 69 | //// LIST FUNCTION 70 | 71 | func listGlobalSettings(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { 72 | client, err := connect(ctx, d) 73 | if err != nil { 74 | plugin.Logger(ctx).Error("jira_global_setting.listGlobalSettings", "connection_error", err) 75 | return nil, err 76 | } 77 | 78 | req, err := client.NewRequest("GET", "/rest/api/3/configuration", nil) 79 | if err != nil { 80 | plugin.Logger(ctx).Error("jira_global_setting.listGlobalSettings", "get_request_error", err) 81 | return nil, err 82 | } 83 | 84 | listGlobalSettings := new(GlobalSetting) 85 | res, err := client.Do(req, listGlobalSettings) 86 | body, _ := io.ReadAll(res.Body) 87 | plugin.Logger(ctx).Debug("jira_global_setting.listGlobalSettings", "res_body", string(body)) 88 | 89 | if err != nil { 90 | if isNotFoundError(err) { 91 | return nil, nil 92 | } 93 | plugin.Logger(ctx).Error("jira_global_setting.listGlobalSettings", "api_error", err) 94 | return nil, err 95 | } 96 | 97 | d.StreamListItem(ctx, listGlobalSettings) 98 | return nil, err 99 | } 100 | 101 | type GlobalSetting struct { 102 | VotingEnabled bool `json:"votingEnabled"` 103 | WatchingEnabled bool `json:"watchingEnabled"` 104 | UnassignedIssuesAllowed bool `json:"unassignedIssuesAllowed"` 105 | SubTasksEnabled bool `json:"subTasksEnabled"` 106 | IssueLinkingEnabled bool `json:"issueLinkingEnabled"` 107 | TimeTrackingEnabled bool `json:"timeTrackingEnabled"` 108 | AttachmentsEnabled bool `json:"attachmentsEnabled"` 109 | TimeTrackingConfiguration TimeTrackingConfig `json:"timeTrackingConfiguration"` 110 | } 111 | 112 | type TimeTrackingConfig struct { 113 | WorkingHoursPerDay float64 `json:"workingHoursPerDay"` 114 | WorkingDaysPerWeek float64 `json:"workingDaysPerWeek"` 115 | TimeFormat string `json:"timeFormat"` 116 | DefaultUnit string `json:"defaultUnit"` 117 | } 118 | -------------------------------------------------------------------------------- /jira/table_jira_group.go: -------------------------------------------------------------------------------- 1 | package jira 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | 8 | "github.com/andygrunwald/go-jira" 9 | "github.com/turbot/steampipe-plugin-sdk/v5/grpc/proto" 10 | "github.com/turbot/steampipe-plugin-sdk/v5/plugin/transform" 11 | 12 | "github.com/turbot/steampipe-plugin-sdk/v5/plugin" 13 | ) 14 | 15 | //// TABLE DEFINITION 16 | 17 | func tableGroup(_ context.Context) *plugin.Table { 18 | return &plugin.Table{ 19 | Name: "jira_group", 20 | Description: "Group is a collection of users. Administrators create groups so that the administrator can assign permissions to a number of people at once.", 21 | Get: &plugin.GetConfig{ 22 | KeyColumns: plugin.SingleColumn("id"), 23 | Hydrate: getGroup, 24 | }, 25 | List: &plugin.ListConfig{ 26 | Hydrate: listGroups, 27 | }, 28 | Columns: commonColumns([]*plugin.Column{ 29 | { 30 | Name: "name", 31 | Description: "The name of the group.", 32 | Type: proto.ColumnType_STRING, 33 | }, 34 | { 35 | Name: "id", 36 | Description: "The ID of the group, if available, which uniquely identifies the group across all Atlassian products. For example, 952d12c3-5b5b-4d04-bb32-44d383afc4b2.", 37 | Type: proto.ColumnType_STRING, 38 | Transform: transform.FromField("GroupId"), 39 | }, 40 | { 41 | Name: "member_ids", 42 | Description: "List of account ids of users associated with the group.", 43 | Type: proto.ColumnType_JSON, 44 | Hydrate: getGroupMembers, 45 | Transform: transform.From(memberIds), 46 | }, 47 | { 48 | Name: "member_names", 49 | Description: "List of names of users associated with the group.", 50 | Type: proto.ColumnType_JSON, 51 | Hydrate: getGroupMembers, 52 | Transform: transform.From(memberNames), 53 | }, 54 | 55 | // Standard columns 56 | { 57 | Name: "title", 58 | Description: ColumnDescriptionTitle, 59 | Type: proto.ColumnType_STRING, 60 | Transform: transform.FromField("Name"), 61 | }, 62 | }), 63 | } 64 | } 65 | 66 | //// LIST FUNCTION 67 | 68 | func listGroups(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { 69 | client, err := connect(ctx, d) 70 | if err != nil { 71 | plugin.Logger(ctx).Error("jira_group.listGroups", "connection_error", err) 72 | return nil, err 73 | } 74 | 75 | last := 0 76 | 77 | // If the requested number of items is less than the paging max limit 78 | // set the limit to that instead 79 | queryLimit := d.QueryContext.Limit 80 | var maxResults int = 1000 81 | if d.QueryContext.Limit != nil { 82 | if *queryLimit < 1000 { 83 | maxResults = int(*queryLimit) 84 | } 85 | } 86 | for { 87 | apiEndpoint := fmt.Sprintf( 88 | "/rest/api/3/group/bulk?startAt=%d&maxResults=%d", 89 | last, 90 | maxResults, 91 | ) 92 | 93 | req, err := client.NewRequest("GET", apiEndpoint, nil) 94 | if err != nil { 95 | plugin.Logger(ctx).Error("jira_group.listGroups", "get_request_error", err) 96 | return nil, err 97 | } 98 | 99 | listGroupResult := new(ListGroupResult) 100 | res, err := client.Do(req, listGroupResult) 101 | body, _ := io.ReadAll(res.Body) 102 | plugin.Logger(ctx).Debug("jira_group.listGroups", "res_body", string(body)) 103 | 104 | if err != nil { 105 | plugin.Logger(ctx).Error("jira_group.listGroups", "api_error", err) 106 | return nil, err 107 | } 108 | 109 | for _, group := range listGroupResult.Groups { 110 | d.StreamListItem(ctx, group) 111 | // Context may get cancelled due to manual cancellation or if the limit has been reached 112 | if d.RowsRemaining(ctx) == 0 { 113 | return nil, nil 114 | } 115 | } 116 | 117 | last = listGroupResult.StartAt + len(listGroupResult.Groups) 118 | if last >= listGroupResult.Total { 119 | return nil, nil 120 | } 121 | } 122 | } 123 | 124 | //// HDRATE FUNCTIONS 125 | 126 | func getGroup(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { 127 | groupId := d.EqualsQuals["id"].GetStringValue() 128 | 129 | if groupId == "" { 130 | return nil, nil 131 | } 132 | 133 | listGroupResult := new(ListGroupResult) 134 | client, err := connect(ctx, d) 135 | if err != nil { 136 | plugin.Logger(ctx).Error("jira_group.getGroup", "connection_error", err) 137 | return nil, err 138 | } 139 | 140 | apiEndpoint := fmt.Sprintf("/rest/api/3/group/bulk?groupId=%s", groupId) 141 | req, err := client.NewRequest("GET", apiEndpoint, nil) 142 | if err != nil { 143 | plugin.Logger(ctx).Error("jira_group.getGroup", "get_request_error", err) 144 | return nil, err 145 | } 146 | 147 | res, err := client.Do(req, listGroupResult) 148 | body, _ := io.ReadAll(res.Body) 149 | plugin.Logger(ctx).Debug("jira_group.getGroup", "res_body", string(body)) 150 | 151 | if err != nil { 152 | plugin.Logger(ctx).Error("jira_group.getGroup", "api_error", err) 153 | return nil, err 154 | } 155 | 156 | if len(listGroupResult.Groups) > 0 { 157 | return listGroupResult.Groups[0], nil 158 | } 159 | return nil, nil 160 | } 161 | 162 | func getGroupMembers(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { 163 | group := h.Item.(Group) 164 | 165 | client, err := connect(ctx, d) 166 | if err != nil { 167 | plugin.Logger(ctx).Error("jira_group.getGroupMembers", "connection_error", err) 168 | return nil, err 169 | } 170 | 171 | groupMembers := []jira.GroupMember{} 172 | 173 | last := 0 174 | // If the requested number of items is less than the paging max limit 175 | // set the limit to that instead 176 | queryLimit := d.QueryContext.Limit 177 | var maxResults int = 1000 178 | if d.QueryContext.Limit != nil { 179 | if *queryLimit < 1000 { 180 | maxResults = int(*queryLimit) 181 | } 182 | } 183 | for { 184 | opts := &jira.GroupSearchOptions{ 185 | MaxResults: maxResults, 186 | StartAt: last, 187 | IncludeInactiveUsers: true, 188 | } 189 | 190 | chunk, resp, err := client.Group.GetWithOptions(group.Name, opts) 191 | if err != nil { 192 | if isNotFoundError(err) { 193 | return groupMembers, nil 194 | } 195 | plugin.Logger(ctx).Error("jira_group.getGroupMembers", "api_error", err) 196 | return nil, err 197 | } 198 | 199 | if groupMembers == nil { 200 | groupMembers = make([]jira.GroupMember, 0, resp.Total) 201 | } 202 | 203 | groupMembers = append(groupMembers, chunk...) 204 | 205 | last = resp.StartAt + len(chunk) 206 | if last >= resp.Total { 207 | return groupMembers, nil 208 | } 209 | } 210 | } 211 | 212 | //// TRANSFORM FUNCTION 213 | 214 | func memberIds(_ context.Context, d *transform.TransformData) (interface{}, error) { 215 | var memberIds []string 216 | for _, member := range d.HydrateItem.([]jira.GroupMember) { 217 | memberIds = append(memberIds, member.AccountID) 218 | } 219 | return memberIds, nil 220 | } 221 | 222 | func memberNames(_ context.Context, d *transform.TransformData) (interface{}, error) { 223 | var memberNames []string 224 | for _, member := range d.HydrateItem.([]jira.GroupMember) { 225 | memberNames = append(memberNames, member.DisplayName) 226 | } 227 | return memberNames, nil 228 | } 229 | 230 | //// Required Structs 231 | 232 | type ListGroupResult struct { 233 | MaxResults int `json:"maxResults"` 234 | StartAt int `json:"startAt"` 235 | Total int `json:"total"` 236 | IsLast bool `json:"isLast"` 237 | Groups []Group `json:"values"` 238 | } 239 | 240 | type Group struct { 241 | Name string `json:"name"` 242 | GroupId string `json:"groupId"` 243 | } 244 | -------------------------------------------------------------------------------- /jira/table_jira_issue_comment.go: -------------------------------------------------------------------------------- 1 | package jira 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strings" 7 | 8 | "github.com/andygrunwald/go-jira" 9 | "github.com/turbot/steampipe-plugin-sdk/v5/grpc/proto" 10 | "github.com/turbot/steampipe-plugin-sdk/v5/plugin/transform" 11 | 12 | "github.com/turbot/steampipe-plugin-sdk/v5/plugin" 13 | ) 14 | 15 | //// TABLE DEFINITION 16 | 17 | func tableIssueComment(_ context.Context) *plugin.Table { 18 | return &plugin.Table{ 19 | Name: "jira_issue_comment", 20 | Description: "Comments that provided in issue.", 21 | Get: &plugin.GetConfig{ 22 | KeyColumns: plugin.AllColumns([]string{"issue_id", "id"}), 23 | Hydrate: getIssueComment, 24 | }, 25 | List: &plugin.ListConfig{ 26 | Hydrate: listIssueComments, 27 | KeyColumns: plugin.KeyColumnSlice{ 28 | {Name: "issue_id", Require: plugin.Required}, 29 | }, 30 | }, 31 | Columns: commonColumns([]*plugin.Column{ 32 | // top fields 33 | { 34 | Name: "id", 35 | Description: "The ID of the issue comment.", 36 | Type: proto.ColumnType_STRING, 37 | Transform: transform.FromGo(), 38 | }, 39 | { 40 | Name: "issue_id", 41 | Description: "The ID of the issue.", 42 | Type: proto.ColumnType_STRING, 43 | }, 44 | { 45 | Name: "self", 46 | Description: "The URL of the issue comment.", 47 | Type: proto.ColumnType_STRING, 48 | }, 49 | { 50 | Name: "body", 51 | Description: "The content of the issue comment.", 52 | Type: proto.ColumnType_STRING, 53 | }, 54 | { 55 | Name: "created", 56 | Description: "Time when the issue comment was created.", 57 | Type: proto.ColumnType_TIMESTAMP, 58 | }, 59 | { 60 | Name: "updated", 61 | Description: "Time when the issue comment was last updated.", 62 | Type: proto.ColumnType_TIMESTAMP, 63 | }, 64 | { 65 | Name: "jsd_public", 66 | Description: "JsdPublic set to false does not hide comments in Service Desk projects.", 67 | Type: proto.ColumnType_BOOL, 68 | }, 69 | 70 | // JSON fields 71 | { 72 | Name: "author", 73 | Description: "The user information who added the issue comment.", 74 | Type: proto.ColumnType_JSON, 75 | }, 76 | { 77 | Name: "update_author", 78 | Description: "The user information who updated the issue comment.", 79 | Type: proto.ColumnType_JSON, 80 | }, 81 | 82 | // Standard columns 83 | { 84 | Name: "title", 85 | Description: ColumnDescriptionTitle, 86 | Type: proto.ColumnType_STRING, 87 | Transform: transform.FromField("ID"), 88 | }, 89 | }), 90 | } 91 | } 92 | 93 | type CommentResult struct { 94 | Comments []Comment `json:"comments" structs:"comments"` 95 | StartAt int `json:"startAt" structs:"startAt"` 96 | MaxResults int `json:"maxResults" structs:"maxResults"` 97 | Total int `json:"total" structs:"total"` 98 | } 99 | 100 | type Comment struct { 101 | ID string 102 | Self string 103 | Author jira.User 104 | Body string 105 | UpdateAuthor jira.User 106 | Updated string 107 | Created string 108 | JsdPublic bool 109 | } 110 | 111 | type commentWithIssueDetails struct { 112 | Comment 113 | IssueId string 114 | } 115 | 116 | //// LIST FUNCTION 117 | 118 | func listIssueComments(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { 119 | issueId := d.EqualsQualString("issue_id") 120 | 121 | // Minize the API call for given issue ID. 122 | if issueId == "" { 123 | return nil, nil 124 | } 125 | 126 | client, err := connect(ctx, d) 127 | if err != nil { 128 | plugin.Logger(ctx).Error("jira_issue_comment.listIssueComments", "connection_error", err) 129 | return nil, err 130 | } 131 | 132 | last := 0 133 | 134 | // If the requested number of items is less than the paging max limit 135 | // set the limit to that instead 136 | queryLimit := d.QueryContext.Limit 137 | var limit int = 5000 138 | if d.QueryContext.Limit != nil { 139 | if *queryLimit < 5000 { 140 | limit = int(*queryLimit) 141 | } 142 | } 143 | 144 | for { 145 | apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/comment?startAt=%d&maxResults=%d", issueId, last, limit) 146 | 147 | req, err := client.NewRequest("GET", apiEndpoint, nil) 148 | if err != nil { 149 | plugin.Logger(ctx).Error("jira_issue_comment.listIssueComments", "get_request_error", err) 150 | return nil, err 151 | } 152 | 153 | comments := new(CommentResult) 154 | _, err = client.Do(req, comments) 155 | if err != nil { 156 | if strings.Contains(err.Error(), "404") { // Handle not found error code 157 | return nil, nil 158 | } 159 | plugin.Logger(ctx).Error("jira_issue_comment.listIssueComments", "api_error", err) 160 | return nil, err 161 | } 162 | 163 | for _, c := range comments.Comments { 164 | d.StreamListItem(ctx, commentWithIssueDetails{c, issueId}) 165 | 166 | // Context may get cancelled due to manual cancellation or if the limit has been reached 167 | if d.RowsRemaining(ctx) == 0 { 168 | return nil, nil 169 | } 170 | } 171 | 172 | last = comments.StartAt + len(comments.Comments) 173 | if last >= comments.Total { 174 | return nil, nil 175 | } 176 | } 177 | } 178 | 179 | //// HYDRATE FUNCTION 180 | 181 | func getIssueComment(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { 182 | 183 | issueId := d.EqualsQualString("issue_id") 184 | id := d.EqualsQualString("id") 185 | 186 | if issueId == "" || id == "" { 187 | return nil, nil 188 | } 189 | 190 | client, err := connect(ctx, d) 191 | if err != nil { 192 | plugin.Logger(ctx).Error("jira_issue_comment.getIssueComment", "connection_error", err) 193 | return nil, err 194 | } 195 | 196 | apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/comment/%s", issueId, id) 197 | 198 | req, err := client.NewRequest("GET", apiEndpoint, nil) 199 | if err != nil { 200 | plugin.Logger(ctx).Error("jira_issue_comment.getIssueComment", "get_request_error", err) 201 | return nil, err 202 | } 203 | 204 | res := new(Comment) 205 | _, err = client.Do(req, res) 206 | if err != nil { 207 | if isNotFoundError(err) { 208 | return nil, nil 209 | } 210 | plugin.Logger(ctx).Error("jira_issue_comment.getIssueComment", "api_error", err) 211 | return nil, err 212 | } 213 | 214 | return commentWithIssueDetails{*res, issueId}, nil 215 | } 216 | -------------------------------------------------------------------------------- /jira/table_jira_issue_type.go: -------------------------------------------------------------------------------- 1 | package jira 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | 8 | "github.com/turbot/steampipe-plugin-sdk/v5/grpc/proto" 9 | "github.com/turbot/steampipe-plugin-sdk/v5/plugin/transform" 10 | 11 | "github.com/turbot/steampipe-plugin-sdk/v5/plugin" 12 | ) 13 | 14 | //// TABLE DEFINITION 15 | 16 | func tableIssueType(_ context.Context) *plugin.Table { 17 | return &plugin.Table{ 18 | Name: "jira_issue_type", 19 | Description: "Issue types distinguish different types of work in unique ways, and help you identify, categorize, and report on your team’s work across your Jira site.", 20 | Get: &plugin.GetConfig{ 21 | KeyColumns: plugin.SingleColumn("id"), 22 | Hydrate: getIssueType, 23 | }, 24 | List: &plugin.ListConfig{ 25 | Hydrate: listIssueTypes, 26 | }, 27 | Columns: commonColumns([]*plugin.Column{ 28 | // top fields 29 | { 30 | Name: "id", 31 | Description: "The ID of the issue type.", 32 | Type: proto.ColumnType_STRING, 33 | Transform: transform.FromGo(), 34 | }, 35 | { 36 | Name: "name", 37 | Description: "The name of the issue type.", 38 | Type: proto.ColumnType_STRING, 39 | }, 40 | { 41 | Name: "self", 42 | Description: "The URL of the issue type details.", 43 | Type: proto.ColumnType_STRING, 44 | }, 45 | { 46 | Name: "description", 47 | Description: "The description of the issue type.", 48 | Type: proto.ColumnType_STRING, 49 | }, 50 | 51 | // other important fields 52 | { 53 | Name: "avatar_id", 54 | Description: "The ID of the issue type's avatar.", 55 | Type: proto.ColumnType_INT, 56 | Transform: transform.FromGo(), 57 | }, 58 | { 59 | Name: "entity_id", 60 | Description: "Unique ID for next-gen projects.", 61 | Type: proto.ColumnType_INT, 62 | Transform: transform.FromGo(), 63 | }, 64 | { 65 | Name: "hierarchy_level", 66 | Description: "Hierarchy level of the issue type.", 67 | Type: proto.ColumnType_INT, 68 | }, 69 | { 70 | Name: "icon_url", 71 | Description: "The URL of the issue type's avatar.", 72 | Type: proto.ColumnType_STRING, 73 | Transform: transform.FromGo(), 74 | }, 75 | { 76 | Name: "subtask", 77 | Description: "Whether this issue type is used to create subtasks.", 78 | Type: proto.ColumnType_BOOL, 79 | }, 80 | 81 | // JSON fields 82 | { 83 | Name: "scope", 84 | Description: "Details of the next-gen projects the issue type is available in.", 85 | Type: proto.ColumnType_JSON, 86 | }, 87 | 88 | // Standard columns 89 | { 90 | Name: "title", 91 | Description: ColumnDescriptionTitle, 92 | Type: proto.ColumnType_STRING, 93 | Transform: transform.FromField("Name"), 94 | }, 95 | }), 96 | } 97 | } 98 | 99 | //// LIST FUNCTION 100 | 101 | func listIssueTypes(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { 102 | client, err := connect(ctx, d) 103 | if err != nil { 104 | plugin.Logger(ctx).Error("jira_issue_type.listIssueTypes", "connection_error", err) 105 | return nil, err 106 | } 107 | 108 | // https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-types/ 109 | // Paging not supported 110 | req, err := client.NewRequest("GET", "/rest/api/3/issuetype", nil) 111 | if err != nil { 112 | plugin.Logger(ctx).Error("jira_issue_type.listIssueTypes", "get_request_error", err) 113 | return nil, err 114 | } 115 | 116 | issuesTypeResult := new([]ListIssuesTypeResult) 117 | res, err := client.Do(req, issuesTypeResult) 118 | body, _ := io.ReadAll(res.Body) 119 | plugin.Logger(ctx).Debug("jira_issue_type.listIssueTypes", "res_body", string(body)) 120 | 121 | if err != nil { 122 | if isNotFoundError(err) || isBadRequestError(err) { 123 | return nil, nil 124 | } 125 | plugin.Logger(ctx).Error("jira_issue_type.listIssueTypes", "api_error", err) 126 | return nil, err 127 | } 128 | 129 | for _, issueType := range *issuesTypeResult { 130 | d.StreamListItem(ctx, issueType) 131 | // Context may get cancelled due to manual cancellation or if the limit has been reached 132 | if d.RowsRemaining(ctx) == 0 { 133 | return nil, nil 134 | } 135 | } 136 | 137 | return nil, err 138 | } 139 | 140 | //// HYDRATE FUNCTION 141 | 142 | func getIssueType(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { 143 | issueTypeID := d.EqualsQuals["id"].GetStringValue() 144 | 145 | if issueTypeID == "" { 146 | return nil, nil 147 | } 148 | issueType := new(ListIssuesTypeResult) 149 | client, err := connect(ctx, d) 150 | if err != nil { 151 | plugin.Logger(ctx).Error("jira_issue_type.getIssueType", "connection_error", err) 152 | return nil, err 153 | } 154 | 155 | apiEndpoint := fmt.Sprintf("/rest/api/3/issuetype/%s", issueTypeID) 156 | req, err := client.NewRequest("GET", apiEndpoint, nil) 157 | if err != nil { 158 | plugin.Logger(ctx).Error("jira_issue_type.getIssueType", "get_request_error", err) 159 | return nil, err 160 | } 161 | 162 | res, err := client.Do(req, issueType) 163 | body, _ := io.ReadAll(res.Body) 164 | plugin.Logger(ctx).Debug("jira_issue_type.getIssueType", "res_body", string(body)) 165 | 166 | if err != nil && isNotFoundError(err) { 167 | plugin.Logger(ctx).Error("jira_issue_type.getIssueType", "api_error", err) 168 | return nil, nil 169 | } 170 | 171 | return issueType, err 172 | } 173 | 174 | //// Required Structs 175 | 176 | type ListIssuesTypeResult struct { 177 | Self string `json:"self"` 178 | ID string `json:"id"` 179 | Description string `json:"description"` 180 | IconURL string `json:"iconUrl"` 181 | Name string `json:"name"` 182 | Subtask bool `json:"subtask"` 183 | AvatarID int64 `json:"avatarId"` 184 | EntityID int64 `json:"entityId"` 185 | HierarchyLevel int32 `json:"hierarchyLevel"` 186 | Scope IssueTypeScope `json:"scope"` 187 | } 188 | 189 | type IssueTypeScope struct { 190 | Type string `json:"type"` 191 | Project IssueTypeProject `json:"project"` 192 | } 193 | 194 | type IssueTypeProject struct { 195 | ID string `json:"id"` 196 | } 197 | -------------------------------------------------------------------------------- /jira/table_jira_issue_worklog.go: -------------------------------------------------------------------------------- 1 | package jira 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/url" 7 | "time" 8 | 9 | "github.com/andygrunwald/go-jira" 10 | "github.com/turbot/steampipe-plugin-sdk/v5/grpc/proto" 11 | "github.com/turbot/steampipe-plugin-sdk/v5/plugin/transform" 12 | 13 | "github.com/turbot/steampipe-plugin-sdk/v5/plugin" 14 | ) 15 | 16 | //// TABLE DEFINITION 17 | 18 | func tableIssueWorklog(_ context.Context) *plugin.Table { 19 | return &plugin.Table{ 20 | Name: "jira_issue_worklog", 21 | Description: "Jira worklog is a feature within the Jira software that allows users to record the amount of time they have spent working on various tasks or issues.", 22 | Get: &plugin.GetConfig{ 23 | KeyColumns: plugin.AllColumns([]string{"issue_id", "id"}), 24 | Hydrate: getIssueWorklog, 25 | }, 26 | List: &plugin.ListConfig{ 27 | Hydrate: listWorklogs, 28 | KeyColumns: plugin.KeyColumnSlice{ 29 | {Name: "issue_id", Require: plugin.Optional}, 30 | {Name: "updated", Require: plugin.Optional, Operators: []string{">", ">="}}, 31 | }, 32 | }, 33 | Columns: commonColumns([]*plugin.Column{ 34 | // top fields 35 | { 36 | Name: "id", 37 | Description: "A unique identifier for the worklog entry.", 38 | Type: proto.ColumnType_STRING, 39 | Transform: transform.FromGo(), 40 | }, 41 | { 42 | Name: "issue_id", 43 | Description: "The ID of the issue.", 44 | Type: proto.ColumnType_STRING, 45 | }, 46 | { 47 | Name: "self", 48 | Description: "The URL of the worklogs.", 49 | Type: proto.ColumnType_STRING, 50 | }, 51 | { 52 | Name: "comment", 53 | Description: "Any comments or descriptions added to the worklog entry.", 54 | Type: proto.ColumnType_STRING, 55 | }, 56 | { 57 | Name: "started", 58 | Description: "The date and time when the worklog activity started.", 59 | Type: proto.ColumnType_TIMESTAMP, 60 | Transform: transform.FromField("Started").Transform(convertJiraTime), 61 | }, 62 | { 63 | Name: "created", 64 | Description: "The date and time when the worklog entry was created.", 65 | Type: proto.ColumnType_TIMESTAMP, 66 | Transform: transform.FromField("Created").Transform(convertJiraTime), 67 | }, 68 | { 69 | Name: "updated", 70 | Description: "The date and time when the worklog entry was last updated.", 71 | Type: proto.ColumnType_TIMESTAMP, 72 | Transform: transform.FromField("Updated").Transform(convertJiraTime), 73 | }, 74 | { 75 | Name: "time_spent", 76 | Description: "The duration of time logged for the task, often in hours or minutes.", 77 | Type: proto.ColumnType_STRING, 78 | }, 79 | { 80 | Name: "time_spent_seconds", 81 | Description: "The duration of time logged in seconds.", 82 | Type: proto.ColumnType_INT, 83 | }, 84 | { 85 | Name: "properties", 86 | Description: "The properties of each worklog.", 87 | Type: proto.ColumnType_JSON, 88 | }, 89 | { 90 | Name: "author", 91 | Description: "Information about the user who created the worklog entry, often including their username, display name, and user account details.", 92 | Type: proto.ColumnType_JSON, 93 | }, 94 | { 95 | Name: "update_author", 96 | Description: "Details of the user who last updated the worklog entry, similar to the author information.", 97 | Type: proto.ColumnType_JSON, 98 | }, 99 | 100 | // Standard columns 101 | { 102 | Name: "title", 103 | Description: ColumnDescriptionTitle, 104 | Type: proto.ColumnType_STRING, 105 | Transform: transform.FromField("ID"), 106 | }, 107 | }), 108 | } 109 | } 110 | 111 | type WorklogDetails struct { 112 | jira.WorklogRecord 113 | IssueId string 114 | } 115 | 116 | //// LIST FUNCTION 117 | 118 | func listWorklogs(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { 119 | if d.EqualsQualString("issue_id") != "" { 120 | return listIssueWorklogs(ctx, d, h) 121 | } 122 | 123 | // Fetch worklogs by updated time. Falls back to updated > 0 in 124 | // cases where update filter is not provided (all worklogs) 125 | return listWorklogsByUpdated(ctx, d, h) 126 | } 127 | 128 | func listIssueWorklogs(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { 129 | issueId := d.EqualsQualString("issue_id") 130 | 131 | if issueId == "" { 132 | return nil, nil 133 | } 134 | 135 | client, err := connect(ctx, d) 136 | if err != nil { 137 | plugin.Logger(ctx).Error("jira_issue_worklog.listIssueWorklogs", "connection_error", err) 138 | return nil, err 139 | } 140 | 141 | last := 0 142 | 143 | // If the requested number of items is less than the paging max limit 144 | // set the limit to that instead 145 | queryLimit := d.QueryContext.Limit 146 | var limit int = 5000 147 | if d.QueryContext.Limit != nil { 148 | if *queryLimit < 5000 { 149 | limit = int(*queryLimit) 150 | } 151 | } 152 | 153 | for { 154 | apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/worklog?startAt=%d&maxResults=%d&expand=properties", issueId, last, limit) 155 | 156 | req, err := client.NewRequest("GET", apiEndpoint, nil) 157 | if err != nil { 158 | plugin.Logger(ctx).Error("jira_issue_worklog.listIssueWorklogs", "get_request_error", err) 159 | return nil, err 160 | } 161 | 162 | w := new(jira.Worklog) 163 | _, err = client.Do(req, w) 164 | if err != nil { 165 | if isNotFoundError(err) { // Handle not found error code 166 | return nil, nil 167 | } 168 | 169 | plugin.Logger(ctx).Error("jira_issue_worklog.listIssueWorklogs", "api_error", err) 170 | return nil, err 171 | } 172 | 173 | for _, c := range w.Worklogs { 174 | d.StreamListItem(ctx, WorklogDetails{c, c.IssueID}) 175 | 176 | // Context may get cancelled due to manual cancellation or if the limit has been reached 177 | if d.RowsRemaining(ctx) == 0 { 178 | return nil, nil 179 | } 180 | } 181 | 182 | last = w.StartAt + len(w.Worklogs) 183 | if last >= w.Total { 184 | return nil, nil 185 | } 186 | } 187 | } 188 | 189 | type ChangedWorklogs struct { 190 | LastPage bool `json:"lastPage" structs:"lastPage"` 191 | NextPage *string `json:"nextPage" structs:"nextPage"` 192 | Values []ChangeWorklog `json:"values" structs:"values"` 193 | } 194 | 195 | type ChangeWorklog struct { 196 | WorklogId int64 `json:"WorklogId,omitempty" structs:"WorklogId,omitempty"` 197 | } 198 | 199 | func listWorklogsByUpdated(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { 200 | since := time.Time{} 201 | 202 | if d.Quals["updated"] != nil { 203 | for _, q := range d.Quals["updated"].Quals { 204 | switch q.Operator { 205 | case ">": 206 | since = q.Value.GetTimestampValue().AsTime().Add(time.Millisecond * 1) 207 | case ">=": 208 | since = q.Value.GetTimestampValue().AsTime() 209 | } 210 | } 211 | } 212 | 213 | client, err := connect(ctx, d) 214 | if err != nil { 215 | plugin.Logger(ctx).Error("jira_issue_worklog.listWorklogsByUpdated", "connection_error", err) 216 | return nil, err 217 | } 218 | 219 | nextPageUrl := fmt.Sprintf("rest/api/2/worklog/updated?since=%d&expand=properties", since.UnixMilli()) 220 | 221 | for { 222 | req, err := client.NewRequest("GET", nextPageUrl, nil) 223 | if err != nil { 224 | plugin.Logger(ctx).Error("jira_issue_worklog.listWorklogsByUpdated", "get_request_error", err) 225 | return nil, err 226 | } 227 | 228 | w := new(ChangedWorklogs) 229 | _, err = client.Do(req, w) 230 | if err != nil { 231 | plugin.Logger(ctx).Error("jira_issue_worklog.listWorklogsByUpdated", "api_error", err) 232 | return nil, err 233 | } 234 | 235 | worklogIds := []int64{} 236 | for _, v := range w.Values { 237 | worklogIds = append(worklogIds, v.WorklogId) 238 | } 239 | 240 | // no need to worry about pagination in batchGetWorklog 241 | // since it accepts same number of worklogs per page (1000) 242 | // as this method returns at maximum (also 1000) 243 | _, err = batchGetWorklog(ctx, worklogIds, d) 244 | if err != nil { 245 | return nil, err 246 | } 247 | 248 | if w.LastPage { 249 | return nil, nil 250 | } 251 | 252 | if d.RowsRemaining(ctx) == 0 { 253 | return nil, nil 254 | } 255 | 256 | if w.NextPage != nil { 257 | nextPageUrl = *w.NextPage 258 | // Extract the path from the full URL using the url package 259 | parsedUrl, err := url.Parse(nextPageUrl) 260 | if err != nil { 261 | plugin.Logger(ctx).Error("jira_issue_worklog.listWorklogsByUpdated", "parsing_error", err) 262 | return nil, err 263 | } 264 | nextPageUrl = parsedUrl.Path + "?" + parsedUrl.RawQuery 265 | } 266 | } 267 | } 268 | 269 | func batchGetWorklog(ctx context.Context, ids []int64, d *plugin.QueryData) (interface{}, error) { 270 | client, err := connect(ctx, d) 271 | if err != nil { 272 | plugin.Logger(ctx).Error("jira_issue_worklog.batchGetWorklog", "connection_error", err) 273 | return nil, err 274 | } 275 | 276 | apiEndpoint := "rest/api/2/worklog/list?expand=properties" 277 | 278 | body := struct { 279 | Ids []int64 `json:"ids"` 280 | }{ 281 | Ids: ids, 282 | } 283 | 284 | req, err := client.NewRequest("POST", apiEndpoint, body) 285 | if err != nil { 286 | plugin.Logger(ctx).Error("jira_issue_worklog.batchGetWorklog", "get_request_error", err) 287 | return nil, err 288 | } 289 | 290 | w := make([]jira.WorklogRecord, 0) 291 | _, err = client.Do(req, &w) 292 | 293 | if err != nil { 294 | if isNotFoundError(err) { // Handle not found error code 295 | return nil, nil 296 | } 297 | 298 | plugin.Logger(ctx).Error("jira_issue_worklog.batchGetWorklog", "api_error", err) 299 | return nil, err 300 | } 301 | 302 | for _, c := range w { 303 | d.StreamListItem(ctx, WorklogDetails{c, c.IssueID}) 304 | 305 | // Context may get cancelled due to manual cancellation or if the limit has been reached 306 | if d.RowsRemaining(ctx) == 0 { 307 | return nil, nil 308 | } 309 | } 310 | 311 | return nil, nil 312 | } 313 | 314 | //// HYDRATE FUNCTION 315 | 316 | func getIssueWorklog(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { 317 | 318 | issueId := d.EqualsQualString("issue_id") 319 | id := d.EqualsQualString("id") 320 | 321 | if issueId == "" || id == "" { 322 | return nil, nil 323 | } 324 | 325 | client, err := connect(ctx, d) 326 | if err != nil { 327 | plugin.Logger(ctx).Error("jira_issue_worklog.getIssueWorklog", "connection_error", err) 328 | return nil, err 329 | } 330 | 331 | apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/worklog/%s?expand=properties", issueId, id) 332 | 333 | req, err := client.NewRequest("GET", apiEndpoint, nil) 334 | if err != nil { 335 | plugin.Logger(ctx).Error("jira_issue_worklog.getIssueWorklog", "get_request_error", err) 336 | return nil, err 337 | } 338 | 339 | res := new(jira.WorklogRecord) 340 | _, err = client.Do(req, res) 341 | if err != nil { 342 | if isNotFoundError(err) { 343 | return nil, nil 344 | } 345 | plugin.Logger(ctx).Error("jira_issue_worklog.getIssueWorklog", "api_error", err) 346 | return nil, err 347 | } 348 | 349 | return WorklogDetails{*res, issueId}, nil 350 | } 351 | -------------------------------------------------------------------------------- /jira/table_jira_priority.go: -------------------------------------------------------------------------------- 1 | package jira 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | 8 | "github.com/andygrunwald/go-jira" 9 | "github.com/turbot/steampipe-plugin-sdk/v5/grpc/proto" 10 | "github.com/turbot/steampipe-plugin-sdk/v5/plugin/transform" 11 | 12 | "github.com/turbot/steampipe-plugin-sdk/v5/plugin" 13 | ) 14 | 15 | //// TABLE DEFINITION 16 | 17 | func tablePriority(_ context.Context) *plugin.Table { 18 | return &plugin.Table{ 19 | Name: "jira_priority", 20 | Description: "Details of the issue priority.", 21 | Get: &plugin.GetConfig{ 22 | KeyColumns: plugin.SingleColumn("id"), 23 | Hydrate: getPriority, 24 | }, 25 | List: &plugin.ListConfig{ 26 | Hydrate: listPriorities, 27 | }, 28 | Columns: commonColumns([]*plugin.Column{ 29 | { 30 | Name: "name", 31 | Description: "The name of the issue priority.", 32 | Type: proto.ColumnType_STRING, 33 | }, 34 | { 35 | Name: "id", 36 | Description: "The ID of the issue priority.", 37 | Type: proto.ColumnType_STRING, 38 | Transform: transform.FromGo(), 39 | }, 40 | { 41 | Name: "description", 42 | Description: "The description of the issue priority.", 43 | Type: proto.ColumnType_STRING, 44 | }, 45 | { 46 | Name: "self", 47 | Description: "The URL of the issue priority.", 48 | Type: proto.ColumnType_STRING, 49 | }, 50 | { 51 | Name: "icon_url", 52 | Description: "The URL of the icon for the issue priority.", 53 | Type: proto.ColumnType_STRING, 54 | Transform: transform.FromField("IconURL"), 55 | }, 56 | { 57 | Name: "status_color", 58 | Description: "The color used to indicate the issue priority.", 59 | Type: proto.ColumnType_STRING, 60 | }, 61 | 62 | // Steampipe standard columns 63 | { 64 | Name: "title", 65 | Description: ColumnDescriptionTitle, 66 | Type: proto.ColumnType_STRING, 67 | Transform: transform.FromField("Name"), 68 | }, 69 | }), 70 | } 71 | } 72 | 73 | //// LIST FUNCTION 74 | 75 | func listPriorities(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { 76 | client, err := connect(ctx, d) 77 | if err != nil { 78 | plugin.Logger(ctx).Error("jira_priority.listPriorities", "connection_error", err) 79 | return nil, err 80 | } 81 | 82 | req, err := client.NewRequest("GET", "rest/api/3/priority", nil) 83 | if err != nil { 84 | plugin.Logger(ctx).Error("jira_priority.listPriorities", "get_request_error", err) 85 | return nil, err 86 | } 87 | priorities := new([]jira.Priority) 88 | 89 | res, err := client.Do(req, priorities) 90 | body, _ := io.ReadAll(res.Body) 91 | plugin.Logger(ctx).Debug("jira_priority.listPriorities", "res_body", string(body)) 92 | 93 | if err != nil { 94 | plugin.Logger(ctx).Error("jira_priority.listPriorities", "api_error", err) 95 | return nil, err 96 | } 97 | 98 | for _, priority := range *priorities { 99 | d.StreamListItem(ctx, priority) 100 | // Context may get cancelled due to manual cancellation or if the limit has been reached 101 | if d.RowsRemaining(ctx) == 0 { 102 | return nil, nil 103 | } 104 | } 105 | 106 | return nil, err 107 | } 108 | 109 | //// HYDRATE FUNCTIONS 110 | 111 | func getPriority(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { 112 | client, err := connect(ctx, d) 113 | if err != nil { 114 | plugin.Logger(ctx).Error("jira_priority.getPriority", "connection_error", err) 115 | return nil, err 116 | } 117 | priorityId := d.EqualsQuals["id"].GetStringValue() 118 | 119 | // Return nil, if no input provided 120 | if priorityId == "" { 121 | return nil, nil 122 | } 123 | 124 | apiEndpoint := fmt.Sprintf("/rest/api/3/priority/%s", priorityId) 125 | req, err := client.NewRequest("GET", apiEndpoint, nil) 126 | if err != nil { 127 | plugin.Logger(ctx).Error("jira_priority.getPriority", "get_request_error", err) 128 | return nil, err 129 | } 130 | result := new(jira.Priority) 131 | 132 | res, err := client.Do(req, result) 133 | body, _ := io.ReadAll(res.Body) 134 | plugin.Logger(ctx).Debug("jira_priority.getPriority", "res_body", string(body)) 135 | 136 | if err != nil { 137 | if isNotFoundError(err) { 138 | return nil, nil 139 | } 140 | plugin.Logger(ctx).Error("jira_priority.getPriority", "api_error", err) 141 | return nil, err 142 | } 143 | 144 | return result, nil 145 | } 146 | -------------------------------------------------------------------------------- /jira/table_jira_project_role.go: -------------------------------------------------------------------------------- 1 | package jira 2 | 3 | import ( 4 | "context" 5 | "io" 6 | 7 | "github.com/andygrunwald/go-jira" 8 | "github.com/turbot/steampipe-plugin-sdk/v5/grpc/proto" 9 | "github.com/turbot/steampipe-plugin-sdk/v5/plugin/transform" 10 | 11 | "github.com/turbot/steampipe-plugin-sdk/v5/plugin" 12 | ) 13 | 14 | //// TABLE DEFINITION 15 | 16 | func tableProjectRole(_ context.Context) *plugin.Table { 17 | return &plugin.Table{ 18 | Name: "jira_project_role", 19 | Description: "Project Roles are a flexible way to associate users and/or groups with particular projects.", 20 | Get: &plugin.GetConfig{ 21 | KeyColumns: plugin.SingleColumn("id"), 22 | Hydrate: getProjectRole, 23 | }, 24 | List: &plugin.ListConfig{ 25 | Hydrate: listProjectRoles, 26 | }, 27 | Columns: commonColumns([]*plugin.Column{ 28 | { 29 | Name: "id", 30 | Description: "The ID of the project role.", 31 | Type: proto.ColumnType_INT, 32 | Transform: transform.FromGo(), 33 | }, 34 | { 35 | Name: "name", 36 | Description: "The name of the project role.", 37 | Type: proto.ColumnType_STRING, 38 | }, 39 | { 40 | Name: "self", 41 | Description: "The URL the project role details.", 42 | Type: proto.ColumnType_STRING, 43 | }, 44 | { 45 | Name: "description", 46 | Description: "The description of the project role.", 47 | Type: proto.ColumnType_STRING, 48 | }, 49 | { 50 | Name: "actor_account_ids", 51 | Description: "The list of user ids who act in this role.", 52 | Type: proto.ColumnType_JSON, 53 | Transform: transform.From(extractActorAccountIds), 54 | }, 55 | { 56 | Name: "actor_names", 57 | Description: "The list of user ids who act in this role.", 58 | Type: proto.ColumnType_JSON, 59 | Transform: transform.From(extractActorNames), 60 | }, 61 | 62 | // Standard columns 63 | { 64 | Name: "title", 65 | Description: ColumnDescriptionTitle, 66 | Type: proto.ColumnType_STRING, 67 | Transform: transform.FromField("Name"), 68 | }, 69 | }), 70 | } 71 | } 72 | 73 | //// LIST FUNCTION 74 | 75 | func listProjectRoles(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { 76 | client, err := connect(ctx, d) 77 | if err != nil { 78 | plugin.Logger(ctx).Error("jira_project_role.listProjectRoles", "connection_error", err) 79 | return nil, err 80 | } 81 | 82 | roles, res, err := client.Role.GetListWithContext(ctx) 83 | body, _ := io.ReadAll(res.Body) 84 | plugin.Logger(ctx).Debug("jira_project_role.listProjectRoles", "res_body", string(body)) 85 | if err != nil { 86 | plugin.Logger(ctx).Error("jira_project_role.listProjectRoles", "api_error", err) 87 | return nil, err 88 | } 89 | 90 | for _, role := range *roles { 91 | d.StreamListItem(ctx, role) 92 | // Context may get cancelled due to manual cancellation or if the limit has been reached 93 | if d.RowsRemaining(ctx) == 0 { 94 | return nil, nil 95 | } 96 | } 97 | 98 | return nil, err 99 | } 100 | 101 | //// HYDRATE FUNCTION 102 | 103 | func getProjectRole(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { 104 | roleId := d.EqualsQuals["id"].GetInt64Value() 105 | 106 | client, err := connect(ctx, d) 107 | if err != nil { 108 | plugin.Logger(ctx).Error("jira_project_role.getProjectRole", "connection_error", err) 109 | return nil, err 110 | } 111 | 112 | if roleId == 0 { 113 | return nil, nil 114 | } 115 | 116 | role, res, err := client.Role.Get(int(roleId)) 117 | body, _ := io.ReadAll(res.Body) 118 | plugin.Logger(ctx).Debug("jira_project_role.getProjectRole", "res_body", string(body)) 119 | if err != nil { 120 | if isNotFoundError(err) { 121 | return nil, nil 122 | } 123 | plugin.Logger(ctx).Error("jira_project_role.getProjectRole", "api_error", err) 124 | return nil, err 125 | } 126 | 127 | return *role, err 128 | } 129 | 130 | //// TRANSFORM FUNCTION 131 | 132 | func extractActorAccountIds(_ context.Context, d *transform.TransformData) (interface{}, error) { 133 | var actorIds []string 134 | for _, actor := range d.HydrateItem.(jira.Role).Actors { 135 | actorIds = append(actorIds, actor.ActorUser.AccountID) 136 | } 137 | return actorIds, nil 138 | } 139 | 140 | func extractActorNames(_ context.Context, d *transform.TransformData) (interface{}, error) { 141 | var actorNames []string 142 | for _, actor := range d.HydrateItem.(jira.Role).Actors { 143 | actorNames = append(actorNames, actor.DisplayName) 144 | } 145 | return actorNames, nil 146 | } 147 | -------------------------------------------------------------------------------- /jira/table_jira_sprint.go: -------------------------------------------------------------------------------- 1 | package jira 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | "time" 8 | 9 | "github.com/andygrunwald/go-jira" 10 | "github.com/turbot/steampipe-plugin-sdk/v5/grpc/proto" 11 | "github.com/turbot/steampipe-plugin-sdk/v5/plugin/transform" 12 | 13 | "github.com/turbot/steampipe-plugin-sdk/v5/plugin" 14 | ) 15 | 16 | //// TABLE DEFINITION 17 | 18 | func tableSprint(_ context.Context) *plugin.Table { 19 | return &plugin.Table{ 20 | Name: "jira_sprint", 21 | Description: "Sprint is a short period in which the development team implements and delivers a discrete and potentially shippable application increment.", 22 | // TODO - Not getting board id details for get call 23 | // Get: &plugin.GetConfig{ 24 | // KeyColumns: plugin.SingleColumn("id"), 25 | // Hydrate: getSprint, 26 | // }, 27 | List: &plugin.ListConfig{ 28 | ParentHydrate: listBoards, 29 | Hydrate: listSprints, 30 | }, 31 | Columns: commonColumns([]*plugin.Column{ 32 | { 33 | Name: "id", 34 | Description: "The ID of the sprint.", 35 | Type: proto.ColumnType_INT, 36 | }, 37 | { 38 | Name: "name", 39 | Description: "The name of the sprint.", 40 | Type: proto.ColumnType_STRING, 41 | }, 42 | { 43 | Name: "board_id", 44 | Description: "The ID of the board the sprint belongs to.z", 45 | Type: proto.ColumnType_INT, 46 | Transform: transform.FromField("BoardId", "OriginBoardId"), 47 | }, 48 | { 49 | Name: "self", 50 | Description: "The URL of the sprint details.", 51 | Type: proto.ColumnType_STRING, 52 | }, 53 | { 54 | Name: "state", 55 | Description: "Status of the sprint.", 56 | Type: proto.ColumnType_STRING, 57 | }, 58 | { 59 | Name: "start_date", 60 | Description: "The start timestamp of the sprint.", 61 | Type: proto.ColumnType_TIMESTAMP, 62 | Transform: transform.FromCamel().NullIfZero(), 63 | }, 64 | { 65 | Name: "end_date", 66 | Description: "The projected time of completion of the sprint.", 67 | Type: proto.ColumnType_TIMESTAMP, 68 | Transform: transform.FromCamel().NullIfZero(), 69 | }, 70 | { 71 | Name: "complete_date", 72 | Description: "Date the sprint was marked as complete.", 73 | Type: proto.ColumnType_TIMESTAMP, 74 | Transform: transform.FromCamel().NullIfZero(), 75 | }, 76 | 77 | // Standard columns 78 | { 79 | Name: "title", 80 | Description: ColumnDescriptionTitle, 81 | Type: proto.ColumnType_STRING, 82 | Transform: transform.FromField("Name"), 83 | }, 84 | }), 85 | } 86 | } 87 | 88 | //// LIST FUNCTION 89 | 90 | func listSprints(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { 91 | board := h.Item.(jira.Board) 92 | 93 | client, err := connect(ctx, d) 94 | if err != nil { 95 | plugin.Logger(ctx).Error("jira_sprint.listSprints", "connection_error", err) 96 | return nil, err 97 | } 98 | 99 | // If the requested number of items is less than the paging max limit 100 | // set the limit to that instead 101 | queryLimit := d.QueryContext.Limit 102 | var maxResults int = 1000 103 | if d.QueryContext.Limit != nil { 104 | if *queryLimit < 1000 { 105 | maxResults = int(*queryLimit) 106 | } 107 | } 108 | 109 | last := 0 110 | for { 111 | apiEndpoint := fmt.Sprintf( 112 | "/rest/agile/1.0/board/%d/sprint?startAt=%d&maxResults=%d", 113 | board.ID, 114 | last, 115 | maxResults, 116 | ) 117 | 118 | req, err := client.NewRequest("GET", apiEndpoint, nil) 119 | if err != nil { 120 | plugin.Logger(ctx).Error("jira_sprint.listSprints", "get_request_error", err) 121 | return nil, err 122 | } 123 | 124 | listResult := new(ListSprintResult) 125 | res, err := client.Do(req, listResult) 126 | body, _ := io.ReadAll(res.Body) 127 | plugin.Logger(ctx).Debug("jira_sprint.listSprints", "res_body", string(body)) 128 | 129 | if err != nil { 130 | if isNotFoundError(err) || isBadRequestError(err) { 131 | return nil, nil 132 | } 133 | plugin.Logger(ctx).Error("jira_sprint.listSprints", "api_error", err) 134 | return nil, err 135 | } 136 | 137 | for _, sprint := range listResult.Values { 138 | d.StreamListItem(ctx, SprintItemInfo{int64(board.ID), sprint}) 139 | // Context may get cancelled due to manual cancellation or if the limit has been reached 140 | if d.RowsRemaining(ctx) == 0 { 141 | return nil, nil 142 | } 143 | } 144 | 145 | last = listResult.StartAt + len(listResult.Values) 146 | if listResult.IsLast { 147 | return nil, nil 148 | } 149 | } 150 | } 151 | 152 | type ListSprintResult struct { 153 | MaxResults int `json:"maxResults"` 154 | StartAt int `json:"startAt"` 155 | Total int `json:"total"` 156 | IsLast bool `json:"isLast"` 157 | Values []Sprint `json:"values"` 158 | } 159 | 160 | type Sprint struct { 161 | Id int64 `json:"id"` 162 | Self string `json:"self"` 163 | Name string `json:"name"` 164 | State string `json:"state"` 165 | EndDate time.Time `json:"endDate"` 166 | StartDate time.Time `json:"startDate"` 167 | CompleteDate time.Time `json:"completeDate"` 168 | OriginBoardId int `json:"originBoardId"` 169 | } 170 | 171 | type SprintItemInfo struct { 172 | BoardId int64 173 | Sprint 174 | } 175 | -------------------------------------------------------------------------------- /jira/table_jira_user.go: -------------------------------------------------------------------------------- 1 | package jira 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | 8 | "github.com/andygrunwald/go-jira" 9 | "github.com/turbot/steampipe-plugin-sdk/v5/grpc/proto" 10 | "github.com/turbot/steampipe-plugin-sdk/v5/plugin/transform" 11 | 12 | "github.com/turbot/steampipe-plugin-sdk/v5/plugin" 13 | ) 14 | 15 | //// TABLE DEFINITION 16 | 17 | func tableUser(_ context.Context) *plugin.Table { 18 | return &plugin.Table{ 19 | Name: "jira_user", 20 | Description: "User in the Jira cloud.", 21 | List: &plugin.ListConfig{ 22 | Hydrate: listUsers, 23 | }, 24 | HydrateConfig: []plugin.HydrateConfig{ 25 | { 26 | // Limit concurrency to avoid a 429 too many requests error 27 | Func: getUserGroups, 28 | MaxConcurrency: 50, 29 | }, 30 | }, 31 | Columns: commonColumns([]*plugin.Column{ 32 | { 33 | Name: "display_name", 34 | Description: "The display name of the user. Depending on the user's privacy setting, this may return an alternative value.", 35 | Type: proto.ColumnType_STRING, 36 | }, 37 | { 38 | Name: "account_id", 39 | Description: "The account ID of the user, which uniquely identifies the user across all Atlassian products. For example, 5b10ac8d82e05b22cc7d4ef5.", 40 | Type: proto.ColumnType_STRING, 41 | Transform: transform.FromGo(), 42 | }, 43 | { 44 | Name: "email_address", 45 | Description: "The email address of the user. Depending on the user's privacy setting, this may be returned as null.", 46 | Type: proto.ColumnType_STRING, 47 | }, 48 | { 49 | Name: "account_type", 50 | Description: "The user account type. Can take the following values: atlassian, app, customer and unknown.", 51 | Type: proto.ColumnType_STRING, 52 | }, 53 | { 54 | Name: "active", 55 | Description: "Indicates if user is active.", 56 | Type: proto.ColumnType_BOOL, 57 | Transform: transform.FromField("Active"), 58 | }, 59 | { 60 | Name: "self", 61 | Description: "The URL of the user.", 62 | Type: proto.ColumnType_STRING, 63 | }, 64 | { 65 | Name: "avatar_urls", 66 | Description: "The avatars of the user.", 67 | Type: proto.ColumnType_JSON, 68 | }, 69 | { 70 | Name: "group_names", 71 | Description: "The groups that the user belongs to.", 72 | Type: proto.ColumnType_JSON, 73 | Hydrate: getUserGroups, 74 | Transform: transform.From(groupNames), 75 | }, 76 | 77 | // Standard columns 78 | { 79 | Name: "title", 80 | Description: ColumnDescriptionTitle, 81 | Type: proto.ColumnType_STRING, 82 | Transform: transform.FromField("DisplayName"), 83 | }, 84 | }), 85 | } 86 | } 87 | 88 | //// LIST FUNCTION 89 | 90 | func listUsers(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { 91 | client, err := connect(ctx, d) 92 | if err != nil { 93 | plugin.Logger(ctx).Error("jira_user.listUsers", "connection_error", err) 94 | return nil, err 95 | } 96 | 97 | // If the requested number of items is less than the paging max limit 98 | // set the limit to that instead 99 | queryLimit := d.QueryContext.Limit 100 | var maxResults int = 1000 101 | if d.QueryContext.Limit != nil { 102 | if *queryLimit < 1000 { 103 | maxResults = int(*queryLimit) 104 | } 105 | } 106 | 107 | last := 0 108 | for { 109 | apiEndpoint := fmt.Sprintf("rest/api/2/users/search?startAt=%d&maxResults=%d", last, maxResults) 110 | 111 | req, err := client.NewRequest("GET", apiEndpoint, nil) 112 | if err != nil { 113 | plugin.Logger(ctx).Error("jira_user.listUsers", "get_request_error", err) 114 | return nil, err 115 | } 116 | 117 | users := new([]jira.User) 118 | res, err := client.Do(req, users) 119 | body, _ := io.ReadAll(res.Body) 120 | plugin.Logger(ctx).Debug("jira_user.listUsers", "res_body", string(body)) 121 | 122 | if err != nil { 123 | plugin.Logger(ctx).Error("jira_user.listUsers", "api_error", err) 124 | return nil, err 125 | } 126 | 127 | for _, user := range *users { 128 | d.StreamListItem(ctx, user) 129 | // Context may get cancelled due to manual cancellation or if the limit has been reached 130 | if d.RowsRemaining(ctx) == 0 { 131 | return nil, nil 132 | } 133 | } 134 | 135 | // evaluate paging start value for next iteration 136 | last = last + len(*users) 137 | 138 | // API doesn't gives paging parameters in the response, 139 | // therefore using output length to quit paging 140 | if len(*users) < 1000 { 141 | return nil, nil 142 | } 143 | } 144 | } 145 | 146 | //// HYDRATE FUNCTIONS 147 | 148 | func getUserGroups(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { 149 | user := h.Item.(jira.User) 150 | 151 | client, err := connect(ctx, d) 152 | if err != nil { 153 | plugin.Logger(ctx).Error("jira_user.getUserGroups", "connection_error", err) 154 | return nil, err 155 | } 156 | 157 | groups, _, err := client.User.GetGroups(user.AccountID) 158 | 159 | if err != nil { 160 | plugin.Logger(ctx).Error("jira_user.getUserGroups", "api_error", err) 161 | return nil, err 162 | } 163 | 164 | return groups, nil 165 | } 166 | 167 | //// TRANSFORM FUNCTION 168 | 169 | func groupNames(_ context.Context, d *transform.TransformData) (interface{}, error) { 170 | userGroups := d.HydrateItem.(*[]jira.UserGroup) 171 | var groupNames []string 172 | for _, group := range *userGroups { 173 | groupNames = append(groupNames, group.Name) 174 | } 175 | return groupNames, nil 176 | } 177 | -------------------------------------------------------------------------------- /jira/table_jira_workflow.go: -------------------------------------------------------------------------------- 1 | package jira 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | "net/url" 8 | 9 | "github.com/turbot/steampipe-plugin-sdk/v5/grpc/proto" 10 | "github.com/turbot/steampipe-plugin-sdk/v5/plugin/transform" 11 | 12 | "github.com/turbot/steampipe-plugin-sdk/v5/plugin" 13 | ) 14 | 15 | //// TABLE DEFINITION 16 | 17 | func tableWorkflow(_ context.Context) *plugin.Table { 18 | return &plugin.Table{ 19 | Name: "jira_workflow", 20 | Description: "A Jira workflow is a set of statuses and transitions that an issue moves through during its lifecycle, and typically represents a process within your organization.", 21 | Get: &plugin.GetConfig{ 22 | KeyColumns: plugin.SingleColumn("name"), 23 | Hydrate: getWorkflow, 24 | }, 25 | List: &plugin.ListConfig{ 26 | Hydrate: listWorkflows, 27 | }, 28 | Columns: commonColumns([]*plugin.Column{ 29 | { 30 | Name: "name", 31 | Description: "The name of the workflow.", 32 | Type: proto.ColumnType_STRING, 33 | Transform: transform.FromField("ID.Name"), 34 | }, 35 | { 36 | Name: "entity_id", 37 | Description: "The entity ID of the workflow.", 38 | Type: proto.ColumnType_STRING, 39 | Transform: transform.FromField("ID.EntityID"), 40 | }, 41 | { 42 | Name: "description", 43 | Description: "The description of the workflow.", 44 | Type: proto.ColumnType_STRING, 45 | }, 46 | { 47 | Name: "is_default", 48 | Description: "Whether this is the default workflow.", 49 | Type: proto.ColumnType_BOOL, 50 | }, 51 | 52 | // json fields 53 | { 54 | Name: "transitions", 55 | Description: "The transitions of the workflow.", 56 | Type: proto.ColumnType_JSON, 57 | }, 58 | { 59 | Name: "statuses", 60 | Description: "The statuses of the workflow.", 61 | Type: proto.ColumnType_JSON, 62 | }, 63 | 64 | // Standard columns 65 | { 66 | Name: "title", 67 | Description: ColumnDescriptionTitle, 68 | Type: proto.ColumnType_STRING, 69 | Transform: transform.FromField("ID.Name"), 70 | }, 71 | }), 72 | } 73 | } 74 | 75 | //// LIST FUNCTION 76 | 77 | func listWorkflows(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { 78 | client, err := connect(ctx, d) 79 | if err != nil { 80 | plugin.Logger(ctx).Error("jira_workflow.listWorkflows", "connection_error", err) 81 | return nil, err 82 | } 83 | 84 | // If the requested number of items is less than the paging max limit 85 | // set the limit to that instead 86 | queryLimit := d.QueryContext.Limit 87 | var maxResults int = 1000 88 | if d.QueryContext.Limit != nil { 89 | if *queryLimit < 1000 { 90 | maxResults = int(*queryLimit) 91 | } 92 | } 93 | 94 | last := 0 95 | for { 96 | apiEndpoint := fmt.Sprintf( 97 | "/rest/api/3/workflow/search?startAt=%d&maxResults=%d&expand=transitions,transitions.rules,statuses,statuses.properties,default", 98 | last, 99 | maxResults, 100 | ) 101 | 102 | req, err := client.NewRequest("GET", apiEndpoint, nil) 103 | if err != nil { 104 | plugin.Logger(ctx).Error("jira_workflow.listWorkflows", "get_request_error", err) 105 | return nil, err 106 | } 107 | 108 | listResult := new(ListWorkflowResult) 109 | res, err := client.Do(req, listResult) 110 | body, _ := io.ReadAll(res.Body) 111 | plugin.Logger(ctx).Debug("jira_workflow.listWorkflows", "res_body", string(body)) 112 | 113 | if err != nil { 114 | plugin.Logger(ctx).Error("jira_workflow.listWorkflows", "api_error", err) 115 | return nil, err 116 | } 117 | 118 | for _, workflow := range listResult.Values { 119 | d.StreamListItem(ctx, workflow) 120 | // Context may get cancelled due to manual cancellation or if the limit has been reached 121 | if d.RowsRemaining(ctx) == 0 { 122 | return nil, nil 123 | } 124 | } 125 | 126 | last = listResult.StartAt + len(listResult.Values) 127 | if listResult.IsLast { 128 | return nil, nil 129 | } 130 | } 131 | } 132 | 133 | //// HYDRATE FUNCTIONS 134 | 135 | func getWorkflow(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { 136 | workflowName := url.PathEscape(d.EqualsQuals["name"].GetStringValue()) 137 | 138 | if workflowName == "" { 139 | return nil, nil 140 | } 141 | 142 | client, err := connect(ctx, d) 143 | if err != nil { 144 | plugin.Logger(ctx).Error("jira_workflow.getWorkflow", "connection_error", err) 145 | return nil, err 146 | } 147 | 148 | apiEndpoint := fmt.Sprintf( 149 | "/rest/api/3/workflow/search?workflowName=%s&expand=transitions,transitions.rules,statuses,statuses.properties,default", 150 | workflowName, 151 | ) 152 | 153 | req, err := client.NewRequest("GET", apiEndpoint, nil) 154 | if err != nil { 155 | plugin.Logger(ctx).Error("jira_workflow.getWorkflow", "get_request_error", err) 156 | return nil, err 157 | } 158 | 159 | workflow := new(ListWorkflowResult) 160 | res, err := client.Do(req, workflow) 161 | body, _ := io.ReadAll(res.Body) 162 | plugin.Logger(ctx).Debug("jira_workflow.getWorkflow", "res_body", string(body)) 163 | 164 | if err != nil { 165 | plugin.Logger(ctx).Error("jira_workflow.getWorkflow", "api_error", err) 166 | return nil, err 167 | } 168 | if len(workflow.Values) < 1 { 169 | return nil, nil 170 | } 171 | 172 | return workflow.Values[0], nil 173 | } 174 | 175 | //// Custom Structs 176 | 177 | type ListWorkflowResult struct { 178 | Self string `json:"self"` 179 | NextPage string `json:"nextPage"` 180 | MaxResults int `json:"maxResults"` 181 | StartAt int `json:"startAt"` 182 | Total int `json:"total"` 183 | IsLast bool `json:"isLast"` 184 | Values []Workflow `json:"values"` 185 | } 186 | 187 | type Workflow struct { 188 | ID WorkflowID `json:"id"` 189 | Description string `json:"description"` 190 | Transitions []WorkflowTransition `json:"transitions"` // Check fields 191 | Statuses []WorkflowStatus `json:"statuses"` 192 | IsDefault bool `json:"isDefault"` 193 | } 194 | 195 | type WorkflowID struct { 196 | Name string `json:"name"` 197 | EntityID string `json:"entityId"` 198 | } 199 | 200 | type WorkflowStatus struct { 201 | ID string `json:"id"` 202 | Name string `json:"name"` 203 | Properties WorkflowStatusProperty `json:"properties"` 204 | } 205 | 206 | type WorkflowStatusProperty struct { 207 | IssueEditable bool `json:"issueEditable"` 208 | } 209 | 210 | type WorkflowTransition struct { 211 | ID string `json:"id"` 212 | Name string `json:"name"` 213 | Description string `json:"description"` 214 | From []string `json:"from"` 215 | To string `json:"to"` 216 | Type string `json:"type"` 217 | Screen WorkflowTransitionScreen `json:"screen"` 218 | Rules WorkflowRules `json:"rules"` 219 | } 220 | 221 | type WorkflowTransitionScreen struct { 222 | ID string `json:"id"` 223 | } 224 | 225 | type WorkflowRules struct { 226 | ConditionsTree interface{} `json:"conditionsTree"` 227 | Validators []WorkflowTransitionRules `json:"validators"` 228 | PostFunctions []WorkflowTransitionRules `json:"postFunctions"` 229 | } 230 | 231 | type WorkflowTransitionRules struct { 232 | Type string `json:"type"` 233 | Configuration interface{} `json:"configuration"` 234 | } 235 | -------------------------------------------------------------------------------- /jira/utils.go: -------------------------------------------------------------------------------- 1 | package jira 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "os" 8 | "strings" 9 | "time" 10 | 11 | "github.com/andygrunwald/go-jira" 12 | jirav2 "github.com/andygrunwald/go-jira/v2/onpremise" 13 | "github.com/turbot/steampipe-plugin-sdk/v5/grpc/proto" 14 | "github.com/turbot/steampipe-plugin-sdk/v5/plugin" 15 | "github.com/turbot/steampipe-plugin-sdk/v5/plugin/transform" 16 | ) 17 | 18 | func connect(_ context.Context, d *plugin.QueryData) (*jira.Client, error) { 19 | 20 | // Load connection from cache, which preserves throttling protection etc 21 | cacheKey := "atlassian-jira" 22 | if cachedData, ok := d.ConnectionManager.Cache.Get(cacheKey); ok { 23 | return cachedData.(*jira.Client), nil 24 | } 25 | 26 | // Default to the env var settings 27 | baseUrl := os.Getenv("JIRA_URL") 28 | username := os.Getenv("JIRA_USER") 29 | token := os.Getenv("JIRA_TOKEN") 30 | personal_access_token := os.Getenv("JIRA_PERSONAL_ACCESS_TOKEN") 31 | 32 | // Prefer config options given in Steampipe 33 | jiraConfig := GetConfig(d.Connection) 34 | 35 | if jiraConfig.BaseUrl != nil { 36 | baseUrl = *jiraConfig.BaseUrl 37 | } 38 | if jiraConfig.Username != nil { 39 | username = *jiraConfig.Username 40 | } 41 | if jiraConfig.Token != nil { 42 | token = *jiraConfig.Token 43 | } 44 | if jiraConfig.PersonalAccessToken != nil { 45 | personal_access_token = *jiraConfig.PersonalAccessToken 46 | } 47 | 48 | if baseUrl == "" { 49 | return nil, errors.New("'base_url' must be set in the connection configuration") 50 | } 51 | if username == "" && token != "" { 52 | return nil, errors.New("'token' is set but 'username' is not set in the connection configuration") 53 | } 54 | if token == "" && personal_access_token == "" { 55 | return nil, errors.New("'token' or 'personal_access_token' must be set in the connection configuration") 56 | } 57 | if token != "" && personal_access_token != "" { 58 | return nil, errors.New("'token' and 'personal_access_token' are both set, please use only one auth method") 59 | } 60 | 61 | var client *jira.Client 62 | var err error 63 | 64 | if personal_access_token != "" { 65 | // If the username is empty, let's assume the user is using a PAT 66 | tokenProvider := jirav2.BearerAuthTransport{Token: personal_access_token} 67 | client, err = jira.NewClient(tokenProvider.Client(), baseUrl) 68 | } else { 69 | tokenProvider := jira.BasicAuthTransport{ 70 | Username: username, 71 | Password: token, 72 | } 73 | client, err = jira.NewClient(tokenProvider.Client(), baseUrl) 74 | } 75 | 76 | if err != nil { 77 | return nil, fmt.Errorf("error creating Jira client: %s", err.Error()) 78 | } 79 | 80 | // Save to cache 81 | d.ConnectionManager.Cache.Set(cacheKey, client) 82 | 83 | // Done 84 | return client, nil 85 | } 86 | 87 | // // Constants 88 | const ( 89 | ColumnDescriptionTitle = "Title of the resource." 90 | ) 91 | 92 | //// TRANSFORM FUNCTION 93 | 94 | // convertJiraTime:: converts jira.Time to time.Time 95 | func convertJiraTime(_ context.Context, d *transform.TransformData) (interface{}, error) { 96 | if d.Value == nil { 97 | return nil, nil 98 | } 99 | if v, ok := d.Value.(jira.Time); ok { 100 | return time.Time(v), nil 101 | } else if v, ok := d.Value.(*jira.Time); ok { 102 | return time.Time(*v), nil 103 | } else if v, ok := d.Value.(*string); ok { 104 | // Handle *string type from V3 API 105 | if v == nil || *v == "" { 106 | return nil, nil 107 | } 108 | layout := "2006-01-02T15:04:05.000-0700" 109 | return time.Parse(layout, *v) 110 | } else if v, ok := d.Value.(string); ok { 111 | // Handle empty strings from V3 API 112 | if v == "" { 113 | return nil, nil 114 | } 115 | layout := "2006-01-02T15:04:05.000-0700" 116 | return time.Parse(layout, v) 117 | } 118 | return nil, nil 119 | } 120 | 121 | // convertJiraDate:: converts jira.Date to time.Time 122 | func convertJiraDate(_ context.Context, d *transform.TransformData) (interface{}, error) { 123 | if d.Value == nil { 124 | return nil, nil 125 | } 126 | switch t := d.Value.(type) { 127 | case jira.Date: 128 | return time.Time(t), nil 129 | case *jira.Date: 130 | return time.Time(*t), nil 131 | case *string: 132 | return time.Parse(time.DateOnly, *t) 133 | case string: 134 | return time.Parse(time.DateOnly, t) 135 | } 136 | return time.Time(d.Value.(jira.Date)), nil 137 | } 138 | 139 | func buildJQLQueryFromQuals(equalQuals plugin.KeyColumnQualMap, tableColumns []*plugin.Column) string { 140 | filters := []string{} 141 | for _, filterQualItem := range tableColumns { 142 | filterQual := equalQuals[filterQualItem.Name] 143 | if filterQual == nil { 144 | continue 145 | } 146 | 147 | // Check only if filter qual map matches with optional column name 148 | if filterQual.Name == filterQualItem.Name { 149 | if filterQual.Quals == nil { 150 | continue 151 | } 152 | 153 | for _, qual := range filterQual.Quals { 154 | if qual.Value != nil { 155 | value := qual.Value 156 | switch filterQualItem.Type { 157 | case proto.ColumnType_STRING: 158 | jqlFieldName := getIssueJQLKey(filterQualItem.Name) 159 | switch qual.Operator { 160 | case "=": 161 | // Special handling for key field - don't quote field name but quote value 162 | if filterQualItem.Name == "key" { 163 | filters = append(filters, fmt.Sprintf("%s = \"%s\"", jqlFieldName, value.GetStringValue())) 164 | } else { 165 | filters = append(filters, fmt.Sprintf("\"%s\" = \"%s\"", jqlFieldName, value.GetStringValue())) 166 | } 167 | case "<>": 168 | // Special handling for key field 169 | if filterQualItem.Name == "key" { 170 | filters = append(filters, fmt.Sprintf("%s != \"%s\"", jqlFieldName, value.GetStringValue())) 171 | } else { 172 | filters = append(filters, fmt.Sprintf("%s != \"%s\"", jqlFieldName, value.GetStringValue())) 173 | } 174 | } 175 | case proto.ColumnType_TIMESTAMP: 176 | switch qual.Operator { 177 | case "=", ">=", ">", "<=", "<": 178 | filters = append(filters, fmt.Sprintf("\"%s\" %s \"%s\"", getIssueJQLKey(filterQualItem.Name), qual.Operator, value.GetTimestampValue().AsTime().Format("2006-01-02 15:04"))) 179 | case "<>": 180 | filters = append(filters, fmt.Sprintf("\"%s\" != \"%s\"", getIssueJQLKey(filterQualItem.Name), value.GetTimestampValue().AsTime().Format("2006-01-02 15:04"))) 181 | } 182 | 183 | } 184 | } 185 | } 186 | 187 | } 188 | } 189 | 190 | if len(filters) > 0 { 191 | return strings.Join(filters, " AND ") 192 | } 193 | 194 | return "" 195 | } 196 | 197 | func getIssueJQLKey(columnName string) string { 198 | remappedColumns := map[string]string{ 199 | "key": "key", 200 | "resolution_date": "resolutiondate", 201 | "status_category": "statuscategory", 202 | } 203 | 204 | if val, ok := remappedColumns[columnName]; ok { 205 | return val 206 | } 207 | return strings.ToLower(strings.Split(columnName, "_")[0]) 208 | } 209 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/turbot/steampipe-plugin-jira/v2/jira" 5 | 6 | "github.com/turbot/steampipe-plugin-sdk/v5/plugin" 7 | ) 8 | 9 | func main() { 10 | plugin.Serve(&plugin.ServeOpts{ 11 | PluginFunc: jira.Plugin}) 12 | } 13 | --------------------------------------------------------------------------------