├── .gitignore ├── .prettierrc ├── .travis.yml ├── LICENSE ├── META-INF ├── MANIFEST.MF └── jsignore.lst ├── README.md ├── documentation ├── access_token.png ├── back_link.png ├── button.png ├── introduction.png ├── linking_artifacts.png ├── links.png ├── main_view.png ├── repository_selection.png ├── rich_hover.png ├── whitelist.png └── wiki │ ├── already-linked.png │ ├── button-disabled.png │ ├── button-enabled.png │ ├── click-select-button.png │ ├── copy-details-button.png │ ├── create-new-issue-warning.png │ ├── create-new-issue.png │ ├── create-work-item-warning.png │ ├── details-view.png │ ├── filter-box.png │ ├── filter-results.png │ ├── finished-creating-work-items.png │ ├── git-commit-comment.png │ ├── git-commit-rich-hover.png │ ├── git-issue-comment.png │ ├── git-issue-rich-hover.png │ ├── git-repository-list.png │ ├── git-request-comment.png │ ├── git-request-rich-hover.png │ ├── gitlab-issue-from-default-template.png │ ├── introduction.png │ ├── issue-to-create.png │ ├── items-to-link.png │ ├── new-work-item-button-enabled.png │ ├── new-work-items-query.png │ ├── no-git-repositories.png │ ├── save-access-token.png │ ├── search-box.png │ ├── search-results.png │ ├── select-a-repository.png │ ├── select-git-issues.png │ ├── set-work-item-attributes-2.png │ ├── set-work-item-attributes.png │ ├── unsaved-new-work-items.png │ ├── view-git-links.png │ ├── work-item-to-create-as-issue.png │ └── work-items-menu.png ├── package-lock.json ├── package.json ├── plugin.xml ├── resources ├── images │ └── icons │ │ └── git-icon.png └── ui │ ├── RunConnector.js │ └── widget │ ├── components │ ├── DefaultIssueTemplate │ │ ├── DefaultIssueTemplate.handlebars │ │ └── DefaultIssueTemplate.js │ ├── DetailsPane │ │ ├── DetailsPane.css │ │ ├── DetailsPane.html │ │ └── DetailsPane.js │ ├── ListItem │ │ ├── ListItem.css │ │ ├── ListItem.html │ │ └── ListItem.js │ ├── MainLayout │ │ ├── MainLayout.html │ │ └── MainLayout.js │ ├── NewWorkItemList │ │ ├── NewWorkItemList.css │ │ ├── NewWorkItemList.html │ │ └── NewWorkItemList.js │ ├── RtcGitConnector │ │ ├── RtcGitConnector.css │ │ ├── RtcGitConnector.html │ │ └── RtcGitConnector.js │ ├── SelectItemMessage │ │ ├── SelectItemMessage.css │ │ ├── SelectItemMessage.html │ │ └── SelectItemMessage.js │ ├── SelectLinkType │ │ ├── SelectLinkType.html │ │ └── SelectLinkType.js │ ├── SelectRegisteredGitRepository │ │ ├── SelectRegisteredGitRepository.html │ │ └── SelectRegisteredGitRepository.js │ ├── SetNewWorkItemAttributes │ │ ├── SetNewWorkItemAttributes.css │ │ ├── SetNewWorkItemAttributes.html │ │ └── SetNewWorkItemAttributes.js │ ├── SetNewWorkItemLinks │ │ ├── SetNewWorkItemLinks.css │ │ ├── SetNewWorkItemLinks.html │ │ └── SetNewWorkItemLinks.js │ ├── SetNewWorkItemValues │ │ ├── SetNewWorkItemValues.css │ │ ├── SetNewWorkItemValues.html │ │ └── SetNewWorkItemValues.js │ ├── ViewAndSelectCommits │ │ ├── ViewAndSelectCommits.html │ │ └── ViewAndSelectCommits.js │ ├── ViewAndSelectIssues │ │ ├── ViewAndSelectIssues.html │ │ └── ViewAndSelectIssues.js │ ├── ViewAndSelectRequests │ │ ├── ViewAndSelectRequests.html │ │ └── ViewAndSelectRequests.js │ ├── ViewCommitsToLink │ │ ├── ViewCommitsToLink.html │ │ └── ViewCommitsToLink.js │ ├── ViewIssuesToLink │ │ ├── ViewIssuesToLink.html │ │ └── ViewIssuesToLink.js │ └── ViewRequestsToLink │ │ ├── ViewRequestsToLink.html │ │ └── ViewRequestsToLink.js │ ├── models │ ├── CommitModel.js │ ├── IssueModel.js │ └── RequestModel.js │ ├── services │ ├── GitRestService.js │ ├── JazzRestService.js │ ├── MainDataStore.js │ └── TemplateService.js │ └── utils │ └── ViewHelper.js ├── src ├── CommitLinkEncoder.js ├── FontAwesomeProvider.js ├── GitLabApiProvider.js └── theme │ ├── customMenuItems.js │ ├── customMenuItemsConfig.js │ └── visibleWorkItemTypeIds.js └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | *.jazzignore 3 | *.log 4 | *.swp 5 | .project 6 | .idea/ 7 | .vscode/ 8 | com.siemens.bt.jazz.workitemeditor.rtcGitConnector*.zip 9 | deployment-properties.ini 10 | jbe-build-wrapper.xml 11 | dist/ 12 | node_modules/ 13 | resources/dist/ 14 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "tabWidth": 4, 4 | "singleQuote": false, 5 | "quoteProps": "preserve", 6 | "trailingComma": "none" 7 | } 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 8 4 | script: 5 | - npm run build 6 | deploy: 7 | provider: releases 8 | api_key: 9 | secure: wAX6SYIx3/sQd1pzPJvNG0rlIfuBKbAQKSuMgLGR1j3+8lBGB4YmNF2BgYEtJf2mopmiBDv4SWpRQ7zZAk2Fs2lokTjHjg0eQqvkAwBgREeWW+q9EK2uMiZPI4Os/cLRiYfVIH9a3ybc4/6VEVKwROkASCT02e1ok7nYEV09KVctqYjt/1on9Cs/PKAtK9H0jAA1QrerK1quIy//5csTJb4AYNtGkpCKHtt5dvEeF5KEA+p3mpNasPtN7xicZ6q2wJg/1KMJR3dhREmbLC9kI9OghYqslU8iSCqdK/YvXuhn+2RrYGvranTa+NqsTJlO1koXWs227zFr31xlI5jqxfWpIxYDN5taaa/79YQLjBDXO5nhBWvO3sJPaurBblzE7YJsQcSo9E830Of+nPWfmrX5+x+HpQx7PtkoCzHR66xi8PxGdW1ILoM3gFLOVMEvDtaoUtBlyDL7J33Ywn3HbKyWDwmMQVu5+ftb2bSNtoQZsvsbcvWRvR0svI3Gac+JvFHZ71I55bILGio7Cu3knZz/EKziW6qh/sJoerUccLuYfvzXkgg5FdN1gwBm8VAPXlN6+F5eak5QwxUlQWcC4FiJvrCtZYvB8I72t28SEpbifqGN1fz2Yz9SBby2VtVXwyw4zAfAO0jUyE55XLR4IY83P3pj5rCeXeJ0Qsu0yl0= 10 | file: com.siemens.bt.jazz.workitemeditor.rtcGitConnector*.zip 11 | file_glob: true 12 | skip_cleanup: true 13 | on: 14 | tags: true 15 | repo: jazz-community/rtc-git-connector 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Siemens AG 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Bundle-ManifestVersion: 2 3 | Bundle-Name: RTC Git Connector 4 | Bundle-SymbolicName: com.siemens.bt.jazz.workitemeditor.rtcGitConnector;singleton:=true 5 | Bundle-Version: 3.0.0 6 | Bundle-Vendor: Siemens Building Technologies 7 | Bundle-Localization: plugin 8 | Require-Bundle: org.jazzcommunity.GitConnectorService;bundle-version="3.0.0", 9 | com.siemens.bt.jazz.services.PersonalTokenService;bundle-version="1.0.3", 10 | com.siemens.jazz.provider.plainjavascript;bundle-version="1.0.0" 11 | -------------------------------------------------------------------------------- /META-INF/jsignore.lst: -------------------------------------------------------------------------------- 1 | /resources/dist/ClipboardJS.js 2 | /resources/dist/CommitLinkEncoder.js 3 | /resources/dist/FontAwesomeProvider.js 4 | /resources/dist/GitHubApi.js 5 | /resources/dist/GitLabApiProvider.js 6 | /resources/dist/Handlebars.js 7 | /resources/dist/JustHandlebarsHelpers.js 8 | /resources/dist/TurndownService.js 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/jazz-community/rtc-git-connector.svg?branch=master)](https://travis-ci.org/jazz-community/rtc-git-connector) 2 | [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fjazz-community%2Frtc-git-connector.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fjazz-community%2Frtc-git-connector?ref=badge_shield) 3 | 4 | ## Introduction 5 | The _RTC Git Connector_ is a plugin for IBM Rational Team Concert (RTC) that enables you to connect RTC work items with Git commits, issues, and pull/merge requests. This plugin helps to bridge the workflow gap between RTC and Git platforms like GitHub and GitLab. 6 | 7 | **Key features** 8 | - Link RTC work items with Git commits, issues and pull/merge requests 9 | - Create Git issues out of RTC 10 | - Create RTC work items from Git issues 11 | - View selected issue and pull/merge request information when hovering the link in RTC 12 | 13 | ![Introduction](https://github.com/jazz-community/rtc-git-connector/blob/master/documentation/wiki/introduction.png) 14 | 15 | ## Installation and Usage 16 | There are detailed instructions on how to install and use the plugin in the [Wiki](https://github.com/jazz-community/rtc-git-connector/wiki) of this repository. 17 | 18 | ## Releases 19 | To download the latest release please navigate to the [releases page](https://github.com/jazz-community/rtc-git-connector/releases). 20 | 21 | ## Contributing 22 | Please use the [Issue Tracker](https://github.com/jazz-community/rtc-git-connector/issues) of this repository to report issues or suggest enhancements. 23 | 24 | For general contribution guidelines, please refer to [CONTRIBUTING.md](https://github.com/jazz-community/welcome/blob/master/CONTRIBUTING.md). 25 | 26 | ## Licensing 27 | Copyright (c) Siemens AG. All rights reserved.
28 | Licensed under the [MIT](https://github.com/jazz-community/rtc-git-connector/blob/master/LICENSE) License. 29 | 30 | 31 | [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fjazz-community%2Frtc-git-connector.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fjazz-community%2Frtc-git-connector?ref=badge_large) -------------------------------------------------------------------------------- /documentation/access_token.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazz-community/rtc-git-connector/8a59c81bfc85c70ef57d4fa83ded54e545faf6c3/documentation/access_token.png -------------------------------------------------------------------------------- /documentation/back_link.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazz-community/rtc-git-connector/8a59c81bfc85c70ef57d4fa83ded54e545faf6c3/documentation/back_link.png -------------------------------------------------------------------------------- /documentation/button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazz-community/rtc-git-connector/8a59c81bfc85c70ef57d4fa83ded54e545faf6c3/documentation/button.png -------------------------------------------------------------------------------- /documentation/introduction.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazz-community/rtc-git-connector/8a59c81bfc85c70ef57d4fa83ded54e545faf6c3/documentation/introduction.png -------------------------------------------------------------------------------- /documentation/linking_artifacts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazz-community/rtc-git-connector/8a59c81bfc85c70ef57d4fa83ded54e545faf6c3/documentation/linking_artifacts.png -------------------------------------------------------------------------------- /documentation/links.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazz-community/rtc-git-connector/8a59c81bfc85c70ef57d4fa83ded54e545faf6c3/documentation/links.png -------------------------------------------------------------------------------- /documentation/main_view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazz-community/rtc-git-connector/8a59c81bfc85c70ef57d4fa83ded54e545faf6c3/documentation/main_view.png -------------------------------------------------------------------------------- /documentation/repository_selection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazz-community/rtc-git-connector/8a59c81bfc85c70ef57d4fa83ded54e545faf6c3/documentation/repository_selection.png -------------------------------------------------------------------------------- /documentation/rich_hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazz-community/rtc-git-connector/8a59c81bfc85c70ef57d4fa83ded54e545faf6c3/documentation/rich_hover.png -------------------------------------------------------------------------------- /documentation/whitelist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazz-community/rtc-git-connector/8a59c81bfc85c70ef57d4fa83ded54e545faf6c3/documentation/whitelist.png -------------------------------------------------------------------------------- /documentation/wiki/already-linked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazz-community/rtc-git-connector/8a59c81bfc85c70ef57d4fa83ded54e545faf6c3/documentation/wiki/already-linked.png -------------------------------------------------------------------------------- /documentation/wiki/button-disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazz-community/rtc-git-connector/8a59c81bfc85c70ef57d4fa83ded54e545faf6c3/documentation/wiki/button-disabled.png -------------------------------------------------------------------------------- /documentation/wiki/button-enabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazz-community/rtc-git-connector/8a59c81bfc85c70ef57d4fa83ded54e545faf6c3/documentation/wiki/button-enabled.png -------------------------------------------------------------------------------- /documentation/wiki/click-select-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazz-community/rtc-git-connector/8a59c81bfc85c70ef57d4fa83ded54e545faf6c3/documentation/wiki/click-select-button.png -------------------------------------------------------------------------------- /documentation/wiki/copy-details-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazz-community/rtc-git-connector/8a59c81bfc85c70ef57d4fa83ded54e545faf6c3/documentation/wiki/copy-details-button.png -------------------------------------------------------------------------------- /documentation/wiki/create-new-issue-warning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazz-community/rtc-git-connector/8a59c81bfc85c70ef57d4fa83ded54e545faf6c3/documentation/wiki/create-new-issue-warning.png -------------------------------------------------------------------------------- /documentation/wiki/create-new-issue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazz-community/rtc-git-connector/8a59c81bfc85c70ef57d4fa83ded54e545faf6c3/documentation/wiki/create-new-issue.png -------------------------------------------------------------------------------- /documentation/wiki/create-work-item-warning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazz-community/rtc-git-connector/8a59c81bfc85c70ef57d4fa83ded54e545faf6c3/documentation/wiki/create-work-item-warning.png -------------------------------------------------------------------------------- /documentation/wiki/details-view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazz-community/rtc-git-connector/8a59c81bfc85c70ef57d4fa83ded54e545faf6c3/documentation/wiki/details-view.png -------------------------------------------------------------------------------- /documentation/wiki/filter-box.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazz-community/rtc-git-connector/8a59c81bfc85c70ef57d4fa83ded54e545faf6c3/documentation/wiki/filter-box.png -------------------------------------------------------------------------------- /documentation/wiki/filter-results.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazz-community/rtc-git-connector/8a59c81bfc85c70ef57d4fa83ded54e545faf6c3/documentation/wiki/filter-results.png -------------------------------------------------------------------------------- /documentation/wiki/finished-creating-work-items.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazz-community/rtc-git-connector/8a59c81bfc85c70ef57d4fa83ded54e545faf6c3/documentation/wiki/finished-creating-work-items.png -------------------------------------------------------------------------------- /documentation/wiki/git-commit-comment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazz-community/rtc-git-connector/8a59c81bfc85c70ef57d4fa83ded54e545faf6c3/documentation/wiki/git-commit-comment.png -------------------------------------------------------------------------------- /documentation/wiki/git-commit-rich-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazz-community/rtc-git-connector/8a59c81bfc85c70ef57d4fa83ded54e545faf6c3/documentation/wiki/git-commit-rich-hover.png -------------------------------------------------------------------------------- /documentation/wiki/git-issue-comment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazz-community/rtc-git-connector/8a59c81bfc85c70ef57d4fa83ded54e545faf6c3/documentation/wiki/git-issue-comment.png -------------------------------------------------------------------------------- /documentation/wiki/git-issue-rich-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazz-community/rtc-git-connector/8a59c81bfc85c70ef57d4fa83ded54e545faf6c3/documentation/wiki/git-issue-rich-hover.png -------------------------------------------------------------------------------- /documentation/wiki/git-repository-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazz-community/rtc-git-connector/8a59c81bfc85c70ef57d4fa83ded54e545faf6c3/documentation/wiki/git-repository-list.png -------------------------------------------------------------------------------- /documentation/wiki/git-request-comment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazz-community/rtc-git-connector/8a59c81bfc85c70ef57d4fa83ded54e545faf6c3/documentation/wiki/git-request-comment.png -------------------------------------------------------------------------------- /documentation/wiki/git-request-rich-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazz-community/rtc-git-connector/8a59c81bfc85c70ef57d4fa83ded54e545faf6c3/documentation/wiki/git-request-rich-hover.png -------------------------------------------------------------------------------- /documentation/wiki/gitlab-issue-from-default-template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazz-community/rtc-git-connector/8a59c81bfc85c70ef57d4fa83ded54e545faf6c3/documentation/wiki/gitlab-issue-from-default-template.png -------------------------------------------------------------------------------- /documentation/wiki/introduction.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazz-community/rtc-git-connector/8a59c81bfc85c70ef57d4fa83ded54e545faf6c3/documentation/wiki/introduction.png -------------------------------------------------------------------------------- /documentation/wiki/issue-to-create.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazz-community/rtc-git-connector/8a59c81bfc85c70ef57d4fa83ded54e545faf6c3/documentation/wiki/issue-to-create.png -------------------------------------------------------------------------------- /documentation/wiki/items-to-link.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazz-community/rtc-git-connector/8a59c81bfc85c70ef57d4fa83ded54e545faf6c3/documentation/wiki/items-to-link.png -------------------------------------------------------------------------------- /documentation/wiki/new-work-item-button-enabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazz-community/rtc-git-connector/8a59c81bfc85c70ef57d4fa83ded54e545faf6c3/documentation/wiki/new-work-item-button-enabled.png -------------------------------------------------------------------------------- /documentation/wiki/new-work-items-query.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazz-community/rtc-git-connector/8a59c81bfc85c70ef57d4fa83ded54e545faf6c3/documentation/wiki/new-work-items-query.png -------------------------------------------------------------------------------- /documentation/wiki/no-git-repositories.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazz-community/rtc-git-connector/8a59c81bfc85c70ef57d4fa83ded54e545faf6c3/documentation/wiki/no-git-repositories.png -------------------------------------------------------------------------------- /documentation/wiki/save-access-token.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazz-community/rtc-git-connector/8a59c81bfc85c70ef57d4fa83ded54e545faf6c3/documentation/wiki/save-access-token.png -------------------------------------------------------------------------------- /documentation/wiki/search-box.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazz-community/rtc-git-connector/8a59c81bfc85c70ef57d4fa83ded54e545faf6c3/documentation/wiki/search-box.png -------------------------------------------------------------------------------- /documentation/wiki/search-results.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazz-community/rtc-git-connector/8a59c81bfc85c70ef57d4fa83ded54e545faf6c3/documentation/wiki/search-results.png -------------------------------------------------------------------------------- /documentation/wiki/select-a-repository.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazz-community/rtc-git-connector/8a59c81bfc85c70ef57d4fa83ded54e545faf6c3/documentation/wiki/select-a-repository.png -------------------------------------------------------------------------------- /documentation/wiki/select-git-issues.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazz-community/rtc-git-connector/8a59c81bfc85c70ef57d4fa83ded54e545faf6c3/documentation/wiki/select-git-issues.png -------------------------------------------------------------------------------- /documentation/wiki/set-work-item-attributes-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazz-community/rtc-git-connector/8a59c81bfc85c70ef57d4fa83ded54e545faf6c3/documentation/wiki/set-work-item-attributes-2.png -------------------------------------------------------------------------------- /documentation/wiki/set-work-item-attributes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazz-community/rtc-git-connector/8a59c81bfc85c70ef57d4fa83ded54e545faf6c3/documentation/wiki/set-work-item-attributes.png -------------------------------------------------------------------------------- /documentation/wiki/unsaved-new-work-items.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazz-community/rtc-git-connector/8a59c81bfc85c70ef57d4fa83ded54e545faf6c3/documentation/wiki/unsaved-new-work-items.png -------------------------------------------------------------------------------- /documentation/wiki/view-git-links.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazz-community/rtc-git-connector/8a59c81bfc85c70ef57d4fa83ded54e545faf6c3/documentation/wiki/view-git-links.png -------------------------------------------------------------------------------- /documentation/wiki/work-item-to-create-as-issue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazz-community/rtc-git-connector/8a59c81bfc85c70ef57d4fa83ded54e545faf6c3/documentation/wiki/work-item-to-create-as-issue.png -------------------------------------------------------------------------------- /documentation/wiki/work-items-menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazz-community/rtc-git-connector/8a59c81bfc85c70ef57d4fa83ded54e545faf6c3/documentation/wiki/work-items-menu.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rtc-git-connector", 3 | "version": "3.0.0", 4 | "description": "Connect RTC work items with various Git hosting systems", 5 | "main": "./resources/ui/RunConnector.js", 6 | "dependencies": { 7 | "@babel/runtime": "^7.23.9", 8 | "@fortawesome/fontawesome": "^1.1.8", 9 | "@fortawesome/fontawesome-free-solid": "^5.0.13", 10 | "@gitbeaker/rest": "^39.34.1", 11 | "@octokit/rest": "^20.0.2", 12 | "base64-js": "^1.5.1", 13 | "buffer": "^6.0.3", 14 | "clipboard": "^2.0.11", 15 | "handlebars": "^4.7.8", 16 | "just-handlebars-helpers": "^1.0.19", 17 | "pako": "^2.1.0", 18 | "turndown": "^7.1.2" 19 | }, 20 | "devDependencies": { 21 | "@babel/core": "^7.23.7", 22 | "@babel/plugin-transform-runtime": "^7.23.9", 23 | "@babel/preset-env": "^7.23.9", 24 | "@jazz-community-org/pack-jazz-web-plugin": "^2.0.1", 25 | "babel-loader": "^9.1.3", 26 | "moment": "^2.30.1", 27 | "prettier": "^3.2.4", 28 | "remove-files-webpack-plugin": "^1.5.0", 29 | "string-replace-loader": "^3.1.0", 30 | "webpack": "^5.89.0", 31 | "webpack-cli": "^5.1.4", 32 | "zip-webpack-plugin": "^4.0.1" 33 | }, 34 | "scripts": { 35 | "build": "webpack --config ./webpack.config.js --mode production", 36 | "pack": "pack-jazz-web-plugin", 37 | "both": "npm run build && npm run pack" 38 | }, 39 | "repository": { 40 | "type": "git", 41 | "url": "git+https://github.com/jazz-community/rtc-git-connector.git" 42 | }, 43 | "keywords": [ 44 | "Jazz", 45 | "Git", 46 | "GitLab", 47 | "GitHub" 48 | ], 49 | "author": "Martin Benninger, Siemens AG", 50 | "license": "MIT", 51 | "bugs": { 52 | "url": "https://github.com/jazz-community/rtc-git-connector/issues" 53 | }, 54 | "homepage": "https://github.com/jazz-community/rtc-git-connector#readme", 55 | "packJazzWebPlugin": { 56 | "pluginId": "com.siemens.bt.jazz.workitemeditor.rtcGitConnector" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 10 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /resources/images/icons/git-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazz-community/rtc-git-connector/8a59c81bfc85c70ef57d4fa83ded54e545faf6c3/resources/images/icons/git-icon.png -------------------------------------------------------------------------------- /resources/ui/RunConnector.js: -------------------------------------------------------------------------------- 1 | define(["dojo/_base/declare", "./widget/components/RtcGitConnector/RtcGitConnector"], function ( 2 | declare, 3 | RtcGitConnector 4 | ) { 5 | return declare( 6 | "com.siemens.bt.jazz.workitemeditor.rtcGitConnector.ui.RunConnector", 7 | com.ibm.team.workitem.web.ui2.internal.action.AbstractAction, 8 | { 9 | // summary: 10 | // Default constructor called when the WorkItemEditor is instantiated. 11 | // For in-depth information on the creation procedure, see 12 | // WorkItemEditorHeader.js, where all actions are created. 13 | // 14 | // params: {actionSpec, workingCopy} 15 | // actionSpec corresponds to the action values defined in plugin.xml. 16 | // workingCopy is a reference to the current work item being edited: 17 | // { 18 | // actionSpec: { 19 | // action, 20 | // iconContext, 21 | // iconUri, 22 | // id, 23 | // label, 24 | // parameter 25 | // }, 26 | // workingCopy: {} 27 | // } 28 | constructor: function (params) { 29 | this.autoOpen(); 30 | }, 31 | 32 | // Disable the widget button if the work item has unsaved changes 33 | isEnabled: function (params) { 34 | var workingCopy = params.workingCopy || params; 35 | return !workingCopy.isChanged(); 36 | }, 37 | 38 | // summary: 39 | // Is run when the action button in the WorkItemEditor view is clicked. 40 | // params: {actionSpec, workingCopy} 41 | // Same as the params passed to the constructor. 42 | run: function () { 43 | var rtcGitConnector = new RtcGitConnector({ 44 | workItem: this.workingCopy 45 | }); 46 | rtcGitConnector.startup(); 47 | }, 48 | 49 | // Open the widget if the auto open parameter is set and the modules bundle is loaded. 50 | autoOpen: function () { 51 | if (window.location.hash.indexOf("&autoOpenRtcGitConnector=true") > -1) { 52 | window.history.replaceState( 53 | undefined, 54 | undefined, 55 | window.location.hash.replace("&autoOpenRtcGitConnector=true", "") 56 | ); 57 | this.run(); 58 | } 59 | } 60 | } 61 | ); 62 | }); 63 | -------------------------------------------------------------------------------- /resources/ui/widget/components/DefaultIssueTemplate/DefaultIssueTemplate.handlebars: -------------------------------------------------------------------------------- 1 | ### Description\n 2 | \n 3 | {{#if attributes.description.content}} 4 | {{{turndown attributes.description.content}}}\n 5 | {{else}} 6 | Please add a description\n 7 | {{/if}} 8 | \n\n 9 | {{#if attributes.[com.ibm.team.apt.attribute.acceptance]}} 10 | ### Acceptance Criteria\n 11 | \n 12 | {{#if attributes.[com.ibm.team.apt.attribute.acceptance].content}} 13 | {{{turndown attributes.[com.ibm.team.apt.attribute.acceptance].content}}}\n 14 | {{else}} 15 | Please add some acceptance criteria\n 16 | {{/if}} 17 | \n\n 18 | {{/if}} 19 | ### Other Attributes\n 20 | \n 21 | | Attributes | Values |\n 22 | | --- | --- |\n 23 | | Owned By | [{{{attributes.owner.name}}}](mailto:{{{attributes.owner.emailAddress}}}) |\n 24 | | Filed Against | {{{attributes.category.label}}} |\n 25 | | Planned For | {{{attributes.target.label}}} |\n 26 | {{#if attributes.internalTags.content}} 27 | | Tags | {{{attributes.internalTags.content}}} |\n 28 | {{/if}} -------------------------------------------------------------------------------- /resources/ui/widget/components/DefaultIssueTemplate/DefaultIssueTemplate.js: -------------------------------------------------------------------------------- 1 | define(["dojo/_base/declare", "dojo/text!./DefaultIssueTemplate.handlebars"], function (declare, template) { 2 | return declare(null, { 3 | templateString: template, 4 | 5 | getTemplateString: function () { 6 | return this.templateString; 7 | } 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /resources/ui/widget/components/DetailsPane/DetailsPane.css: -------------------------------------------------------------------------------- 1 | .rtcGitConnectorViewAndSelectDetails { 2 | box-sizing: border-box; 3 | flex-grow: 1; 4 | width: calc(100% - 20px); 5 | height: 300px; 6 | padding: 4px 10px; 7 | margin-left: 10px; 8 | border: 1px solid #cacaca; 9 | overflow-y: auto; 10 | overflow-x: hidden; 11 | } 12 | 13 | .rtcGitConnectorViewAndSelectDetailsSpan { 14 | display: block; 15 | font-size: 1em; 16 | padding-top: 0.5em; 17 | padding-bottom: 0.5em; 18 | border-bottom: 1px dashed #cacaca; 19 | } 20 | 21 | .rtcGitConnectorViewAndSelectDetailsSpan span { 22 | display: inline-block; 23 | } 24 | 25 | .rtcGitConnectorViewAndSelectDetailsLabel { 26 | color: #666666; 27 | } 28 | 29 | .rtcGitConnectorViewAndSelectDetailsSpan.rtcGitConnectorViewAndSelectDetailsLabel { 30 | font-weight: bold; 31 | } 32 | 33 | .rtcGitConnectorViewAndSelectDetailsSpan .rtcGitConnectorViewAndSelectDetailsLabel { 34 | width: 100px; 35 | vertical-align: top; 36 | } 37 | -------------------------------------------------------------------------------- /resources/ui/widget/components/DetailsPane/DetailsPane.html: -------------------------------------------------------------------------------- 1 |
2 | -------------------------------------------------------------------------------- /resources/ui/widget/components/DetailsPane/DetailsPane.js: -------------------------------------------------------------------------------- 1 | define([ 2 | "dojo/_base/declare", 3 | "dojo/_base/array", 4 | "dojo/dom-construct", 5 | "dijit/_WidgetBase", 6 | "dijit/_TemplatedMixin", 7 | "dojo/text!./DetailsPane.html", 8 | "jazz/css!./DetailsPane.css" 9 | ], function (declare, array, domConstruct, _WidgetBase, _TemplatedMixin, template) { 10 | return declare( 11 | "com.siemens.bt.jazz.workitemeditor.rtcGitConnector.ui.widget.detailsPane", 12 | [_WidgetBase, _TemplatedMixin], 13 | { 14 | templateString: template, 15 | 16 | /** 17 | * Clear the pane and set it to new content. 18 | * @param {String} title The title of the details pane. 19 | * @param {Object[]} items The list of items to be displayed as rows. 20 | * @param {String} items[].label An optional label for the row. 21 | * @param {String} items[].text The text to display in the row. 22 | * @param {String} items[].link An optional link to attach to the text. 23 | * @param {Object} items[].node An optional dom node. Used instead of text when present. 24 | */ 25 | setContent: function (title, items) { 26 | this._clearContent(); 27 | this._addTitleNode(title); 28 | this._addDetailsRows(items); 29 | }, 30 | 31 | _clearContent: function () { 32 | domConstruct.empty(this.domNode); 33 | }, 34 | 35 | _addTitleNode: function (title) { 36 | domConstruct.create( 37 | "span", 38 | { 39 | "class": "rtcGitConnectorViewAndSelectDetailsSpan rtcGitConnectorViewAndSelectDetailsLabel", 40 | innerHTML: title 41 | }, 42 | this.domNode 43 | ); 44 | }, 45 | 46 | _addDetailsRows: function (items) { 47 | var self = this; 48 | 49 | if (items && items.length) { 50 | array.forEach(items, function (item) { 51 | if (item && (item.text || item.node)) { 52 | self._addItemAsRow(item); 53 | } 54 | }); 55 | } 56 | }, 57 | 58 | _addItemAsRow: function (item) { 59 | var detailsRowSpan = domConstruct.create( 60 | "span", 61 | { 62 | "class": "rtcGitConnectorViewAndSelectDetailsSpan" 63 | }, 64 | this.domNode 65 | ); 66 | 67 | if (item.label) { 68 | domConstruct.create( 69 | "span", 70 | { 71 | "class": "rtcGitConnectorViewAndSelectDetailsLabel", 72 | innerHTML: item.label 73 | }, 74 | detailsRowSpan 75 | ); 76 | } 77 | 78 | if (item.node) { 79 | // Add the node 80 | domConstruct.place(item.node, detailsRowSpan); 81 | } else if (item.link) { 82 | // Create a link 83 | domConstruct.create( 84 | "a", 85 | { 86 | innerHTML: item.text, 87 | href: item.link, 88 | target: "_blank" 89 | }, 90 | detailsRowSpan 91 | ); 92 | } else { 93 | // Just text 94 | domConstruct.create( 95 | "span", 96 | { 97 | innerHTML: item.text 98 | }, 99 | detailsRowSpan 100 | ); 101 | } 102 | } 103 | } 104 | ); 105 | }); 106 | -------------------------------------------------------------------------------- /resources/ui/widget/components/ListItem/ListItem.css: -------------------------------------------------------------------------------- 1 | .rtcGitConnectorViewAndSelectListItem { 2 | display: flex; 3 | border-bottom: 1px solid #cacaca; 4 | padding: 0.2em; 5 | font-size: 1em; 6 | transition: all 0.3s ease-out; 7 | } 8 | 9 | .rtcGitConnectorViewAndSelectListItem:hover { 10 | background-color: #cacaca; 11 | } 12 | 13 | .rtcGitConnectorViewAndSelectListItem.selected { 14 | background-color: #cfe5fa; 15 | } 16 | 17 | .rtcGitConnectorViewAndSelectListItemContent { 18 | width: 100%; 19 | height: 100%; 20 | cursor: pointer; 21 | } 22 | 23 | .rtcGitConnectorViewAndSelectListItem.selected .rtcGitConnectorViewAndSelectListItemContent { 24 | cursor: default; 25 | } 26 | 27 | .rtcGitConnectorViewAndSelectListItemContent.notClickable { 28 | cursor: default; 29 | } 30 | 31 | .rtcGitConnectorViewAndSelectListItemButton { 32 | width: 2em; 33 | height: 1em; 34 | margin: auto 0.3em; 35 | font-size: 1.5em; 36 | text-align: center; 37 | cursor: pointer; 38 | transition: transform 200ms; 39 | } 40 | 41 | /* Make the list item button grow and spin once when hovering over the list item. */ 42 | .rtcGitConnectorViewAndSelectListItem:hover .rtcGitConnectorViewAndSelectListItemButton { 43 | transform: scale(1.5); 44 | } 45 | 46 | /* Make the list item button spin once again when the list item is selected (attract attention). */ 47 | .rtcGitConnectorViewAndSelectListItem.selected .rtcGitConnectorViewAndSelectListItemButton { 48 | transform: scale(1.5); 49 | } 50 | 51 | .rtcGitConnectorViewAndSelectListItem.linkButton .rtcGitConnectorViewAndSelectListItemButton { 52 | color: #0645ad; 53 | } 54 | 55 | .rtcGitConnectorViewAndSelectListItem.plusButton .rtcGitConnectorViewAndSelectListItemButton { 56 | color: green; 57 | } 58 | 59 | .rtcGitConnectorViewAndSelectListItem.trashButton .rtcGitConnectorViewAndSelectListItemButton { 60 | color: darkorange; 61 | } 62 | 63 | .rtcGitConnectorViewAndSelectListItem.timesButton .rtcGitConnectorViewAndSelectListItemButton { 64 | color: #cc2200; 65 | } 66 | 67 | .rtcGitConnectorViewAndSelectListItem.checkButton { 68 | opacity: 0.6; 69 | } 70 | 71 | /* Make the "checkButton" have a normal cursor and no animations (disabled). */ 72 | .rtcGitConnectorViewAndSelectListItem.checkButton .rtcGitConnectorViewAndSelectListItemButton { 73 | color: #555; 74 | cursor: default; 75 | transform: none; 76 | } 77 | 78 | .rtcGitConnectorViewAndSelectListItem .rtcGitConnectorViewAndSelectListItemButton.exclamationTriangle { 79 | color: darkorange; 80 | cursor: help; 81 | transform: none; 82 | } 83 | -------------------------------------------------------------------------------- /resources/ui/widget/components/ListItem/ListItem.html: -------------------------------------------------------------------------------- 1 |
2 |
7 |
12 | 16 | 20 |
21 |
22 |
23 | -------------------------------------------------------------------------------- /resources/ui/widget/components/ListItem/ListItem.js: -------------------------------------------------------------------------------- 1 | define([ 2 | "dojo/_base/declare", 3 | "dojo/dom-class", 4 | "dojo/dom-style", 5 | "com.siemens.bt.jazz.workitemeditor.rtcGitConnector|dist|FontAwesomeProvider.js", 6 | "dijit/_WidgetBase", 7 | "dijit/_TemplatedMixin", 8 | "dojo/text!./ListItem.html", 9 | "jazz/css!./ListItem.css" 10 | ], function (declare, domClass, domStyle, FontAwesomeProvider, _WidgetBase, _TemplatedMixin, template) { 11 | return declare( 12 | "com.siemens.bt.jazz.workitemeditor.rtcGitConnector.ui.widget.listItem", 13 | [_WidgetBase, _TemplatedMixin], 14 | { 15 | templateString: template, 16 | baseClass: "rtcGitConnectorViewAndSelectListItem", 17 | 18 | itemId: "", 19 | _setItemIdAttr: { 20 | node: "listItem", 21 | type: "attribute", 22 | attribute: "data-item-id" 23 | }, 24 | 25 | title: "", 26 | _setTitleAttr: { node: "firstLine", type: "innerHTML" }, 27 | 28 | details: "", 29 | _setDetailsAttr: { node: "secondLine", type: "innerHTML" }, 30 | 31 | buttonTitle: "", 32 | _setButtonTitleAttr: { 33 | node: "itemButton", 34 | type: "attribute", 35 | attribute: "title" 36 | }, 37 | 38 | buttonType: "link", 39 | _setButtonTypeAttr: function (buttonName) { 40 | domClass.remove(this.listItem, this.buttonType + "Button"); 41 | domClass.add(this.listItem, buttonName + "Button"); 42 | this.itemButton.innerHTML = FontAwesomeProvider.FontAwesome.icon({ 43 | prefix: "fas", 44 | iconName: buttonName 45 | }).html[0]; 46 | this._set("buttonType", buttonName); 47 | }, 48 | 49 | selected: false, 50 | _setSelectedAttr: function (selected) { 51 | if (selected) { 52 | domClass.add(this.listItem, "selected"); 53 | } else { 54 | domClass.remove(this.listItem, "selected"); 55 | } 56 | 57 | this._set("selected", selected); 58 | }, 59 | 60 | notClickable: false, 61 | _setNotClickableAttr: function (notClickable) { 62 | if (notClickable) { 63 | domClass.add(this.itemContent, "notClickable"); 64 | } else { 65 | domClass.remove(this.itemContent, "notClickable"); 66 | } 67 | 68 | this._set("notClickable", notClickable); 69 | }, 70 | 71 | duplicate: false, 72 | _setDuplicateAttr: function (duplicate) { 73 | if (duplicate) { 74 | this.itemRightButton.innerHTML = FontAwesomeProvider.FontAwesome.icon({ 75 | prefix: "fas", 76 | iconName: "exclamation-triangle" 77 | }).html[0]; 78 | domStyle.set(this.itemRightButton, "display", "block"); 79 | } else { 80 | this.itemRightButton.innerHtml = ""; 81 | domStyle.set(this.itemRightButton, "display", "none"); 82 | } 83 | 84 | this._set("duplicate", duplicate); 85 | }, 86 | 87 | _onButtonClick: function (e) { 88 | this.onButtonClick(this.itemId); 89 | }, 90 | 91 | onButtonClick: function (itemId) { 92 | // The container widget can set this function to react to the button click event. 93 | }, 94 | 95 | _onContentClick: function (e) { 96 | this.onContentClick(this.itemId); 97 | }, 98 | 99 | onContentClick: function (itemId) { 100 | // The container widget can set this function to react to the content click event. 101 | }, 102 | 103 | constructor: function (itemId) { 104 | this.itemId = itemId; 105 | } 106 | } 107 | ); 108 | }); 109 | -------------------------------------------------------------------------------- /resources/ui/widget/components/MainLayout/MainLayout.html: -------------------------------------------------------------------------------- 1 |
2 |
6 |
10 | 11 | 21 | 22 | 25 | 26 | 34 | 35 | 43 | 44 | 53 | 54 |
55 | 56 |
57 | 58 | 61 | 62 |
63 | 64 | 72 | 73 |
79 | 89 | 101 |
102 | 110 |

111 | 119 | 122 |
123 |
124 | -------------------------------------------------------------------------------- /resources/ui/widget/components/NewWorkItemList/NewWorkItemList.css: -------------------------------------------------------------------------------- 1 | .rtcGitConnectorNewWorkItemList { 2 | background-color: white; 3 | border: 1px solid #cacaca; 4 | padding: 10px; 5 | margin: 7px 10px; 6 | } 7 | 8 | .rtcGitConnectorNewWorkItemListTitle { 9 | margin-bottom: 0.5em; 10 | font-size: 1em; 11 | font-weight: bold; 12 | color: #666666; 13 | } 14 | 15 | .rtcGitConnectorNewWorkItemListFlex { 16 | display: flex; 17 | } 18 | 19 | .rtcGitConnectorNewWorkItemListRow { 20 | display: block; 21 | padding-top: 2px; 22 | padding-bottom: 2px; 23 | flex-grow: 1; 24 | } 25 | 26 | .rtcGitConnectorNewWorkItemListRow:hover { 27 | background-color: #fedda8; 28 | } 29 | 30 | .rtcGitConnectorNewWorkItemListRow > img { 31 | float: left; 32 | margin-top: 1px; 33 | margin-left: 20px; 34 | margin-right: 6px; 35 | } 36 | 37 | .rtcGitConnectorNewWorkItemListRow > span { 38 | display: block; 39 | padding-top: 2px; 40 | padding-bottom: 2px; 41 | } 42 | 43 | span.rtcGitConnectorNewWorkItemListRowBullet { 44 | position: absolute; 45 | width: 16px; 46 | height: 16px; 47 | text-align: center; 48 | padding: 0; 49 | } 50 | 51 | span.rtcGitConnectorNewWorkItemListRowBullet > span { 52 | color: black; 53 | font-size: 24px; 54 | position: relative; 55 | top: 1px; 56 | line-height: 16px; 57 | } 58 | 59 | img.rtcGitConnectorNewWorkItemQueryIcon { 60 | padding-top: 2px; 61 | padding-bottom: 2px; 62 | } 63 | 64 | /* Copy the styles from secondary-button because they are not available on the welcome page */ 65 | .rtcGitConnectorNewWorkItemListButton { 66 | flex-grow: 0; 67 | 68 | color: #333; 69 | background-color: #fbfbfb; 70 | border-color: #ccc !important; 71 | 72 | padding: 6px 12px; 73 | margin: 0; 74 | border: 1px solid; 75 | cursor: pointer; 76 | line-height: 12px; 77 | border-radius: 3px; 78 | box-sizing: border-box; 79 | transition: opacity 150ms ease-out; 80 | } 81 | -------------------------------------------------------------------------------- /resources/ui/widget/components/NewWorkItemList/NewWorkItemList.html: -------------------------------------------------------------------------------- 1 |
2 |
Please review and save the following new work items:
3 |
4 |
5 | -------------------------------------------------------------------------------- /resources/ui/widget/components/NewWorkItemList/NewWorkItemList.js: -------------------------------------------------------------------------------- 1 | define([ 2 | "dojo/_base/declare", 3 | "dojo/_base/array", 4 | "dojo/dom-construct", 5 | "dojo/on", 6 | "dijit/_WidgetBase", 7 | "dijit/_TemplatedMixin", 8 | "dijit/registry", 9 | "dojo/text!./NewWorkItemList.html", 10 | "jazz/css!./NewWorkItemList.css", 11 | "com.ibm.team.workitem.web.cache.internal.Cache", 12 | "com.ibm.team.workitem.web.cache.internal.QueryProxy", 13 | "com.ibm.team.workitem.web.client.internal.query.AttributeExpression", 14 | "com.ibm.team.workitem.web.client.internal.query.Operator", 15 | "com.ibm.team.workitem.web.client.internal.query.QueryDescriptor", 16 | "com.ibm.team.workitem.web.client.internal.query.Term", 17 | "com.ibm.team.workitem.web.client.internal.query.UIItem", 18 | "com.ibm.team.workitem.web.client.internal.query.Variable", 19 | "com.ibm.team.workitem.web.client.util" 20 | ], function (declare, array, domConstruct, on, _WidgetBase, _TemplatedMixin, registry, template) { 21 | var Cache = com.ibm.team.workitem.web.cache.internal.Cache; 22 | var QueryProxy = com.ibm.team.workitem.web.cache.internal.QueryProxy; 23 | var AttributeExpression = com.ibm.team.workitem.web.client.internal.query.AttributeExpression; 24 | var Operator = com.ibm.team.workitem.web.client.internal.query.Operator; 25 | var QueryDescriptor = com.ibm.team.workitem.web.client.internal.query.QueryDescriptor; 26 | var Term = com.ibm.team.workitem.web.client.internal.query.Term; 27 | var UIItem = com.ibm.team.workitem.web.client.internal.query.UIItem; 28 | var Variable = com.ibm.team.workitem.web.client.internal.query.Variable; 29 | var util = com.ibm.team.workitem.web.client.util; 30 | 31 | var NewWorkItemList = declare( 32 | "com.siemens.bt.jazz.workitemeditor.rtcGitConnector.ui.widget.newWorkItemList", 33 | [_WidgetBase, _TemplatedMixin], 34 | { 35 | templateString: template, 36 | id: "rtcGitConnectorNewWorkItemListWidget", 37 | newWorkItems: [], 38 | subscriptionHandles: [], 39 | 40 | // Add the widget above the work item editor on the work item editor page. 41 | addToPage: function () { 42 | try { 43 | jazz.app.currentApplication.workbench._pageWidgetCache[ 44 | "com.ibm.team.workitem" 45 | ].domNode.lastElementChild.insertAdjacentElement("beforebegin", this.domNode); 46 | } catch (e) { 47 | console.log("Error placing NewWorkItemList on the page."); 48 | } 49 | }, 50 | 51 | // Update from the current list of new work items. 52 | // If there are no more items in the list the specified handles 53 | // are unsubscribed and the widget is destroyed. 54 | updateContent: function (subscriptionHandles) { 55 | this._updateSubscriptions(subscriptionHandles); 56 | this._getNewWorkItems(); 57 | 58 | if (this.newWorkItems.length) { 59 | // Set the updated list in the view 60 | this._clearContent(); 61 | this._addNewWorkItemsToView(); 62 | } else { 63 | // Unsubscribe from all handles before destroying 64 | this._updateSubscriptions(); 65 | 66 | // Add success message with query for new work items created from git issues 67 | this._createSuccessMessage(); 68 | 69 | // Destroy the widget when when the list is empty 70 | this.destroyRecursive(false); 71 | } 72 | }, 73 | 74 | // Get all new work items from the work item cache 75 | _getNewWorkItems: function () { 76 | this.newWorkItems = Cache.getAllItems({ 77 | filterSelectors: ["isNew"], 78 | filterAttributes: ["isWorkItem"] 79 | }); 80 | }, 81 | 82 | // Only keep the current subscriptions. Unsubscribe any old ones. 83 | // Unsubscribes all handles if the new list of handles is empty or not present. 84 | _updateSubscriptions: function (newSubscriptionHandles) { 85 | if (!newSubscriptionHandles) { 86 | newSubscriptionHandles = []; 87 | } 88 | 89 | if (this.subscriptionHandles.length) { 90 | this.subscriptionHandles.forEach(function (oldSubscriptionHandle) { 91 | if ( 92 | !newSubscriptionHandles.some(function (newSubscriptionHandle) { 93 | return newSubscriptionHandle === oldSubscriptionHandle; 94 | }) 95 | ) { 96 | dojo.unsubscribe(oldSubscriptionHandle); 97 | } 98 | }); 99 | } 100 | 101 | this.subscriptionHandles = newSubscriptionHandles; 102 | }, 103 | 104 | // Empty the links container so that in can be recreated 105 | _clearContent: function () { 106 | domConstruct.empty(this.linksContainer); 107 | }, 108 | 109 | // Add all new work items to the view in the form of links 110 | _addNewWorkItemsToView: function () { 111 | var self = this; 112 | 113 | if (this.newWorkItems && this.newWorkItems.length) { 114 | array.forEach(this.newWorkItems, function (newWorkItem) { 115 | self._addNewWorkItemToView(newWorkItem); 116 | }); 117 | } 118 | }, 119 | 120 | // Add a link to a single work item to the view 121 | _addNewWorkItemToView: function (workItem) { 122 | var workItemUrl = workItem.getUrl(true); 123 | var workItemSummary = workItem.object.attributes.summary; 124 | 125 | if (typeof workItemSummary.content === "string") { 126 | workItemSummary = workItemSummary.content; 127 | } 128 | 129 | var newWorkItemRow = domConstruct.create( 130 | "a", 131 | { 132 | "class": "rtcGitConnectorNewWorkItemListRow", 133 | href: workItemUrl 134 | }, 135 | this.linksContainer 136 | ); 137 | 138 | if (window.location.href === workItemUrl) { 139 | var bulletSpan = domConstruct.create( 140 | "span", 141 | { 142 | "class": "rtcGitConnectorNewWorkItemListRowBullet" 143 | }, 144 | newWorkItemRow 145 | ); 146 | domConstruct.create( 147 | "span", 148 | { 149 | innerHTML: "•" 150 | }, 151 | bulletSpan 152 | ); 153 | } 154 | 155 | domConstruct.create( 156 | "img", 157 | { 158 | src: workItem.getTypeIconUrl(), 159 | alt: workItem.object.attributes.workItemType.label 160 | }, 161 | newWorkItemRow 162 | ); 163 | domConstruct.create( 164 | "span", 165 | { 166 | innerHTML: " * [New " + workItem.object.attributes.workItemType.label + "] " + workItemSummary 167 | }, 168 | newWorkItemRow 169 | ); 170 | }, 171 | 172 | // Add success message with query for new work items created from git issues 173 | _createSuccessMessage: function () { 174 | var newWorkItemsQuery = this._createNewWorkItemsQuery(); 175 | var successMessageDiv = domConstruct.create("div", { 176 | id: "rtcGitConnectorNewWorkItemListSuccessMessageDiv", 177 | "class": "rtcGitConnectorNewWorkItemList" 178 | }); 179 | domConstruct.create( 180 | "div", 181 | { 182 | "class": "rtcGitConnectorNewWorkItemListTitle", 183 | innerHTML: "Finished creating work items from git issues" 184 | }, 185 | successMessageDiv 186 | ); 187 | var flexContainer = domConstruct.create( 188 | "div", 189 | { 190 | "class": "rtcGitConnectorNewWorkItemListFlex" 191 | }, 192 | successMessageDiv 193 | ); 194 | var queryRow = domConstruct.create( 195 | "a", 196 | { 197 | "class": "rtcGitConnectorNewWorkItemListRow", 198 | href: this._getQueryNewWorkItemsUrl(newWorkItemsQuery) 199 | }, 200 | flexContainer 201 | ); 202 | domConstruct.create( 203 | "img", 204 | { 205 | "class": "rtcGitConnectorNewWorkItemQueryIcon", 206 | src: this._getQueryIconUrl(newWorkItemsQuery), 207 | alt: "Query for all new work items created from git issues" 208 | }, 209 | queryRow 210 | ); 211 | domConstruct.create( 212 | "span", 213 | { 214 | innerHTML: "Click here to view all new work items created from git issues" 215 | }, 216 | queryRow 217 | ); 218 | var removeButton = domConstruct.create( 219 | "button", 220 | { 221 | "class": "rtcGitConnectorNewWorkItemListButton", 222 | type: "button", 223 | innerHTML: "Hide" 224 | }, 225 | flexContainer 226 | ); 227 | 228 | on(queryRow, "click", this._removeSuccessMessageFromPage); 229 | on(removeButton, "click", this._removeSuccessMessageFromPage); 230 | 231 | this.domNode.insertAdjacentElement("beforebegin", successMessageDiv); 232 | }, 233 | 234 | // Remove the success message div from the page 235 | _removeSuccessMessageFromPage: function () { 236 | domConstruct.destroy("rtcGitConnectorNewWorkItemListSuccessMessageDiv"); 237 | }, 238 | 239 | // Get the url of the query for all new work items created from git issues 240 | _getQueryNewWorkItemsUrl: function (query) { 241 | return window.location.pathname + util.getRunNewQueryUri(query); 242 | }, 243 | 244 | // Get the url of the query icon 245 | _getQueryIconUrl: function (query) { 246 | var queryProxy = new QueryProxy({ 247 | query: query 248 | }); 249 | 250 | return queryProxy.getMenuIconUri(); 251 | }, 252 | 253 | // Create a query for all work items with the tag "from-git-issue" and 254 | // created today and created by the current user. 255 | _createNewWorkItemsQuery: function () { 256 | // Create a query term with the "AND" operator 257 | var term = new Term(Operator.AND); 258 | 259 | // Create an attribute expression that matches the tag "from-git-issue" 260 | var tagsExpression = new AttributeExpression("internalTags", new Operator("is", null), [ 261 | new UIItem("from-git-issue") 262 | ]); 263 | term.addAttributeExpression(tagsExpression); 264 | 265 | // Create an attribute expression that matches the creationDate and the value of "today" 266 | var creationExpression = new AttributeExpression("creationDate", new Operator("is", null)); 267 | var creationVariable = new Variable("now", "0d"); 268 | creationExpression.setVariables([creationVariable]); 269 | term.addAttributeExpression(creationExpression); 270 | 271 | // Create an attribute expression that matches the creator and the value "currentUser" 272 | var creatorExpression = new AttributeExpression("creator", new Operator("is", null)); 273 | var creatorVariable = new Variable("currentUser", ""); 274 | creatorExpression.setVariables([creatorVariable]); 275 | term.addAttributeExpression(creatorExpression); 276 | 277 | // Create a query object with a title for the UI and the created query terms 278 | var queryDto = { 279 | name: "My new work items from git issues (created today)", 280 | itemId: "", 281 | expression: term.createDTO() 282 | }; 283 | var query = QueryDescriptor.createFromEditableQueryDTO(queryDto); 284 | 285 | return query; 286 | } 287 | } 288 | ); 289 | 290 | // Don't expose the class directly but rather just the update function. 291 | return new (function () { 292 | // Updates the list of new work items. Creates and places the widget in the dom 293 | // if it doesn't exist. Removes and destroys the widget if the list is empty. 294 | // Also unsubscribes from the specified handles when destroying the widget. 295 | this.UpdateNewWorkItemList = function (subscriptionHandles) { 296 | // Get the existing widget by id 297 | var newWorkItemListWidget = registry.byId("rtcGitConnectorNewWorkItemListWidget"); 298 | 299 | if (!newWorkItemListWidget) { 300 | // Create a new instance if the widget wasn't found 301 | newWorkItemListWidget = new NewWorkItemList(); 302 | 303 | // Remove the success message if it exists before adding the new widget to the page 304 | newWorkItemListWidget._removeSuccessMessageFromPage(); 305 | 306 | // Add the new widget to the page 307 | newWorkItemListWidget.addToPage(); 308 | } 309 | 310 | // Create the list of new work items and add it to the view 311 | newWorkItemListWidget.updateContent(subscriptionHandles); 312 | }; 313 | })(); 314 | }); 315 | -------------------------------------------------------------------------------- /resources/ui/widget/components/RtcGitConnector/RtcGitConnector.css: -------------------------------------------------------------------------------- 1 | /* 2 | Don't depend on the styles being within this container style (or any container really). 3 | Dojo can and does place the elements at different places in the dom from where they were defined. 4 | 5 | If you try something like this: 6 | 7 | .rtcGitConnectorMainLayout .span { 8 | some style rule... 9 | } 10 | 11 | You would expect all spans under the element with the "rtcGitConnectorMainLayout" class 12 | to have the specified style rules. Because the spans may have been moved by dojo this might 13 | not be the case (e.g. Dialog, Select, ...). 14 | 15 | Just use long descriptive names and apply the classes to the elements directly. 16 | */ 17 | 18 | /* 19 | Set the height and width of the widget in percentage of the window height / width. 20 | */ 21 | #connectWithGitMainDialog { 22 | background-color: #f5f5f5; 23 | } 24 | 25 | .rtcGitConnectorMainLayout { 26 | box-sizing: border-box; 27 | width: 80vw; 28 | min-width: 800px; 29 | min-height: 60vh; 30 | position: relative; 31 | overflow: hidden; 32 | } 33 | 34 | .rtcGitConnectorSelectRegisteredGitRepository { 35 | display: flex; 36 | background-color: white; 37 | border: 1px solid #cacaca; 38 | padding-left: 10px; 39 | } 40 | 41 | .rtcGitConnectorError { 42 | color: red; 43 | } 44 | 45 | .rtcGitConnectorMessage { 46 | display: block; 47 | padding: 6px; 48 | border-radius: 4px; 49 | border-style: solid; 50 | border-width: 1px; 51 | } 52 | 53 | .rtcGitConnectorMessageInfo { 54 | background-color: #d9edf7; 55 | /* border-color: #bce8f1; */ 56 | color: #31708f; 57 | } 58 | 59 | .rtcGitConnectorMessageWarning { 60 | background-color: #fcf8e3; 61 | /* border-color: #faebcc; */ 62 | color: #8a6d3b; 63 | } 64 | 65 | .rtcGitConnectorMessageError { 66 | background-color: #f2dede; 67 | /* border-color: #ebccd1; */ 68 | color: #a94442; 69 | } 70 | 71 | .rtcGitConnectorLabelText { 72 | display: inline-block; 73 | margin-bottom: 0.5em; 74 | font-size: 1em; 75 | font-weight: bold; 76 | color: #666666; 77 | } 78 | 79 | .rtcGitConnectorSelectRegisteredGitRepository .rtcGitConnectorLabelText { 80 | width: 200px; 81 | margin-top: auto; 82 | margin-bottom: auto; 83 | } 84 | 85 | .rtcGitConnectorSelectList { 86 | width: calc(100% - 201px); 87 | flex-grow: 1; 88 | border-top: 0px; 89 | border-bottom: 0px; 90 | border-right: 0px; 91 | } 92 | 93 | /* Limit the size of the dojo select list drop-down arrow. It looks bad when any larger. */ 94 | .rtcGitConnectorSelectList .dijitArrowButton { 95 | width: 1em; 96 | } 97 | 98 | .rtcGitConnectorSelectListSpan { 99 | box-sizing: border-box; 100 | display: block; 101 | width: 100%; 102 | text-align: left; 103 | padding: 0.2em; 104 | font-size: 1em; 105 | } 106 | 107 | /* Add a light blue background to the selected option in the drop-down list. */ 108 | .dijitSelectSelectedOption .rtcGitConnectorSelectListSpan { 109 | background-color: #cfe5fa; 110 | } 111 | 112 | .rtcGitConnectorSelectListSecondLine { 113 | color: gray; 114 | } 115 | 116 | #rtcGitConnectorSelectLinkTypeContainer { 117 | margin-bottom: 10px; 118 | } 119 | 120 | .rtcGitConnectorSelectLinkType { 121 | display: flex; 122 | background-color: white; 123 | border-left-width: 1px; 124 | border-bottom-width: 1px; 125 | border-top-width: 0px; 126 | border-right-width: 1px; 127 | border-style: solid; 128 | border-color: #cacaca; 129 | } 130 | 131 | .rtcGitConnectorSelectLinkType .linkTypeItem { 132 | display: inline-block; 133 | /* flex-grow: 1; */ 134 | font-size: 1em; 135 | cursor: pointer; 136 | height: 2.75em; 137 | line-height: 2em; 138 | text-decoration: none; 139 | color: black; 140 | padding: 4px 20px; 141 | margin: 0px; 142 | overflow: hidden; 143 | border-bottom: 0px solid #3087b3; 144 | box-sizing: border-box; 145 | transition: all 0.1s ease-out; 146 | } 147 | 148 | .rtcGitConnectorSelectLinkType .linkTypeItem:hover { 149 | border-bottom-width: 3px; 150 | } 151 | 152 | .rtcGitConnectorSelectLinkType .linkTypeItem.selected { 153 | border-bottom-width: 3px; 154 | cursor: default; 155 | } 156 | 157 | .viewAndSelectSearchInput { 158 | flex-grow: 1; 159 | margin-right: 0.5em; 160 | margin-top: auto; 161 | margin-bottom: auto; 162 | } 163 | 164 | .viewAndSelectSearchButton { 165 | margin-right: 0.5em !important; 166 | } 167 | 168 | .viewAndSelectFilterInput { 169 | flex-grow: 1; 170 | margin-right: 0.5em; 171 | margin-top: auto; 172 | margin-bottom: auto; 173 | } 174 | 175 | .rtcGitConnectorViewAndSelectContainer { 176 | background-color: white; 177 | border: 1px solid #cacaca; 178 | padding: 10px; 179 | margin-bottom: 10px; 180 | } 181 | 182 | .rtcGitConnectorViewAndSelectWrapper { 183 | display: flex; 184 | width: 100%; 185 | } 186 | 187 | .rtcGitConnectorViewAndSelectList { 188 | box-sizing: border-box; 189 | flex-grow: 1; 190 | width: 100%; 191 | height: 300px; 192 | margin-right: 10px; 193 | border: 1px solid #cacaca; 194 | overflow-y: scroll; 195 | } 196 | 197 | .rtcGitConnectorViewAndSelectListControl { 198 | display: flex; 199 | border-bottom: 1px solid #cacaca; 200 | padding: 0.5em; 201 | } 202 | 203 | .viewAndSelectControlLabel { 204 | color: #666666; 205 | width: 75px; 206 | margin-top: auto; 207 | margin-bottom: auto; 208 | } 209 | 210 | .rtcGitConnectorViewItemsToLinkList .rtcGitConnectorViewAndSelectListItem:last-child { 211 | border-bottom-width: 0px; 212 | } 213 | 214 | .rtcGitConnectorListsToLinkContainer { 215 | display: flex; 216 | margin-right: -10px; 217 | } 218 | 219 | .rtcGitConnectorListsToLinkContainer .rtcGitConnectorListToLink { 220 | flex-grow: 1; 221 | width: 0; 222 | transition: all 0.3s ease-out; 223 | } 224 | 225 | .viewItemsToLinkContainer { 226 | background-color: white; 227 | border: 1px solid #cacaca; 228 | padding: 10px; 229 | margin-bottom: 10px; 230 | } 231 | 232 | .rtcGitConnectorViewItemsToLinkList { 233 | width: 100%; 234 | border: 1px solid #cacaca; 235 | } 236 | 237 | .rtcGitConnectorHighlightText { 238 | background-color: yellowgreen; 239 | } 240 | 241 | #rtcGitConnectorFullPageLoadingOverlay { 242 | position: absolute; 243 | top: -1em; 244 | bottom: -1em; 245 | left: -1em; 246 | right: -1em; 247 | background-color: white; 248 | z-index: 10000; 249 | text-align: center; 250 | line-height: 20vh; 251 | } 252 | 253 | #rtcGitConnectorFullPageLoadingOverlayContent { 254 | position: fixed; 255 | width: 80vw; 256 | bottom: 0; 257 | } 258 | 259 | #rtcGitConnectorFullPageLoadingOverlayContent span { 260 | display: block; 261 | } 262 | -------------------------------------------------------------------------------- /resources/ui/widget/components/RtcGitConnector/RtcGitConnector.html: -------------------------------------------------------------------------------- 1 | 18 | -------------------------------------------------------------------------------- /resources/ui/widget/components/RtcGitConnector/RtcGitConnector.js: -------------------------------------------------------------------------------- 1 | define([ 2 | "dojo/_base/declare", 3 | "dojo/dom", 4 | "dojo/dom-style", 5 | "../MainLayout/MainLayout", 6 | "../../services/MainDataStore", 7 | "../../services/JazzRestService", 8 | "../../services/GitRestService", 9 | "dijit/_WidgetBase", 10 | "dijit/_TemplatedMixin", 11 | "dijit/_WidgetsInTemplateMixin", 12 | "dijit/registry", 13 | "dijit/Dialog", 14 | "dojo/text!./RtcGitConnector.html", 15 | "dojo/domReady!", 16 | "jazz.ui.Dialog" 17 | ], function ( 18 | declare, 19 | dom, 20 | domStyle, 21 | MainLayout, 22 | MainDataStore, 23 | JazzRestService, 24 | GitRestService, 25 | _WidgetBase, 26 | _TemplatedMixin, 27 | _WidgetsInTemplateMixin, 28 | registry, 29 | Dialog, 30 | template 31 | ) { 32 | return declare([_WidgetBase, _TemplatedMixin, _WidgetsInTemplateMixin], { 33 | templateString: template, 34 | mainDataStore: null, 35 | jazzRestService: null, 36 | mainDialog: null, 37 | 38 | // Set the work item and project area properties in the 39 | // data store so that other classes can access them. 40 | // Important: wrap all constructor parameters in an object 41 | // to prevent the creation of the widget from failing at the 42 | // lifecycle event that mixes the parameters into the widget instance. 43 | constructor: function (params) { 44 | this.mainDataStore = MainDataStore.getInstance(); 45 | this.jazzRestService = JazzRestService.getInstance(); 46 | this.mainDataStore.newWorkItemMode = params.workItem.isNewWorkItem(); 47 | this.mainDataStore.workItem = params.workItem; 48 | this.mainDataStore.projectArea = params.workItem.object.attributes.projectArea; 49 | }, 50 | 51 | startup: function () { 52 | this.mainDataStore.hasHiddenChanges = this.jazzRestService.moveOldLinksToNewLinkTypes( 53 | this.mainDataStore.workItem 54 | ); 55 | 56 | // Show the error dialog in Internet Explorer (better than nothing happening) 57 | if (this.isInternetExplorer()) { 58 | this.mainErrorDialog.startup(); 59 | this.mainErrorDialog.show(); 60 | } else { 61 | // Change the popup title in new work item mode 62 | var title = this.mainDataStore.newWorkItemMode 63 | ? "Create Work Items from Git Issues" 64 | : "Connect with Git"; 65 | var mainLayout = new MainLayout(); 66 | this.mainDialog = new jazz.ui.Dialog({ 67 | heading: title, 68 | contentNode: mainLayout.domNode, 69 | footerNode: mainLayout.footerButtons, 70 | contentPadding: jazz.ui.Dialog.DialogContentPadding.SIDES, 71 | id: "connectWithGitMainDialog", 72 | width: "auto" 73 | }); 74 | } 75 | 76 | this.setEventHandlers(); 77 | }, 78 | 79 | setEventHandlers: function () { 80 | var self = this; 81 | 82 | // Clean up the dom and custom class instances when the widget is closed. 83 | // This is especially important for making the widget work when opened 84 | // and closed multiple times. 85 | this.mainDialog.onClose = function () { 86 | // Destroy all dialogs and remove them from the dom 87 | self.destroyWidgetById("getAndSaveAccessTokenDialog"); 88 | self.destroyWidgetById("browserIsInternetExplorerContainer"); 89 | this.destroyRecursive(false); 90 | 91 | // Destroy data store and services 92 | self.destroyWidgetInstance(); 93 | }; 94 | 95 | // Clean up for the error dialog 96 | this.mainErrorDialog.onHide = function () { 97 | self.destroyWidgetById("getAndSaveAccessTokenDialog"); 98 | self.destroyWidgetById("connectWithGitMainDialog"); 99 | this.destroyRecursive(false); 100 | 101 | self.destroyWidgetInstance(); 102 | }; 103 | }, 104 | 105 | // These custom singleton instances need to be manually destroyed. 106 | // If the widget is opened again it will then get new instances, 107 | // which is the intended behavior. 108 | destroyWidgetInstance: function () { 109 | MainDataStore.destroyInstance(); 110 | JazzRestService.destroyInstance(); 111 | GitRestService.destroyInstance(); 112 | }, 113 | 114 | // Finds a widget by it's HTML id and destroys it, 115 | // also removing it from the dom. 116 | destroyWidgetById: function (domId) { 117 | var widgetToDestroy = registry.byId(domId); 118 | 119 | if (widgetToDestroy) { 120 | widgetToDestroy.destroyRecursive(false); 121 | } 122 | }, 123 | 124 | // Check if the current browser is Internet Explorer 125 | isInternetExplorer: function () { 126 | var ms_ie = false; 127 | var ua = window.navigator.userAgent; 128 | var old_ie = ua.indexOf("MSIE "); 129 | var new_ie = ua.indexOf("Trident/"); 130 | 131 | if (old_ie > -1 || new_ie > -1) { 132 | ms_ie = true; 133 | } 134 | 135 | return ms_ie; 136 | } 137 | }); 138 | }); 139 | -------------------------------------------------------------------------------- /resources/ui/widget/components/SelectItemMessage/SelectItemMessage.css: -------------------------------------------------------------------------------- 1 | /* See: http://www.cssarrowplease.com/ */ 2 | 3 | /* 4 | Make the message div about the same width as a list item. 5 | Add extra margin top so that there is space for the arrow. 6 | */ 7 | .rtcGitConnectorSelectItemMessage { 8 | position: relative; 9 | width: calc(50% - 35px); 10 | margin-top: 20px; 11 | margin-bottom: 10px; 12 | margin-left: 11px; 13 | } 14 | 15 | /* 16 | Use before and after to create and position zero size elements in the same spot. 17 | */ 18 | .rtcGitConnectorSelectItemMessage::after, 19 | .rtcGitConnectorSelectItemMessage::before { 20 | position: absolute; 21 | bottom: 100%; 22 | left: 25px; 23 | height: 0; 24 | width: 0; 25 | border: solid transparent; 26 | content: " "; 27 | pointer-events: none; 28 | } 29 | 30 | /* 31 | Use the after element as the inner triangle. 32 | Looks like an extension of the div. 33 | The border width defines the height of the triangle. 34 | The triangle is created by making only the bottom border visible. 35 | Margin is used for positioning the tip of the triangle in the 36 | center (should equal border width). 37 | */ 38 | .rtcGitConnectorSelectItemMessage::after { 39 | border-bottom-color: #d9edf7; /* backgroundColor */ 40 | border-width: 15px; /* arrowHeight */ 41 | margin-left: -15px; 42 | } 43 | 44 | /* 45 | Use the before element as the outer triangle. 46 | Looks like an extension of the border around the div. 47 | The border width should use the formula commented below. 48 | This is important when the div has a wider border, otherwise 49 | the border on the triangle part will be too small. 50 | Margin is used for positioning the tip of the triangle in the 51 | center (should equal border width). 52 | */ 53 | .rtcGitConnectorSelectItemMessage::before { 54 | border-bottom-color: #31708f; /* borderColor */ 55 | border-width: 17px; /* afterBorderWidth + Math.round(divBorderWidth * Math.sqrt(2)) */ 56 | margin-left: -17px; 57 | } 58 | -------------------------------------------------------------------------------- /resources/ui/widget/components/SelectItemMessage/SelectItemMessage.html: -------------------------------------------------------------------------------- 1 |
2 |
6 |
7 | -------------------------------------------------------------------------------- /resources/ui/widget/components/SelectItemMessage/SelectItemMessage.js: -------------------------------------------------------------------------------- 1 | define([ 2 | "dojo/_base/declare", 3 | "dojo/dom-style", 4 | "dijit/_WidgetBase", 5 | "dijit/_TemplatedMixin", 6 | "dojo/text!./SelectItemMessage.html", 7 | "jazz/css!./SelectItemMessage.css" 8 | ], function (declare, domStyle, _WidgetBase, _TemplatedMixin, template) { 9 | return declare( 10 | "com.siemens.bt.jazz.workitemeditor.rtcGitConnector.ui.widget.selectItemMessage", 11 | [_WidgetBase, _TemplatedMixin], 12 | { 13 | templateString: template, 14 | 15 | // Hide the message by default 16 | // Set to false to show the message 17 | hidden: true, 18 | _setHiddenAttr: function (hidden) { 19 | domStyle.set(this.messageNode, "display", hidden ? "none" : "block"); 20 | this._set("hidden", hidden); 21 | }, 22 | 23 | // Define the default message to show 24 | // Set to a different string to change the message 25 | message: "Click the colorful icon on the left to select an item to be saved.", 26 | _setMessageAttr: { node: "messageNode", type: "innerHTML" } 27 | } 28 | ); 29 | }); 30 | -------------------------------------------------------------------------------- /resources/ui/widget/components/SelectLinkType/SelectLinkType.html: -------------------------------------------------------------------------------- 1 |
2 | 9 | 10 | 15 | 16 | 22 | 23 | 29 | 30 | 36 | 37 |
41 | 42 |
46 | 47 | 67 |
68 | -------------------------------------------------------------------------------- /resources/ui/widget/components/SelectRegisteredGitRepository/SelectRegisteredGitRepository.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
9 |
10 | -------------------------------------------------------------------------------- /resources/ui/widget/components/SelectRegisteredGitRepository/SelectRegisteredGitRepository.js: -------------------------------------------------------------------------------- 1 | define([ 2 | "dojo/_base/declare", 3 | "dojo/_base/array", 4 | "../../services/MainDataStore", 5 | "dijit/_WidgetBase", 6 | "dijit/_TemplatedMixin", 7 | "dijit/_WidgetsInTemplateMixin", 8 | "dijit/form/Select", 9 | "dojo/text!./SelectRegisteredGitRepository.html" 10 | ], function (declare, array, MainDataStore, _WidgetBase, _TemplatedMixin, _WidgetsInTemplateMixin, Select, template) { 11 | return declare( 12 | "com.siemens.bt.jazz.workitemeditor.rtcGitConnector.ui.widget.selectRegisteredGitRepository", 13 | [_WidgetBase, _TemplatedMixin, _WidgetsInTemplateMixin], 14 | { 15 | templateString: template, 16 | mainDataStore: null, 17 | selectListOptions: null, 18 | 19 | constructor: function () { 20 | this.mainDataStore = MainDataStore.getInstance(); 21 | 22 | // Set the initial select list option 23 | this.selectListOptions = [ 24 | { 25 | value: "", 26 | label: this.createLabelString(" ", "Loading..."), 27 | selected: true, 28 | disabled: true 29 | } 30 | ]; 31 | }, 32 | 33 | startup: function () { 34 | this.initializeSelectList(); 35 | this.setEventHandlers(); 36 | this.watchDataStore(); 37 | }, 38 | 39 | initializeSelectList: function () { 40 | this.setOptionsList(); 41 | this.selectRegisteredGitRepository.maxHeight = -1; // Automatically adjust the height to fit the viewport 42 | }, 43 | 44 | watchDataStore: function () { 45 | var self = this; 46 | 47 | // React when the list of git repositories changes 48 | this.mainDataStore.registeredGitRepositories.watchElements(function () { 49 | // Update the view list to reflect the new list of git repositories 50 | self.setRegisteredGitRepositoriesAsListOptions(self.mainDataStore.registeredGitRepositories); 51 | }); 52 | }, 53 | 54 | setEventHandlers: function () { 55 | var self = this; 56 | 57 | // React when the selected list option changes 58 | this.selectRegisteredGitRepository.onChange = function (value) { 59 | // Remove the first item in the list if it doesn't have a value. 60 | // This removes the placeholder option that is initially selected (Select a Git Repository...) 61 | if (this.options[0].value === "") { 62 | this.removeOption(this.options[0]); 63 | } 64 | 65 | var selectedRepository = self.mainDataStore.selectedRepositorySettings.get("repository"); 66 | 67 | // Do nothing if the correct repository is already set in the store 68 | if (!selectedRepository || selectedRepository.key !== value) { 69 | // Check if there are changes 70 | if ( 71 | self.mainDataStore.selectedRepositoryData.commitsToLink.length > 0 || 72 | self.mainDataStore.selectedRepositoryData.issuesToLink.length > 0 || 73 | self.mainDataStore.selectedRepositoryData.requestsToLink.length > 0 74 | ) { 75 | // Ask the user if they want to lose their changes 76 | if ( 77 | confirm( 78 | "Changing the repository will result in losing your unsaved changes. Are you sure you want to discard your changes?" 79 | ) 80 | ) { 81 | // Change the repository if the user wants to lose their changes 82 | self._setSelectedRepositoryByKey(value); 83 | } else { 84 | // Set the selected repository back to it's previous value if the user doesn't want to lose their changes 85 | self.selectRegisteredGitRepository.set("value", selectedRepository.key); 86 | } 87 | } else { 88 | // Change the repository if there are no changes 89 | self._setSelectedRepositoryByKey(value); 90 | } 91 | } 92 | }; 93 | }, 94 | 95 | // Need to run the startup method on the select list after setting a new options list. 96 | // This is a "bug" in dijit/form/Select (or feature?) 97 | setOptionsList: function () { 98 | this.selectRegisteredGitRepository.set("options", this.selectListOptions); 99 | this.selectRegisteredGitRepository.startup(); 100 | }, 101 | 102 | // Add spans and classes to the label for custom formatting 103 | createLabelString: function (firstLine, secondLine) { 104 | return ( 105 | '' + 106 | firstLine + 107 | "" + 108 | '' + 109 | secondLine + 110 | "" 111 | ); 112 | }, 113 | 114 | // Set the view options from the list of git repositories 115 | setRegisteredGitRepositoriesAsListOptions: function (registeredGitRepositories) { 116 | if (!registeredGitRepositories.length) { 117 | // Just add a placeholder if the list doesn't contain any items 118 | this.selectListOptions = [ 119 | { 120 | value: "", 121 | label: this.createLabelString(" ", "No git repositories registered..."), 122 | selected: true, 123 | disabled: true 124 | } 125 | ]; 126 | } else { 127 | // Add a placeholder option (no repository should be selected by default) 128 | this.selectListOptions = [ 129 | { 130 | value: "", 131 | label: this.createLabelString(" ", "Select a Git Repository..."), 132 | selected: true, 133 | disabled: true 134 | } 135 | ]; 136 | 137 | // Format all items and add to the view list 138 | array.forEach( 139 | registeredGitRepositories, 140 | function (registeredGitRepository) { 141 | this.selectListOptions.push({ 142 | value: registeredGitRepository.key, 143 | label: this.createLabelString(registeredGitRepository.name, registeredGitRepository.url) 144 | }); 145 | }, 146 | this 147 | ); 148 | } 149 | 150 | // No repository should be selected right after the list has changed 151 | this.mainDataStore.selectedRepositorySettings.set("repository", null); 152 | this.setOptionsList(); 153 | }, 154 | 155 | // Update the data store. If the selected option is not found in the data store, 156 | // the selected repository in the data store will be set to null 157 | _setSelectedRepositoryByKey: function (key) { 158 | this.mainDataStore.selectedRepositorySettings.set( 159 | "repository", 160 | this.mainDataStore.registeredGitRepositories.find(function (element) { 161 | return element.key === key; 162 | }) 163 | ); 164 | } 165 | } 166 | ); 167 | }); 168 | -------------------------------------------------------------------------------- /resources/ui/widget/components/SetNewWorkItemAttributes/SetNewWorkItemAttributes.css: -------------------------------------------------------------------------------- 1 | #rtcGitConnectorSetNewWorkItemAttributes .workItemEditor { 2 | padding: 0; 3 | } 4 | -------------------------------------------------------------------------------- /resources/ui/widget/components/SetNewWorkItemAttributes/SetNewWorkItemAttributes.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 | -------------------------------------------------------------------------------- /resources/ui/widget/components/SetNewWorkItemAttributes/SetNewWorkItemAttributes.js: -------------------------------------------------------------------------------- 1 | define([ 2 | "dojo/_base/declare", 3 | "dojo/_base/lang", 4 | "../../services/MainDataStore", 5 | "dijit/_WidgetBase", 6 | "dijit/_TemplatedMixin", 7 | "dojo/text!./SetNewWorkItemAttributes.html", 8 | "jazz/css!./SetNewWorkItemAttributes.css", 9 | "com.ibm.team.workitem.web.ui.internal.view.editor.WorkItemOverview" 10 | ], function (declare, lang, MainDataStore, _WidgetBase, _TemplatedMixin, template) { 11 | var WorkItemOverview = com.ibm.team.workitem.web.ui.internal.view.editor.WorkItemOverview; 12 | 13 | return declare( 14 | "com.siemens.bt.jazz.workitemeditor.rtcGitConnector.ui.widget.setNewWorkItemAttributes", 15 | [_WidgetBase, _TemplatedMixin], 16 | { 17 | templateString: template, 18 | mainDataStore: null, 19 | hasOverview: false, 20 | attributesToShow: ["category", "owner", "target", "foundIn", "internalTags"], 21 | 22 | constructor: function () { 23 | this.mainDataStore = MainDataStore.getInstance(); 24 | }, 25 | 26 | // Only create the overview once the show method is called 27 | show: function () { 28 | if (!this.hasOverview) { 29 | this.hasOverview = true; 30 | this.createOverview(); 31 | } 32 | }, 33 | 34 | // Create a work item editor with only the specified attributes. 35 | // ["category", "owner", "target", "foundIn", "internalTags"] 36 | // If the attribute is not available for the current work item presentation 37 | // it will just be left out. 38 | createOverview: function () { 39 | var workItem = this.mainDataStore.workItem; 40 | var workItemEditorWidget = this._getWorkItemEditorWidget(workItem); 41 | var page = this._getOverviewPage(workItem); 42 | 43 | if (!workItemEditorWidget || !page) { 44 | return; 45 | } 46 | 47 | // Work on a copy of the page so that the real presentation properties are not affected. 48 | page = lang.clone(page); 49 | 50 | var section = this._getDetailsSection(page); 51 | 52 | if (!section) { 53 | return; 54 | } 55 | 56 | this._filterSectionPresentationsByAttributes(section, this.attributesToShow); 57 | this._setSectionTitle(section, "Some values for the new work items"); 58 | 59 | // Remove all other sections from the page 60 | page.sections = [section]; 61 | 62 | // Arguments used to create a WorkItemOverview object 63 | var createArgs = { 64 | workItem: workItemEditorWidget._workItem, 65 | parentController: workItemEditorWidget, 66 | editorUtil: workItemEditorWidget.editorUtil, 67 | pageProps: page, 68 | retainedState: false, 69 | isCustomAttributeLayout: {} 70 | }; 71 | 72 | var workItemOverView = new WorkItemOverview(createArgs); 73 | 74 | // Place the work item overview in the dom 75 | this.attributesContainer.insertAdjacentElement("afterBegin", workItemOverView.domNode); 76 | }, 77 | 78 | // Get the work item editor widget instance from the work item page instance 79 | // taken from the cache 80 | _getWorkItemEditorWidget: function (workItem) { 81 | var workItemEditorWidget; 82 | 83 | try { 84 | workItemEditorWidget = jazz.app.currentApplication.workbench._pageWidgetCache[ 85 | "com.ibm.team.workitem" 86 | ]._multipaneContentWidget.getCachedWidget("__jazzWorkItemEditor", workItem.getId()); 87 | } catch (e) { 88 | workItemEditorWidget = null; 89 | } 90 | 91 | return workItemEditorWidget; 92 | }, 93 | 94 | // Get the presentation properties for the overview part of the work item editor page 95 | _getOverviewPage: function (workItem) { 96 | var page; 97 | 98 | try { 99 | page = workItem.workItemSpec.presentationProps.pages.find(function (page) { 100 | return page.layout === "builtInOverviewLayout"; 101 | }); 102 | } catch (e) { 103 | page = null; 104 | } 105 | 106 | return page; 107 | }, 108 | 109 | // Get the details section from the specified page 110 | _getDetailsSection: function (page) { 111 | var section; 112 | 113 | try { 114 | section = page.sections.find(function (section) { 115 | return section.slot === "details"; 116 | }); 117 | } catch (e) { 118 | section = null; 119 | } 120 | 121 | return section; 122 | }, 123 | 124 | // Only keep the section presentations for the specified attributes 125 | _filterSectionPresentationsByAttributes: function (section, attributes) { 126 | section.presentations = section.presentations.filter(function (presentation) { 127 | return attributes.some(function (attribute) { 128 | return attribute === presentation.attributeId; 129 | }); 130 | }); 131 | }, 132 | 133 | // Set the specified title for the specified section 134 | _setSectionTitle: function (section, title) { 135 | var prop = section.properties.find(function (property) { 136 | return property.key === "title"; 137 | }); 138 | 139 | if (prop) { 140 | prop.value = title; 141 | } 142 | } 143 | } 144 | ); 145 | }); 146 | -------------------------------------------------------------------------------- /resources/ui/widget/components/SetNewWorkItemLinks/SetNewWorkItemLinks.css: -------------------------------------------------------------------------------- 1 | .setNewWorkItemLinksContainer { 2 | padding-top: 0.5em; 3 | } 4 | -------------------------------------------------------------------------------- /resources/ui/widget/components/SetNewWorkItemLinks/SetNewWorkItemLinks.html: -------------------------------------------------------------------------------- 1 |
2 | 6 |
7 | -------------------------------------------------------------------------------- /resources/ui/widget/components/SetNewWorkItemLinks/SetNewWorkItemLinks.js: -------------------------------------------------------------------------------- 1 | define([ 2 | "dojo/_base/declare", 3 | "dojo/_base/lang", 4 | "dojo/dom-construct", 5 | "dojo/dom-style", 6 | "dojo/string", 7 | "../../services/MainDataStore", 8 | "dijit/MenuItem", 9 | "dijit/_WidgetBase", 10 | "dijit/_TemplatedMixin", 11 | "dojo/text!./SetNewWorkItemLinks.html", 12 | "jazz/css!./SetNewWorkItemLinks.css", 13 | "com.ibm.jdojox.util.ArrayList", 14 | "com.ibm.jdojox.util.JDojoX", 15 | "com.ibm.team.rtc.foundation.web.model.Bindable", 16 | "com.ibm.team.rtc.foundation.web.model.BindableList", 17 | "com.ibm.team.rtc.foundation.web.model.Label", 18 | "com.ibm.team.rtc.foundation.web.model.ListBindings", 19 | "com.ibm.team.rtc.foundation.web.ui.util.Sprites", 20 | "com.ibm.team.rtc.foundation.web.ui.views.controller.ActionDropdown", 21 | "com.ibm.team.rtc.foundation.web.ui.views.controller.ArtifactList", 22 | "com.ibm.team.rtc.foundation.web.ui.views.controller.ArtifactMultiList", 23 | "com.ibm.team.workitem.web.model.links.WorkItemEndpoints", 24 | "com.ibm.team.workitem.web.ui.internal.view.editor.presentations.nonattribute.links.LinksDialogLauncher" 25 | ], function ( 26 | declare, 27 | lang, 28 | domConstruct, 29 | domStyle, 30 | string, 31 | MainDataStore, 32 | MenuItem, 33 | _WidgetBase, 34 | _TemplatedMixin, 35 | template 36 | ) { 37 | var ArrayList = com.ibm.jdojox.util.ArrayList; 38 | var JDojoX = com.ibm.jdojox.util.JDojoX; 39 | var Bindable = com.ibm.team.rtc.foundation.web.model.Bindable; 40 | var BindableList = com.ibm.team.rtc.foundation.web.model.BindableList; 41 | var Label = com.ibm.team.rtc.foundation.web.model.Label; 42 | var ListBindings = com.ibm.team.rtc.foundation.web.model.ListBindings; 43 | var Sprites = com.ibm.team.rtc.foundation.web.ui.util.Sprites; 44 | var ActionDropdown = com.ibm.team.rtc.foundation.web.ui.views.controller.ActionDropdown; 45 | var ArtifactList = com.ibm.team.rtc.foundation.web.ui.views.controller.ArtifactList; 46 | var ArtifactMultiList = com.ibm.team.rtc.foundation.web.ui.views.controller.ArtifactMultiList; 47 | var WorkItemEndpoints = com.ibm.team.workitem.web.model.links.WorkItemEndpoints; 48 | var LinksDialogLauncher = 49 | com.ibm.team.workitem.web.ui.internal.view.editor.presentations.nonattribute.links.LinksDialogLauncher; 50 | 51 | return declare( 52 | "com.siemens.bt.jazz.workitemeditor.rtcGitConnector.ui.widget.setNewWorkItemLinks", 53 | [_WidgetBase, _TemplatedMixin], 54 | { 55 | templateString: template, 56 | mainDataStore: null, 57 | hasPresentation: false, 58 | enabledEndpoints: null, 59 | enabledEndpointsWithValues: null, 60 | workingCopy: null, 61 | 62 | constructor: function () { 63 | this.mainDataStore = MainDataStore.getInstance(); 64 | this.workingCopy = this.mainDataStore.workItem.getWorkingCopy(); 65 | 66 | // Create the list of link types to show 67 | this.enabledEndpoints = new ArrayList(); 68 | this.enabledEndpoints.add(WorkItemEndpoints.PARENT_WORK_ITEM); 69 | 70 | // Create a list to keep track of what link types have links set 71 | // This is used for the view to dynamically show/hide when links 72 | // are added or removed 73 | this.enabledEndpointsWithValues = new BindableList(); 74 | }, 75 | 76 | // Only create the presentation once the show method is called 77 | show: function () { 78 | if (!this.hasPresentation) { 79 | this.hasPresentation = true; 80 | this.createLinksDropdown(); 81 | this.createLinksList(); 82 | } 83 | }, 84 | 85 | // Create the dropdown for selecting work items to link. Will be 86 | // styled as a single button if there is only one item 87 | createLinksDropdown: function () { 88 | var self = this; 89 | var workItemReferences = this.workingCopy.getWorkItemReferences(); 90 | 91 | // Create a list of endpoints to show in the dropdown 92 | var endpointList = new BindableList(); 93 | endpointList.add(this.enabledEndpoints); 94 | 95 | // Set the first endpoint in the list to be the default 96 | var defaultEndpoint = new Bindable(); 97 | defaultEndpoint.setValue(this.enabledEndpoints.at(0)); 98 | 99 | // Create a div for placing the dropdown in 100 | var actionsMenuDiv = domConstruct.create("div", null, this.linksContainer); 101 | 102 | // Create the ActionDropdown and place it 103 | var actionsMenu = ActionDropdown.create({}, actionsMenuDiv); 104 | 105 | // Setup the ActionDropdown 106 | actionsMenu 107 | .renderer( 108 | lang.hitch(this, function (endpoint) { 109 | return new MenuItem({ 110 | label: this._getEndpointLabel(endpoint), 111 | iconClass: Sprites.cssClassName(endpoint.getIcon()) 112 | }); 113 | }) 114 | ) 115 | .values(endpointList) 116 | .defaultValue(defaultEndpoint) 117 | .bind(); 118 | 119 | // Open the links chooser dialog when a menu item is clicked 120 | actionsMenu.valueChosen.addListener( 121 | lang.hitch(this, function (chosenDescriptor) { 122 | this._launchLinksDialog(self.workingCopy, workItemReferences, chosenDescriptor); 123 | }) 124 | ); 125 | 126 | // Hide the menu dropdown part when there is only a single item 127 | if (this.enabledEndpoints.size() === 1) { 128 | this._hideDropdown(actionsMenu._view); 129 | } 130 | }, 131 | 132 | // Create the presentation of the set links. Can be used to remove the links 133 | createLinksList: function () { 134 | var self = this; 135 | var workItemReferences = this.workingCopy.getWorkItemReferences(); 136 | 137 | // Create a div for placing the list view in 138 | var listViewDiv = domConstruct.create("div", null, this.linksContainer); 139 | 140 | // Create the ArtifactMultiList and place it 141 | var listView = new ArtifactMultiList(listViewDiv).listFactory(function () { 142 | return ArtifactList.create(); 143 | }); 144 | 145 | // Initialize empty BindableLists to bind with the enabledEndpointsWithValues later 146 | var headers = new BindableList(); 147 | var readOnly = new BindableList(); 148 | var typesMap = new BindableList(); 149 | 150 | // Bind the headers to the endpoint display names and icons 151 | ListBindings.bindWithAdapter(this.enabledEndpointsWithValues, headers, function (endpoint) { 152 | return new Label(endpoint.getDisplayName()).iconUrl(endpoint.getIcon().toUri()); 153 | }); 154 | 155 | // Bind the readOnly attribute to reflect whether links from this endpoint can be deleted or not 156 | ListBindings.bindWithAdapter(this.enabledEndpointsWithValues, readOnly, function (endpoint) { 157 | return new Bindable(!endpoint.isUserDeleteable()); 158 | }); 159 | 160 | // Bind the typesMap to the work item references for each endpoint 161 | ListBindings.bindWithAdapter(this.enabledEndpointsWithValues, typesMap, function (endpoint) { 162 | var result = new BindableList(); 163 | var references = workItemReferences.getReferences(endpoint); 164 | 165 | // Bind result to the work item references for this endpoint 166 | ListBindings.bind(references, result); 167 | 168 | // Listen for changes in the result list 169 | result.onListChanged().addListener(function (callbackArg) { 170 | // Check if a reference has been removed from the result list 171 | if (!JDojoX.isEqual(result, references) && callbackArg.type === "remove") { 172 | // Remove the reference from the work item references 173 | workItemReferences.remove(endpoint, callbackArg.elements[0]); 174 | 175 | // Remove the endpoint from the list of endpoints with values 176 | // if the endpoint doesn't have any more references 177 | if (!workItemReferences.hasReferences(endpoint)) { 178 | self.enabledEndpointsWithValues.remove(endpoint); 179 | } 180 | } 181 | }); 182 | 183 | return result; 184 | }); 185 | 186 | // Setup the ArtifactMultiList 187 | listView 188 | .renderer(function (itemReference) { 189 | return new Label(itemReference.getComment()) 190 | .iconUrl(itemReference.getIconUrl()) 191 | .url(itemReference.getUrl()); 192 | }) 193 | .headers(headers) 194 | .sections(typesMap) 195 | .readOnly(readOnly) 196 | .compact(new Bindable(false)) 197 | .bind(); 198 | }, 199 | 200 | // Get the label for the specified endpoint 201 | _getEndpointLabel: function (endpoint) { 202 | return string.substitute(endpoint.isSingleValued() ? "Set ${0}" : "Add ${0}", [ 203 | endpoint.getDisplayName() 204 | ]); 205 | }, 206 | 207 | // Launch a dialog for selecting links of a specific type for the specified work item 208 | _launchLinksDialog: function (workItem, workItemReferences, chosenDescriptor) { 209 | var self = this; 210 | 211 | LinksDialogLauncher.launchDialog(workItem, chosenDescriptor, function (endpoint) { 212 | // Get an array of all the endpoint references (passed as multiple arguments after endpoint) 213 | var references = Array.prototype.slice.call(arguments, 1); 214 | 215 | // Remove the existing reference if this is a single valued endpoint 216 | // and the work item already has a reference 217 | if (endpoint.isSingleValued() && workItemReferences.hasReferences(endpoint)) { 218 | workItemReferences.remove(endpoint, workItemReferences.getReferences(endpoint).at(0)); 219 | } 220 | 221 | // Add the new references to the endpoint 222 | workItemReferences.add.apply(workItemReferences, [endpoint].concat(references)); 223 | 224 | // Add the endpoint to the custom list so that it will be shown in the view 225 | // Only add each endpoint to the list once. The individual references will be added to the endpoint 226 | if (!self.enabledEndpointsWithValues.contains(endpoint)) { 227 | self.enabledEndpointsWithValues.add(endpoint); 228 | } 229 | }); 230 | }, 231 | 232 | // Hide the arrow drop down from the actions menu. This is useful when the menu only contains a single item 233 | _hideDropdown: function (actionsMenuView) { 234 | domStyle.set(actionsMenuView._dropdownElement, "float", "none"); 235 | domStyle.set(actionsMenuView._dropdownElement, "border-right", "none"); 236 | domStyle.set(actionsMenuView._dropdownArrow, "display", "none"); 237 | } 238 | } 239 | ); 240 | }); 241 | -------------------------------------------------------------------------------- /resources/ui/widget/components/SetNewWorkItemValues/SetNewWorkItemValues.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazz-community/rtc-git-connector/8a59c81bfc85c70ef57d4fa83ded54e545faf6c3/resources/ui/widget/components/SetNewWorkItemValues/SetNewWorkItemValues.css -------------------------------------------------------------------------------- /resources/ui/widget/components/SetNewWorkItemValues/SetNewWorkItemValues.html: -------------------------------------------------------------------------------- 1 |
2 |
6 |
10 |
11 | -------------------------------------------------------------------------------- /resources/ui/widget/components/SetNewWorkItemValues/SetNewWorkItemValues.js: -------------------------------------------------------------------------------- 1 | define([ 2 | "dojo/_base/declare", 3 | "dojo/dom-style", 4 | "../../services/MainDataStore", 5 | "../SetNewWorkItemAttributes/SetNewWorkItemAttributes", 6 | "../SetNewWorkItemLinks/SetNewWorkItemLinks", 7 | "dijit/_WidgetBase", 8 | "dijit/_TemplatedMixin", 9 | "dijit/_WidgetsInTemplateMixin", 10 | "dojo/text!./SetNewWorkItemValues.html", 11 | "jazz/css!./SetNewWorkItemValues.css" 12 | ], function ( 13 | declare, 14 | domStyle, 15 | MainDataStore, 16 | SetNewWorkItemAttributes, 17 | SetNewWorkItemLinks, 18 | _WidgetBase, 19 | _TemplatedMixin, 20 | _WidgetsInTemplateMixin, 21 | template 22 | ) { 23 | return declare( 24 | "com.siemens.bt.jazz.workitemeditor.rtcGitConnector.ui.widget.setNewWorkItemValues", 25 | [_WidgetBase, _TemplatedMixin, _WidgetsInTemplateMixin], 26 | { 27 | templateString: template, 28 | 29 | visible: false, 30 | _setVisibleAttr: function (visible) { 31 | domStyle.set(this.domNode, "display", visible ? "block" : "none"); 32 | this._set("visible", visible); 33 | }, 34 | 35 | constructor: function () { 36 | this.mainDataStore = MainDataStore.getInstance(); 37 | }, 38 | 39 | startup: function () { 40 | if (this.mainDataStore.newWorkItemMode) { 41 | this.watchDataStore(); 42 | } 43 | }, 44 | 45 | watchDataStore: function () { 46 | var self = this; 47 | 48 | // Only show the attributes when the list of selected issues isn't empty 49 | this.mainDataStore.selectedRepositoryData.issuesToLink.watchElements(function () { 50 | var hasItems = self.mainDataStore.selectedRepositoryData.issuesToLink.length > 0; 51 | self.set("visible", hasItems); 52 | 53 | if (hasItems) { 54 | self.setNewWorkItemAttributes.show(); 55 | self.setNewWorkItemLinks.show(); 56 | } 57 | }); 58 | } 59 | } 60 | ); 61 | }); 62 | -------------------------------------------------------------------------------- /resources/ui/widget/components/ViewAndSelectCommits/ViewAndSelectCommits.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 |
6 |
7 |
Search:
8 | 16 | 23 | 26 |
27 |
28 |
Filter:
29 | 37 | 40 |
41 |
42 |
43 |
44 |
48 |
49 |
50 | -------------------------------------------------------------------------------- /resources/ui/widget/components/ViewAndSelectCommits/ViewAndSelectCommits.js: -------------------------------------------------------------------------------- 1 | define([ 2 | "dojo/_base/declare", 3 | "dojo/_base/array", 4 | "dojo/_base/lang", 5 | "dojo/dom", 6 | "dojo/dom-construct", 7 | "dojo/on", 8 | "../../services/MainDataStore", 9 | "../../services/JazzRestService", 10 | "../../services/GitRestService", 11 | "../../utils/ViewHelper", 12 | "../DetailsPane/DetailsPane", 13 | "../ListItem/ListItem", 14 | "dijit/_WidgetBase", 15 | "dijit/_TemplatedMixin", 16 | "dijit/_WidgetsInTemplateMixin", 17 | "dojo/text!./ViewAndSelectCommits.html" 18 | ], function ( 19 | declare, 20 | array, 21 | lang, 22 | dom, 23 | domConstruct, 24 | on, 25 | MainDataStore, 26 | JazzRestService, 27 | GitRestService, 28 | ViewHelper, 29 | DetailsPane, 30 | ListItem, 31 | _WidgetBase, 32 | _TemplatedMixin, 33 | _WidgetsInTemplateMixin, 34 | template 35 | ) { 36 | return declare( 37 | "com.siemens.bt.jazz.workitemeditor.rtcGitConnector.ui.widget.viewAndSelectCommits", 38 | [_WidgetBase, _TemplatedMixin, _WidgetsInTemplateMixin], 39 | { 40 | templateString: template, 41 | mainDataStore: null, 42 | jazzRestService: null, 43 | gitRestService: null, 44 | viewCommits: null, 45 | 46 | constructor: function () { 47 | this.mainDataStore = MainDataStore.getInstance(); 48 | this.jazzRestService = JazzRestService.getInstance(); 49 | this.gitRestService = GitRestService.getInstance(); 50 | }, 51 | 52 | startup: function () { 53 | this.initializeViewCommitsList(); 54 | this.watchDataStore(); 55 | this.setEventHandlers(); 56 | }, 57 | 58 | setEventHandlers: function () { 59 | var self = this; 60 | var commitsLoadedFunc = function (commits) { 61 | self.mainDataStore.selectedRepositoryData.commits.splice( 62 | 0, 63 | self.mainDataStore.selectedRepositoryData.commits.length 64 | ); 65 | self.mainDataStore.selectedRepositoryData.commits.push.apply( 66 | self.mainDataStore.selectedRepositoryData.commits, 67 | commits 68 | ); 69 | self.mainDataStore.selectedRepositorySettings.set("commitsLoaded", true); 70 | self.mainDataStore.selectedRepositorySettings.set("commitsLoading", false); 71 | 72 | // Enable the search and clear buttons after loading 73 | dom.byId("viewAndSelectCommitsSearchButton").removeAttribute("disabled"); 74 | dom.byId("viewAndSelectCommitsSearchClearButton").removeAttribute("disabled"); 75 | }; 76 | var commitsLoadErrorFunc = function (error) { 77 | self.mainDataStore.selectedRepositorySettings.set("commitsLoadError", error || "Unknown Error"); 78 | 79 | // Enable the search and clear buttons after loading 80 | dom.byId("viewAndSelectCommitsSearchButton").removeAttribute("disabled"); 81 | dom.byId("viewAndSelectCommitsSearchClearButton").removeAttribute("disabled"); 82 | }; 83 | var searchButtonClickFunc = function (event) { 84 | // Don't do anything if commits are already being loaded 85 | if (!self.mainDataStore.selectedRepositorySettings.get("commitsLoading")) { 86 | var selectedRepository = self.mainDataStore.selectedRepositorySettings.get("repository"); 87 | var gitHost = self.mainDataStore.selectedRepositorySettings.get("gitHost"); 88 | var accessToken = self.mainDataStore.selectedRepositorySettings.get("accessToken"); 89 | var commitSha = self.commitsSearchInput.value; 90 | var alreadyLinkedUrls = self.jazzRestService.getGitCommitLinksFromWorkItem( 91 | self.mainDataStore.workItem 92 | ); 93 | 94 | // Disable the search and clear buttons while loading 95 | dom.byId("viewAndSelectCommitsSearchButton").setAttribute("disabled", "disabled"); 96 | dom.byId("viewAndSelectCommitsSearchClearButton").setAttribute("disabled", "disabled"); 97 | 98 | // Set the commitsLoading to true to prevent multiple requests 99 | self.mainDataStore.selectedRepositorySettings.set("commitsLoading", true); 100 | self.mainDataStore.selectedRepositorySettings.set("commitsLoaded", false); 101 | 102 | if (commitSha) { 103 | // Try to get the commit with the specified SHA 104 | self.gitRestService 105 | .getCommitById(selectedRepository, gitHost, accessToken, commitSha, alreadyLinkedUrls) 106 | .then(commitsLoadedFunc, commitsLoadErrorFunc); 107 | } else { 108 | // Get all commits if there is no SHA 109 | self.gitRestService 110 | .getRecentCommits(selectedRepository, gitHost, accessToken, alreadyLinkedUrls) 111 | .then(commitsLoadedFunc, commitsLoadErrorFunc); 112 | } 113 | } 114 | }; 115 | 116 | on(this.commitsFilterInput, "change", function (value) { 117 | self.setViewCommitsListFromStore(value); 118 | }); 119 | 120 | on(dom.byId("viewAndSelectCommitsFilterClearButton"), "click", function (event) { 121 | self.commitsFilterInput.set("value", ""); 122 | }); 123 | 124 | on(dom.byId("viewAndSelectCommitsSearchButton"), "click", searchButtonClickFunc); 125 | 126 | on(dom.byId("viewAndSelectCommitsSearchClearButton"), "click", function (event) { 127 | self.commitsSearchInput.set("value", ""); 128 | searchButtonClickFunc(); 129 | }); 130 | }, 131 | 132 | watchDataStore: function () { 133 | var self = this; 134 | 135 | // Watch the store to know when the commits finished loading 136 | this.mainDataStore.selectedRepositorySettings.watch("commitsLoaded", function (name, oldValue, value) { 137 | if (value) { 138 | // Commits finished loading, update the view 139 | self.setViewCommitsListFromStore(); 140 | } else { 141 | // Commits are not loaded, reinitialize the view (loading...) 142 | self.initializeViewCommitsList(); 143 | } 144 | }); 145 | 146 | // Watch the store to react when the list of commits changes (add / remove from commits to link list) 147 | this.mainDataStore.selectedRepositoryData.commits.watchElements(function () { 148 | // Only react if the commits have finished loading 149 | if (self.mainDataStore.selectedRepositorySettings.get("commitsLoaded")) { 150 | // Update the local list of commits (and the view) 151 | self.setViewCommitsListFromStore(); 152 | } 153 | }); 154 | }, 155 | 156 | initializeViewCommitsList: function () { 157 | this.viewCommits = [ 158 | { 159 | message: "Loading...", 160 | alreadyLinked: true 161 | } 162 | ]; 163 | 164 | // Clear the filter input 165 | this.commitsFilterInput.set("value", ""); 166 | 167 | // Draw the commits list in the view 168 | this.drawViewCommits(); 169 | this.drawDetailsView(); 170 | }, 171 | 172 | setViewCommitsListFromStore: function (filterValue) { 173 | // Clone the store array 174 | this.viewCommits = lang.clone(this.mainDataStore.selectedRepositoryData.commits); 175 | 176 | array.forEach(this.viewCommits, function (commit) { 177 | commit.originalSha = commit.sha; 178 | }); 179 | 180 | if (this.viewCommits.length < 1) { 181 | this.viewCommits = [ 182 | { 183 | message: "No commits found", 184 | alreadyLinked: true 185 | } 186 | ]; 187 | } else { 188 | // Need to sort the viewCommits here (by date created -> newest on top) 189 | this.sortViewCommitsByDate(); 190 | 191 | if (!filterValue) { 192 | // Take the filter from the input if it wasn't passed in 193 | filterValue = this.commitsFilterInput.value; 194 | } 195 | 196 | // Filter the view commits using the filter input text 197 | if (filterValue) { 198 | this.filterViewCommitsByText(filterValue); 199 | } 200 | } 201 | 202 | // Draw the commits list in the view 203 | this.drawViewCommits(); 204 | this.drawDetailsView(); 205 | }, 206 | 207 | // Draw the commits list from the view commits 208 | drawViewCommits: function () { 209 | var self = this; 210 | domConstruct.empty(this.listItemsContainer); 211 | 212 | array.forEach(this.viewCommits, function (commit) { 213 | var listItem = new ListItem(commit.originalSha); 214 | listItem.set("title", commit.message.split(/\r?\n/g)[0]); 215 | listItem.set("details", ViewHelper.GetCommitDateString(commit)); 216 | listItem.set("buttonType", commit.alreadyLinked ? "check" : "link"); 217 | listItem.set("duplicate", false); 218 | listItem.set( 219 | "buttonTitle", 220 | !commit.alreadyLinked ? "Add Link" : commit.originalSha ? "Already Linked" : "" 221 | ); 222 | 223 | listItem.onButtonClick = lang.hitch(self, self.listItemButtonClick); 224 | listItem.onContentClick = lang.hitch(self, self.setSelectedItemById); 225 | 226 | commit.listItem = listItem; 227 | domConstruct.place(listItem.domNode, self.listItemsContainer); 228 | }); 229 | }, 230 | 231 | // Remove the commit with the specified sha from the commits list in store and add to the selected list 232 | listItemButtonClick: function (itemId) { 233 | var selectedCommit = null; 234 | 235 | for (var i = this.mainDataStore.selectedRepositoryData.commits.length - 1; i >= 0; i--) { 236 | if ( 237 | this.mainDataStore.selectedRepositoryData.commits[i].sha === itemId && 238 | !this.mainDataStore.selectedRepositoryData.commits[i].alreadyLinked 239 | ) { 240 | selectedCommit = this.mainDataStore.selectedRepositoryData.commits.splice(i, 1)[0]; 241 | break; 242 | } 243 | } 244 | 245 | if ( 246 | selectedCommit && 247 | !this.mainDataStore.selectedRepositoryData.commitsToLink.find(function (commit) { 248 | return commit.sha === selectedCommit.sha; 249 | }) 250 | ) { 251 | this.mainDataStore.selectedRepositoryData.commitsToLink.push(selectedCommit); 252 | } 253 | }, 254 | 255 | // Set the selected list item in the view using the item id 256 | setSelectedItemById: function (itemId) { 257 | var self = this; 258 | 259 | array.forEach(this.viewCommits, function (commit) { 260 | if (commit.originalSha && commit.originalSha === itemId) { 261 | commit.listItem.set("selected", true); 262 | self.drawDetailsView(commit); 263 | } else { 264 | commit.listItem.set("selected", false); 265 | } 266 | }); 267 | }, 268 | 269 | // Draw the details view for the selected commit 270 | drawDetailsView: function (commit) { 271 | var items = []; 272 | 273 | if (!commit) { 274 | items.push({ 275 | text: "Select a commit to view more details" 276 | }); 277 | } else { 278 | items.push( 279 | { 280 | label: "Message: ", 281 | text: commit.message.replace(/(\r\n|\n|\r)/gm, "
") 282 | }, 283 | { 284 | label: "Author: ", 285 | text: commit.authorName + " (" + commit.authorEmail + ")" 286 | }, 287 | { 288 | label: "Date: ", 289 | text: ViewHelper.GetFormattedDateFromString(commit.authoredDate) 290 | }, 291 | { 292 | label: "SHA: ", 293 | text: commit.sha 294 | }, 295 | { 296 | label: "Web Link: ", 297 | text: "Open this commit in a new tab", 298 | link: commit.webUrl 299 | } 300 | ); 301 | } 302 | 303 | this.detailsPane.setContent("Commit Details", items); 304 | }, 305 | 306 | // Sort the view commits by the authoredDate 307 | sortViewCommitsByDate: function () { 308 | this.viewCommits = ViewHelper.SortListDataByDate("authoredDate", this.viewCommits); 309 | }, 310 | 311 | // Filter the view commits using the filter text. 312 | // Only keep commits that contain the filter text either 313 | // in the commit message or commit author name or sha or email 314 | filterViewCommitsByText: function (filterText) { 315 | this.viewCommits = ViewHelper.FilterListDataByText( 316 | filterText, 317 | ["sha", "message", "authorName", "authorEmail"], 318 | this.viewCommits 319 | ); 320 | } 321 | } 322 | ); 323 | }); 324 | -------------------------------------------------------------------------------- /resources/ui/widget/components/ViewAndSelectIssues/ViewAndSelectIssues.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 |
6 |
7 |
Search:
8 | 16 | 23 | 26 |
27 |
28 |
Filter:
29 | 37 | 40 |
41 |
42 |
43 |
44 |
48 |
49 |
50 | -------------------------------------------------------------------------------- /resources/ui/widget/components/ViewAndSelectRequests/ViewAndSelectRequests.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 |
6 |
7 |
Search:
8 | 16 | 23 | 26 |
27 |
28 |
Filter:
29 | 37 | 40 |
41 |
42 |
43 |
44 |
48 |
49 |
50 | -------------------------------------------------------------------------------- /resources/ui/widget/components/ViewAndSelectRequests/ViewAndSelectRequests.js: -------------------------------------------------------------------------------- 1 | define([ 2 | "dojo/_base/declare", 3 | "dojo/_base/array", 4 | "dojo/_base/lang", 5 | "dojo/dom", 6 | "dojo/dom-construct", 7 | "dojo/on", 8 | "../../services/MainDataStore", 9 | "../../services/JazzRestService", 10 | "../../services/GitRestService", 11 | "../../utils/ViewHelper", 12 | "../DetailsPane/DetailsPane", 13 | "../ListItem/ListItem", 14 | "dijit/_WidgetBase", 15 | "dijit/_TemplatedMixin", 16 | "dijit/_WidgetsInTemplateMixin", 17 | "dojo/text!./ViewAndSelectRequests.html" 18 | ], function ( 19 | declare, 20 | array, 21 | lang, 22 | dom, 23 | domConstruct, 24 | on, 25 | MainDataStore, 26 | JazzRestService, 27 | GitRestService, 28 | ViewHelper, 29 | DetailsPane, 30 | ListItem, 31 | _WidgetBase, 32 | _TemplatedMixin, 33 | _WidgetsInTemplateMixin, 34 | template 35 | ) { 36 | return declare( 37 | "com.siemens.bt.jazz.workitemeditor.rtcGitConnector.ui.widget.viewAndSelectRequests", 38 | [_WidgetBase, _TemplatedMixin, _WidgetsInTemplateMixin], 39 | { 40 | templateString: template, 41 | mainDataStore: null, 42 | jazzRestService: null, 43 | gitRestService: null, 44 | viewRequests: null, 45 | 46 | constructor: function () { 47 | this.mainDataStore = MainDataStore.getInstance(); 48 | this.jazzRestService = JazzRestService.getInstance(); 49 | this.gitRestService = GitRestService.getInstance(); 50 | }, 51 | 52 | startup: function () { 53 | this.initializeViewRequestsList(); 54 | this.watchDataStore(); 55 | this.setEventHandlers(); 56 | }, 57 | 58 | setEventHandlers: function () { 59 | var self = this; 60 | var requestsLoadedFunc = function (requests) { 61 | self.mainDataStore.selectedRepositoryData.requests.splice( 62 | 0, 63 | self.mainDataStore.selectedRepositoryData.requests.length 64 | ); 65 | self.mainDataStore.selectedRepositoryData.requests.push.apply( 66 | self.mainDataStore.selectedRepositoryData.requests, 67 | requests 68 | ); 69 | self.mainDataStore.selectedRepositorySettings.set("requestsLoaded", true); 70 | self.mainDataStore.selectedRepositorySettings.set("requestsLoading", false); 71 | 72 | // Enable the search and clear buttons after loading 73 | dom.byId("viewAndSelectRequestsSearchButton").removeAttribute("disabled"); 74 | dom.byId("viewAndSelectRequestsSearchClearButton").removeAttribute("disabled"); 75 | }; 76 | var requestsLoadErrorFunc = function (error) { 77 | self.mainDataStore.selectedRepositorySettings.set("requestsLoadError", error || "Unknown Error"); 78 | 79 | // Enable the search and clear buttons after loading 80 | dom.byId("viewAndSelectRequestsSearchButton").removeAttribute("disabled"); 81 | dom.byId("viewAndSelectRequestsSearchClearButton").removeAttribute("disabled"); 82 | }; 83 | var searchButtonClickFunc = function (event) { 84 | // Don't do anything if requests are already being loaded 85 | if (!self.mainDataStore.selectedRepositorySettings.get("requestsLoading")) { 86 | var selectedRepository = self.mainDataStore.selectedRepositorySettings.get("repository"); 87 | var gitHost = self.mainDataStore.selectedRepositorySettings.get("gitHost"); 88 | var accessToken = self.mainDataStore.selectedRepositorySettings.get("accessToken"); 89 | var requestId = self.requestsSearchInput.value; 90 | var alreadyLinkedUrls = self.jazzRestService.getRequestLinksFromWorkItem( 91 | self.mainDataStore.workItem 92 | ); 93 | 94 | // Disable the search and clear buttons while loading 95 | dom.byId("viewAndSelectRequestsSearchButton").setAttribute("disabled", "disabled"); 96 | dom.byId("viewAndSelectRequestsSearchClearButton").setAttribute("disabled", "disabled"); 97 | 98 | // Set the requestsLoading to true to prevent multiple requests 99 | self.mainDataStore.selectedRepositorySettings.set("requestsLoading", true); 100 | self.mainDataStore.selectedRepositorySettings.set("requestsLoaded", false); 101 | 102 | if (requestId) { 103 | // Try to get the request with the specified id 104 | self.gitRestService 105 | .getRequestById(selectedRepository, gitHost, accessToken, requestId, alreadyLinkedUrls) 106 | .then(requestsLoadedFunc, requestsLoadErrorFunc); 107 | } else { 108 | // Get all requests if there is no id 109 | self.gitRestService 110 | .getRecentRequests(selectedRepository, gitHost, accessToken, alreadyLinkedUrls) 111 | .then(requestsLoadedFunc, requestsLoadErrorFunc); 112 | } 113 | } 114 | }; 115 | 116 | on(this.requestsFilterInput, "change", function (value) { 117 | self.setViewRequestsListFromStore(value); 118 | }); 119 | 120 | on(dom.byId("viewAndSelectRequestsFilterClearButton"), "click", function (event) { 121 | self.requestsFilterInput.set("value", ""); 122 | }); 123 | 124 | on(dom.byId("viewAndSelectRequestsSearchButton"), "click", searchButtonClickFunc); 125 | 126 | on(dom.byId("viewAndSelectRequestsSearchClearButton"), "click", function (event) { 127 | self.requestsSearchInput.set("value", ""); 128 | searchButtonClickFunc(); 129 | }); 130 | }, 131 | 132 | watchDataStore: function () { 133 | var self = this; 134 | 135 | // Watch the store to know when the requests finished loading 136 | this.mainDataStore.selectedRepositorySettings.watch("requestsLoaded", function (name, oldValue, value) { 137 | if (value) { 138 | // Requests finished loading, update the view 139 | self.setViewRequestsListFromStore(); 140 | } else { 141 | // Requests are not loaded, reinitialize the view (loading...) 142 | self.initializeViewRequestsList(); 143 | } 144 | }); 145 | 146 | // Watch the store to react when the list of requests changes (add / remove from requests to link list) 147 | this.mainDataStore.selectedRepositoryData.requests.watchElements(function () { 148 | // Only react if the requests have finished loading 149 | if (self.mainDataStore.selectedRepositorySettings.get("requestsLoaded")) { 150 | // Update the local list of requests (and the view) 151 | self.setViewRequestsListFromStore(); 152 | } 153 | }); 154 | }, 155 | 156 | initializeViewRequestsList: function () { 157 | this.viewRequests = [ 158 | { 159 | title: "Loading...", 160 | alreadyLinked: true 161 | } 162 | ]; 163 | 164 | // Clear the filter input 165 | this.requestsFilterInput.set("value", ""); 166 | 167 | // Draw the requests list in the view 168 | this.drawViewRequests(); 169 | this.drawDetailsView(); 170 | }, 171 | 172 | setViewRequestsListFromStore: function (filterValue) { 173 | // Clone the store array 174 | this.viewRequests = lang.clone(this.mainDataStore.selectedRepositoryData.requests); 175 | 176 | array.forEach(this.viewRequests, function (request) { 177 | request.originalId = request.id; 178 | }); 179 | 180 | if (this.viewRequests.length < 1) { 181 | this.viewRequests = [ 182 | { 183 | title: "No requests found", 184 | alreadyLinked: true 185 | } 186 | ]; 187 | } else { 188 | // Need to sort the viewRequests here (by date created -> newest on top) 189 | this.sortViewRequestsByDate(); 190 | 191 | if (!filterValue) { 192 | // Take the filter from the input if it wasn't passed in 193 | filterValue = this.requestsFilterInput.value; 194 | } 195 | 196 | // Filter the view requests using the filter input text 197 | if (filterValue) { 198 | this.filterViewRequestsByText(filterValue); 199 | } 200 | } 201 | 202 | // Draw the requests list in the view 203 | this.drawViewRequests(); 204 | this.drawDetailsView(); 205 | }, 206 | 207 | // Draw the requests list from the view requests 208 | drawViewRequests: function () { 209 | var self = this; 210 | domConstruct.empty(this.listItemsContainer); 211 | 212 | array.forEach(this.viewRequests, function (request) { 213 | var listItem = new ListItem(request.originalId); 214 | listItem.set("title", request.title); 215 | listItem.set("details", ViewHelper.GetIssueOrRequestDateString(request)); 216 | listItem.set("buttonType", request.alreadyLinked ? "check" : "link"); 217 | listItem.set("duplicate", false); 218 | listItem.set( 219 | "buttonTitle", 220 | !request.alreadyLinked ? "Add Link" : request.originalId ? "Already Linked" : "" 221 | ); 222 | 223 | listItem.onButtonClick = lang.hitch(self, self.listItemButtonClick); 224 | listItem.onContentClick = lang.hitch(self, self.setSelectedItemById); 225 | 226 | request.listItem = listItem; 227 | domConstruct.place(listItem.domNode, self.listItemsContainer); 228 | }); 229 | }, 230 | 231 | // Remove the request with the specified id from the requests list in store and add to the selected list 232 | listItemButtonClick: function (itemId) { 233 | var selectedRequest = null; 234 | 235 | for (var i = this.mainDataStore.selectedRepositoryData.requests.length - 1; i >= 0; i--) { 236 | if ( 237 | this.mainDataStore.selectedRepositoryData.requests[i].id == itemId && 238 | !this.mainDataStore.selectedRepositoryData.requests[i].alreadyLinked 239 | ) { 240 | selectedRequest = this.mainDataStore.selectedRepositoryData.requests.splice(i, 1)[0]; 241 | break; 242 | } 243 | } 244 | 245 | if ( 246 | selectedRequest && 247 | !this.mainDataStore.selectedRepositoryData.requestsToLink.find(function (request) { 248 | return request.id == selectedRequest.id; 249 | }) 250 | ) { 251 | this.mainDataStore.selectedRepositoryData.requestsToLink.push(selectedRequest); 252 | } 253 | }, 254 | 255 | // Set the selected list item in the view using the item id 256 | setSelectedItemById: function (itemId) { 257 | var self = this; 258 | 259 | array.forEach(this.viewRequests, function (request) { 260 | if (request.originalId && request.originalId === itemId) { 261 | request.listItem.set("selected", true); 262 | self.drawDetailsView(request); 263 | } else { 264 | request.listItem.set("selected", false); 265 | } 266 | }); 267 | }, 268 | 269 | // Draw the details view for the selected request 270 | drawDetailsView: function (request) { 271 | var gitHost = this.mainDataStore.selectedRepositorySettings.get("gitHost"); 272 | var items = []; 273 | 274 | if (!request) { 275 | items.push({ 276 | text: "Select a " + gitHost.requestPrefix.toLowerCase() + "request to view more details" 277 | }); 278 | } else { 279 | items.push( 280 | { 281 | label: "Title: ", 282 | text: request.title 283 | }, 284 | { 285 | label: "State: ", 286 | text: request.state 287 | }, 288 | { 289 | label: "Labels: ", 290 | text: request.labels || "-" 291 | }, 292 | { 293 | label: "Milestone: ", 294 | text: request.milestone || "-" 295 | }, 296 | { 297 | label: "Opened by: ", 298 | text: request.openedBy 299 | }, 300 | { 301 | label: "Date opened: ", 302 | text: ViewHelper.GetFormattedDateFromString(request.openedDate) 303 | }, 304 | { 305 | label: "Request id: ", 306 | text: "#" + request.id 307 | }, 308 | { 309 | label: "Web Link: ", 310 | text: "Open this " + gitHost.requestPrefix.toLowerCase() + "request in a new tab", 311 | link: request.webUrl 312 | } 313 | ); 314 | } 315 | 316 | this.detailsPane.setContent(gitHost.requestPrefix + "Request Details", items); 317 | }, 318 | 319 | // Sort the view requests by the openedDate 320 | sortViewRequestsByDate: function () { 321 | this.viewRequests = ViewHelper.SortListDataByDate("openedDate", this.viewRequests); 322 | }, 323 | 324 | // Filter the view requests using the filter text. 325 | // Only keep requests that contain the filter text either 326 | // in the request title or request author name or id or state 327 | filterViewRequestsByText: function (filterText) { 328 | this.viewRequests = ViewHelper.FilterListDataByText( 329 | filterText, 330 | ["id", "title", "state", "openedBy", "labels", "milestone"], 331 | this.viewRequests 332 | ); 333 | } 334 | } 335 | ); 336 | }); 337 | -------------------------------------------------------------------------------- /resources/ui/widget/components/ViewCommitsToLink/ViewCommitsToLink.html: -------------------------------------------------------------------------------- 1 |
2 | 6 |
7 | -------------------------------------------------------------------------------- /resources/ui/widget/components/ViewCommitsToLink/ViewCommitsToLink.js: -------------------------------------------------------------------------------- 1 | define([ 2 | "dojo/_base/declare", 3 | "dojo/_base/array", 4 | "dojo/_base/lang", 5 | "dojo/dom-construct", 6 | "dojo/dom-style", 7 | "../../services/MainDataStore", 8 | "../../utils/ViewHelper", 9 | "../ListItem/ListItem", 10 | "dijit/_WidgetBase", 11 | "dijit/_TemplatedMixin", 12 | "dijit/_WidgetsInTemplateMixin", 13 | "dojo/text!./ViewCommitsToLink.html" 14 | ], function ( 15 | declare, 16 | array, 17 | lang, 18 | domConstruct, 19 | domStyle, 20 | MainDataStore, 21 | ViewHelper, 22 | ListItem, 23 | _WidgetBase, 24 | _TemplatedMixin, 25 | _WidgetsInTemplateMixin, 26 | template 27 | ) { 28 | return declare( 29 | "com.siemens.bt.jazz.workitemeditor.rtcGitConnector.ui.widget.viewCommitsToLink", 30 | [_WidgetBase, _TemplatedMixin, _WidgetsInTemplateMixin], 31 | { 32 | templateString: template, 33 | mainDataStore: null, 34 | 35 | constructor: function () { 36 | this.mainDataStore = MainDataStore.getInstance(); 37 | }, 38 | 39 | startup: function () { 40 | this.watchDataStore(); 41 | }, 42 | 43 | watchDataStore: function () { 44 | var self = this; 45 | 46 | // React when commits are added or removed from the commits to link list 47 | this.mainDataStore.selectedRepositoryData.commitsToLink.watchElements(function () { 48 | if (self.mainDataStore.selectedRepositoryData.commitsToLink.length > 0) { 49 | // show commits to link list 50 | domStyle.set("viewCommitsToLinkContainer", "display", "block"); 51 | domStyle.set("rtcGitConnectorCommitsListToLink", "width", "100%"); 52 | domStyle.set("rtcGitConnectorCommitsListToLink", "margin-right", "10px"); 53 | } else { 54 | // hide commits to link list 55 | domStyle.set("rtcGitConnectorCommitsListToLink", "width", "0"); 56 | domStyle.set("rtcGitConnectorCommitsListToLink", "margin-right", "0"); 57 | domStyle.set("viewCommitsToLinkContainer", "display", "none"); 58 | } 59 | 60 | self.drawCommitsToLink(self.mainDataStore.selectedRepositoryData.commitsToLink); 61 | }); 62 | }, 63 | 64 | // Draw the commits to link list in the view 65 | drawCommitsToLink: function (commitsToLink) { 66 | var self = this; 67 | domConstruct.empty(this.listItemsContainer); 68 | 69 | array.forEach(commitsToLink, function (commit) { 70 | var listItem = new ListItem(commit.sha); 71 | listItem.set("title", commit.message.split(/\r?\n/g)[0]); 72 | listItem.set("details", ViewHelper.GetCommitDateString(commit)); 73 | listItem.set("buttonType", "trash"); 74 | listItem.set("notClickable", true); 75 | listItem.set("duplicate", false); 76 | listItem.set("buttonTitle", "Remove"); 77 | 78 | listItem.onButtonClick = lang.hitch(self, self.listItemButtonClick); 79 | 80 | commit.listItem = listItem; 81 | domConstruct.place(listItem.domNode, self.listItemsContainer); 82 | }); 83 | }, 84 | 85 | // Remove the commit with the specified sha from the commits to link list in store and add to the commits list 86 | listItemButtonClick: function (itemId) { 87 | var selectedCommit = null; 88 | 89 | for (var i = this.mainDataStore.selectedRepositoryData.commitsToLink.length - 1; i >= 0; i--) { 90 | if (this.mainDataStore.selectedRepositoryData.commitsToLink[i].sha === itemId) { 91 | selectedCommit = this.mainDataStore.selectedRepositoryData.commitsToLink.splice(i, 1)[0]; 92 | break; 93 | } 94 | } 95 | 96 | if ( 97 | selectedCommit && 98 | !this.mainDataStore.selectedRepositoryData.commits.find(function (commit) { 99 | return commit.sha === selectedCommit.sha; 100 | }) 101 | ) { 102 | this.mainDataStore.selectedRepositoryData.commits.push(selectedCommit); 103 | } 104 | } 105 | } 106 | ); 107 | }); 108 | -------------------------------------------------------------------------------- /resources/ui/widget/components/ViewIssuesToLink/ViewIssuesToLink.html: -------------------------------------------------------------------------------- 1 |
2 | 6 |
7 | -------------------------------------------------------------------------------- /resources/ui/widget/components/ViewIssuesToLink/ViewIssuesToLink.js: -------------------------------------------------------------------------------- 1 | define([ 2 | "dojo/_base/declare", 3 | "dojo/_base/array", 4 | "dojo/_base/lang", 5 | "dojo/dom-construct", 6 | "dojo/dom-style", 7 | "../../services/MainDataStore", 8 | "../../utils/ViewHelper", 9 | "../ListItem/ListItem", 10 | "dijit/Tooltip", 11 | "dijit/_WidgetBase", 12 | "dijit/_TemplatedMixin", 13 | "dijit/_WidgetsInTemplateMixin", 14 | "dojo/text!./ViewIssuesToLink.html" 15 | ], function ( 16 | declare, 17 | array, 18 | lang, 19 | domConstruct, 20 | domStyle, 21 | MainDataStore, 22 | ViewHelper, 23 | ListItem, 24 | Tooltip, 25 | _WidgetBase, 26 | _TemplatedMixin, 27 | _WidgetsInTemplateMixin, 28 | template 29 | ) { 30 | return declare( 31 | "com.siemens.bt.jazz.workitemeditor.rtcGitConnector.ui.widget.viewIssuesToLink", 32 | [_WidgetBase, _TemplatedMixin, _WidgetsInTemplateMixin], 33 | { 34 | templateString: template, 35 | mainDataStore: null, 36 | 37 | constructor: function () { 38 | this.mainDataStore = MainDataStore.getInstance(); 39 | }, 40 | 41 | startup: function () { 42 | this.watchDataStore(); 43 | }, 44 | 45 | watchDataStore: function () { 46 | var self = this; 47 | 48 | // React when issues are added or removed from the issues to link list 49 | this.mainDataStore.selectedRepositoryData.issuesToLink.watchElements(function () { 50 | if (self.mainDataStore.selectedRepositoryData.issuesToLink.length > 0) { 51 | // show issues to link list 52 | domStyle.set("viewIssuesToLinkContainer", "display", "block"); 53 | domStyle.set("rtcGitConnectorIssuesListToLink", "width", "100%"); 54 | domStyle.set("rtcGitConnectorIssuesListToLink", "margin-right", "10px"); 55 | } else { 56 | // hide issues to link list 57 | domStyle.set("rtcGitConnectorIssuesListToLink", "width", "0"); 58 | domStyle.set("rtcGitConnectorIssuesListToLink", "margin-right", "0"); 59 | domStyle.set("viewIssuesToLinkContainer", "display", "none"); 60 | } 61 | 62 | self.drawIssuesToLink(self.mainDataStore.selectedRepositoryData.issuesToLink); 63 | }); 64 | }, 65 | 66 | // Draw the issues to link list in the view 67 | drawIssuesToLink: function (issuesToLink) { 68 | var self = this; 69 | var gitHost = this.mainDataStore.selectedRepositorySettings.get("gitHost"); 70 | domConstruct.empty(this.listItemsContainer); 71 | 72 | if (self.tooltip) { 73 | self.tooltip.destroy(); 74 | self.tooltip = null; 75 | } 76 | 77 | self.tooltip = new Tooltip({ 78 | position: ["above", "below"], 79 | showDelay: 0 80 | }); 81 | 82 | array.forEach(issuesToLink, function (issue) { 83 | var details; 84 | var buttonType; 85 | var duplicate = false; 86 | 87 | if (issue.id < 0) { 88 | var workItemTags = self.mainDataStore.workItem.getValue({ 89 | path: ["attributes", "internalTags", "content"] 90 | }); 91 | 92 | if (workItemTags.length && workItemTags.indexOf("created-as-git-issue") !== -1) { 93 | duplicate = true; 94 | self.tooltip.set("label", "This work item has already been created as a git issue."); 95 | } 96 | 97 | details = 98 | "This will create a new issue in " + 99 | gitHost.displayName + 100 | " using the information from the current work item"; 101 | buttonType = "times"; 102 | } else { 103 | if ( 104 | self.mainDataStore.newWorkItemMode && 105 | issue.labels && 106 | issue.labels.indexOf("created-as-rtc-work-item") !== -1 107 | ) { 108 | duplicate = true; 109 | self.tooltip.set("label", "This git issue has already been created as a work item."); 110 | } 111 | 112 | details = ViewHelper.GetIssueOrRequestDateString(issue); 113 | buttonType = "trash"; 114 | } 115 | 116 | var listItem = new ListItem(issue.id); 117 | listItem.set("title", issue.title); 118 | listItem.set("details", details); 119 | listItem.set("buttonType", buttonType); 120 | listItem.set("notClickable", true); 121 | listItem.set("duplicate", duplicate); 122 | listItem.set("buttonTitle", "Remove"); 123 | 124 | listItem.onButtonClick = lang.hitch(self, self.listItemButtonClick); 125 | 126 | issue.listItem = listItem; 127 | domConstruct.place(listItem.domNode, self.listItemsContainer); 128 | 129 | if (duplicate) { 130 | self.tooltip.addTarget(listItem.itemRightButton); 131 | } 132 | }); 133 | }, 134 | 135 | // Remove the issue with the specified id from the issues to link list in store and add to the issues list 136 | listItemButtonClick: function (itemId) { 137 | var selectedIssue = null; 138 | 139 | for (var i = this.mainDataStore.selectedRepositoryData.issuesToLink.length - 1; i >= 0; i--) { 140 | if (this.mainDataStore.selectedRepositoryData.issuesToLink[i].id == itemId) { 141 | selectedIssue = this.mainDataStore.selectedRepositoryData.issuesToLink.splice(i, 1)[0]; 142 | break; 143 | } 144 | } 145 | 146 | if ( 147 | selectedIssue && 148 | !this.mainDataStore.selectedRepositoryData.issues.find(function (issue) { 149 | return issue.id == selectedIssue.id; 150 | }) 151 | ) { 152 | this.mainDataStore.selectedRepositoryData.issues.push(selectedIssue); 153 | } 154 | } 155 | } 156 | ); 157 | }); 158 | -------------------------------------------------------------------------------- /resources/ui/widget/components/ViewRequestsToLink/ViewRequestsToLink.html: -------------------------------------------------------------------------------- 1 |
2 | 6 |
7 | -------------------------------------------------------------------------------- /resources/ui/widget/components/ViewRequestsToLink/ViewRequestsToLink.js: -------------------------------------------------------------------------------- 1 | define([ 2 | "dojo/_base/declare", 3 | "dojo/_base/array", 4 | "dojo/_base/lang", 5 | "dojo/dom-construct", 6 | "dojo/dom-style", 7 | "../../services/MainDataStore", 8 | "../../utils/ViewHelper", 9 | "../ListItem/ListItem", 10 | "dijit/_WidgetBase", 11 | "dijit/_TemplatedMixin", 12 | "dijit/_WidgetsInTemplateMixin", 13 | "dojo/text!./ViewRequestsToLink.html" 14 | ], function ( 15 | declare, 16 | array, 17 | lang, 18 | domConstruct, 19 | domStyle, 20 | MainDataStore, 21 | ViewHelper, 22 | ListItem, 23 | _WidgetBase, 24 | _TemplatedMixin, 25 | _WidgetsInTemplateMixin, 26 | template 27 | ) { 28 | return declare( 29 | "com.siemens.bt.jazz.workitemeditor.rtcGitConnector.ui.widget.viewRequestsToLink", 30 | [_WidgetBase, _TemplatedMixin, _WidgetsInTemplateMixin], 31 | { 32 | templateString: template, 33 | mainDataStore: null, 34 | 35 | constructor: function () { 36 | this.mainDataStore = MainDataStore.getInstance(); 37 | }, 38 | 39 | startup: function () { 40 | this.watchDataStore(); 41 | }, 42 | 43 | watchDataStore: function () { 44 | var self = this; 45 | 46 | // React when requests are added or removed from the requests to link list 47 | this.mainDataStore.selectedRepositoryData.requestsToLink.watchElements(function () { 48 | if (self.mainDataStore.selectedRepositoryData.requestsToLink.length > 0) { 49 | // show requests to link list 50 | domStyle.set("viewRequestsToLinkContainer", "display", "block"); 51 | domStyle.set("rtcGitConnectorRequestsListToLink", "width", "100%"); 52 | domStyle.set("rtcGitConnectorRequestsListToLink", "margin-right", "10px"); 53 | } else { 54 | // hide requests to link list 55 | domStyle.set("rtcGitConnectorRequestsListToLink", "width", "0"); 56 | domStyle.set("rtcGitConnectorRequestsListToLink", "margin-right", "0"); 57 | domStyle.set("viewRequestsToLinkContainer", "display", "none"); 58 | } 59 | 60 | self.drawRequestsToLink(self.mainDataStore.selectedRepositoryData.requestsToLink); 61 | }); 62 | }, 63 | 64 | // Draw the requests to link list in the view 65 | drawRequestsToLink: function (requestsToLink) { 66 | var self = this; 67 | domConstruct.empty(this.listItemsContainer); 68 | 69 | array.forEach(requestsToLink, function (request) { 70 | var listItem = new ListItem(request.id); 71 | listItem.set("title", request.title); 72 | listItem.set("details", ViewHelper.GetIssueOrRequestDateString(request)); 73 | listItem.set("buttonType", "trash"); 74 | listItem.set("notClickable", true); 75 | listItem.set("duplicate", false); 76 | listItem.set("buttonTitle", "Remove"); 77 | 78 | listItem.onButtonClick = lang.hitch(self, self.listItemButtonClick); 79 | 80 | request.listItem = listItem; 81 | domConstruct.place(listItem.domNode, self.listItemsContainer); 82 | }); 83 | }, 84 | 85 | // Remove the request with the specified id from the requests to link list in store and add to the requests list 86 | listItemButtonClick: function (itemId) { 87 | var selectedRequest = null; 88 | 89 | for (var i = this.mainDataStore.selectedRepositoryData.requestsToLink.length - 1; i >= 0; i--) { 90 | if (this.mainDataStore.selectedRepositoryData.requestsToLink[i].id == itemId) { 91 | selectedRequest = this.mainDataStore.selectedRepositoryData.requestsToLink.splice(i, 1)[0]; 92 | break; 93 | } 94 | } 95 | 96 | if ( 97 | selectedRequest && 98 | !this.mainDataStore.selectedRepositoryData.requests.find(function (request) { 99 | return request.id == selectedRequest.id; 100 | }) 101 | ) { 102 | this.mainDataStore.selectedRepositoryData.requests.push(selectedRequest); 103 | } 104 | } 105 | } 106 | ); 107 | }); 108 | -------------------------------------------------------------------------------- /resources/ui/widget/models/CommitModel.js: -------------------------------------------------------------------------------- 1 | define(["dojo/_base/declare"], function (declare) { 2 | var CommitModel = declare(null, { 3 | sha: null, // The full sha of the commit 4 | message: null, // The full commit message 5 | authorName: null, // The full name of the author 6 | authorEmail: null, // The email of the author 7 | authoredDate: null, // The date & time when the commit was made 8 | webUrl: null, // The web URL to view the commit 9 | alreadyLinked: null // True if already linked to the current work item 10 | }); 11 | 12 | // Return an instance so that the functions can be used as if they were static 13 | return new (function () { 14 | // Create a CommitModel object from a GitHub commit object 15 | this.CreateFromGitHubCommit = function (gitHubCommit, alreadyLinkedUrls) { 16 | var commitModel = new CommitModel(); 17 | commitModel.sha = gitHubCommit.sha; 18 | commitModel.message = gitHubCommit.commit.message; 19 | commitModel.authorName = gitHubCommit.commit.author.name; 20 | commitModel.authorEmail = gitHubCommit.commit.author.email; 21 | commitModel.authoredDate = gitHubCommit.commit.author.date; 22 | commitModel.webUrl = gitHubCommit.html_url; 23 | commitModel.alreadyLinked = alreadyLinkedUrls.indexOf(commitModel.webUrl.toLowerCase()) > -1; 24 | 25 | return commitModel; 26 | }; 27 | 28 | // Create a CommitModel object from a GitLab commit object 29 | this.CreateFromGitLabCommit = function (gitLabCommit, commitUrlPath, alreadyLinkedUrls) { 30 | var commitModel = new CommitModel(); 31 | commitModel.sha = gitLabCommit.id; 32 | commitModel.message = gitLabCommit.message; 33 | commitModel.authorName = gitLabCommit.author_name; 34 | commitModel.authorEmail = gitLabCommit.author_email; 35 | commitModel.authoredDate = gitLabCommit.authored_date; 36 | commitModel.webUrl = commitUrlPath + commitModel.sha; 37 | commitModel.alreadyLinked = alreadyLinkedUrls.indexOf(commitModel.webUrl.toLowerCase()) > -1; 38 | 39 | return commitModel; 40 | }; 41 | })(); 42 | }); 43 | -------------------------------------------------------------------------------- /resources/ui/widget/models/IssueModel.js: -------------------------------------------------------------------------------- 1 | define(["dojo/_base/declare"], function (declare) { 2 | var IssueModel = declare(null, { 3 | id: null, // The issue id in the web UI 4 | title: null, // The title of the issue 5 | description: null, // The description of the issue 6 | labels: null, // A comma separated string with all the labels (spaces are replaced with "-") 7 | labelsWithSpaces: null, // Same as labels but without replacing spaces 8 | milestone: null, // The name of the milestone assigned to the issue 9 | state: null, // The state of the issue 10 | openedBy: null, // The user that opened the issue (user name or real name) 11 | openedDate: null, // The date & time when the issue was opened 12 | webUrl: null, // The web URL to view the issue 13 | apiUrl: null, // The api URL to view the issue 14 | alreadyLinked: null, // True if already linked to the current work item 15 | service: null, // Required for building the work item link urls TODO: find a nicer way to solve this... 16 | type: "issue", 17 | projectId: null, 18 | iid: null, 19 | linkUrl: null // URL generated for custom hover view and information service 20 | }); 21 | 22 | // Return an instance so that the functions can be used as if they were static 23 | return new (function () { 24 | // Create an IssueModel object from a GitHub issue object 25 | this.CreateFromGitHubIssue = function (gitHubIssue, alreadyLinkedUrls) { 26 | var issueModel = new IssueModel(); 27 | issueModel.id = gitHubIssue.number; 28 | issueModel.title = gitHubIssue.title; 29 | issueModel.description = gitHubIssue.body; 30 | issueModel.state = gitHubIssue.state; 31 | issueModel.openedBy = gitHubIssue.user.login; 32 | issueModel.openedDate = gitHubIssue.created_at; 33 | issueModel.webUrl = gitHubIssue.html_url; 34 | issueModel.apiUrl = gitHubIssue.url; 35 | // TODO: this needs to be adjusted here and everywhere else to properly find the duplicates 36 | issueModel.alreadyLinked = alreadyLinkedUrls.indexOf(issueModel.webUrl.toLowerCase()) > -1; 37 | issueModel.service = "github"; 38 | issueModel.milestone = gitHubIssue.milestone && gitHubIssue.milestone.title; 39 | 40 | if (gitHubIssue.labels && gitHubIssue.labels.length) { 41 | issueModel.labels = gitHubIssue.labels 42 | .map(function (label) { 43 | return label.name.replace(/ /g, "-"); 44 | }) 45 | .join(", "); 46 | issueModel.labelsWithSpaces = gitHubIssue.labels 47 | .map(function (label) { 48 | return label.name; 49 | }) 50 | .join(", "); 51 | } 52 | 53 | return issueModel; 54 | }; 55 | 56 | // Create an IssueModel object from a GitLab issue object 57 | this.CreateFromGitLabIssue = function (gitLabIssue, alreadyLinkedUrls) { 58 | var issueModel = new IssueModel(); 59 | issueModel.id = gitLabIssue.iid; 60 | issueModel.title = gitLabIssue.title; 61 | issueModel.description = gitLabIssue.description; 62 | issueModel.state = gitLabIssue.state; 63 | issueModel.openedBy = gitLabIssue.author.name; 64 | issueModel.openedDate = gitLabIssue.created_at; 65 | issueModel.webUrl = gitLabIssue.web_url; 66 | issueModel.service = "gitlab"; 67 | issueModel.projectId = gitLabIssue.project_id; 68 | issueModel.iid = gitLabIssue.iid; 69 | issueModel.linkUrl = 70 | net.jazz.ajax._contextRoot + 71 | "/service/org.jazzcommunity.GitConnectorService.IGitConnectorService" + 72 | "/" + 73 | "gitlab" + 74 | "/" + 75 | new URL(issueModel.webUrl).hostname + 76 | "/project/" + 77 | issueModel.projectId + 78 | "/" + 79 | issueModel.type + 80 | "/" + 81 | issueModel.iid + 82 | "/link"; 83 | var lowerCaseLinkUrl = issueModel.linkUrl.toLowerCase(); 84 | issueModel.alreadyLinked = alreadyLinkedUrls.some(function (alreadyLinkedUrl) { 85 | return alreadyLinkedUrl.indexOf(lowerCaseLinkUrl) > -1; 86 | }); 87 | issueModel.milestone = gitLabIssue.milestone && gitLabIssue.milestone.title; 88 | 89 | if (gitLabIssue.labels && gitLabIssue.labels.length) { 90 | issueModel.labels = gitLabIssue.labels 91 | .map(function (label) { 92 | return label.replace(/ /g, "-"); 93 | }) 94 | .join(", "); 95 | issueModel.labelsWithSpaces = gitLabIssue.labels.join(", "); 96 | } 97 | 98 | return issueModel; 99 | }; 100 | })(); 101 | }); 102 | -------------------------------------------------------------------------------- /resources/ui/widget/models/RequestModel.js: -------------------------------------------------------------------------------- 1 | define(["dojo/_base/declare"], function (declare) { 2 | var RequestModel = declare(null, { 3 | id: null, // The request id in the web UI 4 | title: null, // The title of the request 5 | labels: null, // A comma separated string with all the labels 6 | milestone: null, // The name of the milestone assigned to the request 7 | state: null, // The state of the request 8 | openedBy: null, // The user that opened the request (user name or real name) 9 | openedDate: null, // The date & time when the request was opened 10 | webUrl: null, // The web URL to view the request 11 | apiUrl: null, // The api URL to view the request 12 | alreadyLinked: null, // True if already linked to the current work item 13 | service: null, 14 | type: null, 15 | projectId: null, 16 | iid: null, 17 | linkUrl: null 18 | }); 19 | 20 | // Return an instance so that the functions can be used as if they were static 21 | return new (function () { 22 | // Create a RequestModel object from a GitHub request object 23 | this.CreateFromGitHubRequest = function (gitHubRequest, alreadyLinkedUrls) { 24 | var requestModel = new RequestModel(); 25 | requestModel.id = gitHubRequest.number; 26 | requestModel.title = gitHubRequest.title; 27 | requestModel.state = gitHubRequest.state; 28 | requestModel.openedBy = gitHubRequest.user.login; 29 | requestModel.openedDate = gitHubRequest.created_at; 30 | requestModel.webUrl = gitHubRequest.html_url; 31 | requestModel.apiUrl = gitHubRequest.url; 32 | requestModel.service = "github"; 33 | requestModel.alreadyLinked = alreadyLinkedUrls.indexOf(requestModel.webUrl.toLowerCase()) > -1; 34 | requestModel.milestone = gitHubRequest.milestone && gitHubRequest.milestone.title; 35 | 36 | if (gitHubRequest.labels && gitHubRequest.labels.length) { 37 | requestModel.labels = gitHubRequest.labels 38 | .map(function (label) { 39 | return label.name; 40 | }) 41 | .join(", "); 42 | } 43 | 44 | return requestModel; 45 | }; 46 | 47 | // Create a RequestModel object from a GitLab request object 48 | this.CreateFromGitLabRequest = function (gitLabRequest, alreadyLinkedUrls) { 49 | var requestModel = new RequestModel(); 50 | requestModel.id = gitLabRequest.iid; 51 | requestModel.title = gitLabRequest.title; 52 | requestModel.state = gitLabRequest.state; 53 | requestModel.openedBy = gitLabRequest.author.name; 54 | requestModel.openedDate = gitLabRequest.created_at; 55 | requestModel.webUrl = gitLabRequest.web_url; 56 | requestModel.service = "gitlab"; 57 | requestModel.type = "merge-request"; 58 | requestModel.projectId = gitLabRequest.project_id; 59 | requestModel.iid = gitLabRequest.iid; 60 | requestModel.linkUrl = 61 | net.jazz.ajax._contextRoot + 62 | "/service/org.jazzcommunity.GitConnectorService.IGitConnectorService" + 63 | "/" + 64 | "gitlab" + 65 | "/" + 66 | new URL(requestModel.webUrl).hostname + 67 | "/project/" + 68 | requestModel.projectId + 69 | "/" + 70 | requestModel.type + 71 | "/" + 72 | requestModel.iid + 73 | "/link"; 74 | var lowerCaseLinkUrl = requestModel.linkUrl.toLowerCase(); 75 | requestModel.alreadyLinked = alreadyLinkedUrls.some(function (alreadyLinkedUrl) { 76 | return alreadyLinkedUrl.indexOf(lowerCaseLinkUrl) > -1; 77 | }); 78 | requestModel.milestone = gitLabRequest.milestone && gitLabRequest.milestone.title; 79 | 80 | if (gitLabRequest.labels && gitLabRequest.labels.length) { 81 | requestModel.labels = gitLabRequest.labels.join(", "); 82 | } 83 | 84 | return requestModel; 85 | }; 86 | })(); 87 | }); 88 | -------------------------------------------------------------------------------- /resources/ui/widget/services/MainDataStore.js: -------------------------------------------------------------------------------- 1 | define(["dojo/_base/declare", "dojo/Stateful", "dojox/mvc/StatefulArray"], function (declare, Stateful, StatefulArray) { 2 | var _instance = null; 3 | var MainDataStore = declare(null, { 4 | newWorkItemMode: null, 5 | workItem: null, 6 | projectArea: null, 7 | registeredGitRepositories: null, 8 | selectedRepositorySettings: { 9 | repository: null, // Object from registeredGitRepositories 10 | gitHost: { 11 | name: "", // Name is uppercase "GITHUB", "GITLAB", "OTHER" 12 | displayName: "", 13 | requestPrefix: "" 14 | }, 15 | accessToken: null, // For GitHub or GitLab 16 | linkType: null, // Uppercase "COMMIT", "ISSUE", "REQUEST" 17 | commitsLoaded: false, 18 | commitsLoading: false, 19 | commitsLoadError: null, 20 | issuesLoaded: false, 21 | issuesLoading: false, 22 | issuesLoadError: null, 23 | requestsLoaded: false, 24 | requestsLoading: false, 25 | requestsLoadError: null 26 | }, 27 | selectedRepositoryData: { 28 | commits: null, 29 | issues: null, 30 | requests: null, 31 | commitsToLink: null, 32 | issuesToLink: null, 33 | requestsToLink: null 34 | }, 35 | currentUserId: null, 36 | hasHiddenChanges: false, 37 | 38 | constructor: function () { 39 | this.registeredGitRepositories = new StatefulArray([]); 40 | this.selectedRepositorySettings = new Stateful(this.selectedRepositorySettings); 41 | this.selectedRepositoryData.commits = new StatefulArray([]); 42 | this.selectedRepositoryData.issues = new StatefulArray([]); 43 | this.selectedRepositoryData.requests = new StatefulArray([]); 44 | this.selectedRepositoryData.commitsToLink = new StatefulArray([]); 45 | this.selectedRepositoryData.issuesToLink = new StatefulArray([]); 46 | this.selectedRepositoryData.requestsToLink = new StatefulArray([]); 47 | }, 48 | 49 | // Rest settings and data for the selected repository 50 | resetSelectedRepository: function () { 51 | this.selectedRepositorySettings.set("gitHost", { 52 | name: "", 53 | displayName: "", 54 | requestPrefix: "" 55 | }); 56 | this.selectedRepositorySettings.set("accessToken", null); 57 | this.selectedRepositorySettings.set("linkType", null); 58 | this.selectedRepositorySettings.set("commitsLoaded", false); 59 | this.selectedRepositorySettings.set("commitsLoading", false); 60 | this.selectedRepositorySettings.set("commitsLoadError", null); 61 | this.selectedRepositorySettings.set("issuesLoaded", false); 62 | this.selectedRepositorySettings.set("issuesLoading", false); 63 | this.selectedRepositorySettings.set("issuesLoadError", null); 64 | this.selectedRepositorySettings.set("requestsLoaded", false); 65 | this.selectedRepositorySettings.set("requestsLoading", false); 66 | this.selectedRepositorySettings.set("requestsLoadError", null); 67 | this.selectedRepositoryData.commits.length = 0; 68 | this.selectedRepositoryData.issues.length = 0; 69 | this.selectedRepositoryData.requests.length = 0; 70 | this.selectedRepositoryData.commitsToLink.splice(0, this.selectedRepositoryData.commitsToLink.length); 71 | this.selectedRepositoryData.issuesToLink.splice(0, this.selectedRepositoryData.issuesToLink.length); 72 | this.selectedRepositoryData.requestsToLink.splice(0, this.selectedRepositoryData.requestsToLink.length); 73 | } 74 | }); 75 | 76 | // Returns an instance so that you don't need to instantiate this class. 77 | // It's functions can be called directly after importing. Example: 78 | // MainDataStore.getInstance(); 79 | // MainDataStore.destroyInstance(); 80 | // 81 | // This is basically a singleton that can be asked to use a new instance when needed 82 | return new (function () { 83 | // Gets the existing instance or creates one if none exists (singleton) 84 | this.getInstance = function () { 85 | if (!_instance) { 86 | _instance = new MainDataStore(); 87 | } 88 | 89 | return _instance; 90 | }; 91 | 92 | // Destroys the existing instance. It doesn't matter if none exists. 93 | // This causes the next call to getInstance to create a new instance 94 | this.destroyInstance = function () { 95 | _instance = null; 96 | }; 97 | })(); 98 | }); 99 | -------------------------------------------------------------------------------- /resources/ui/widget/services/TemplateService.js: -------------------------------------------------------------------------------- 1 | define([ 2 | "dojo/_base/declare", 3 | "com.siemens.bt.jazz.workitemeditor.rtcGitConnector|dist|Handlebars.js", 4 | "com.siemens.bt.jazz.workitemeditor.rtcGitConnector|dist|JustHandlebarsHelpers.js", 5 | "com.siemens.bt.jazz.workitemeditor.rtcGitConnector|dist|TurndownService.js" 6 | ], function (declare, Handlebars, JustHandlebarsHelpers, TurndownService) { 7 | // This syntax is used to get around the Jazz JavaScript parser 8 | var TurndownService = TurndownService["default"]; 9 | return declare(null, { 10 | constructor: function () { 11 | var turndownService = new TurndownService(); 12 | 13 | this._doNotEscapeMarkdown(turndownService); 14 | this._registerHtmlToMarkdownHelper(turndownService); 15 | this._registerJustHandlebarsHelpers(); 16 | }, 17 | 18 | renderTemplateWithWorkItem: function (templateString, workItem) { 19 | var template = Handlebars.compile(templateString); 20 | return template(workItem.object); 21 | }, 22 | 23 | _doNotEscapeMarkdown: function (turndownService) { 24 | // Override the escape method so that markdown is not escaped 25 | // when converting HTML to Markdown 26 | turndownService.escape = function (input) { 27 | return input; 28 | }; 29 | }, 30 | 31 | _registerHtmlToMarkdownHelper: function (turndownService) { 32 | Handlebars.registerHelper("turndown", function (inputString) { 33 | // Convert HTML to Markdown. First replace non-breaking spaces with normal ones. 34 | // The HTML editor in Jazz creates non-breaking spaces when there are multiple spaces in a row. 35 | // For the Markdown formatting to work correctly, these need to be normal spaces. 36 | return inputString ? turndownService.turndown(inputString.replace(/ /g, " ")) : ""; 37 | }); 38 | }, 39 | 40 | _registerJustHandlebarsHelpers: function () { 41 | // Register all the helpers in the just-handlebars-helpers package 42 | JustHandlebarsHelpers.registerHelpers(Handlebars); 43 | } 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /resources/ui/widget/utils/ViewHelper.js: -------------------------------------------------------------------------------- 1 | define(["dojo/_base/declare", "dojo/dom-construct", "dojo/query", "dijit/registry"], function ( 2 | declare, 3 | domConstruct, 4 | query, 5 | registry 6 | ) { 7 | // Return an instance so that the functions can be used as if they were static 8 | return new (function () { 9 | var self = this; 10 | 11 | // Sort a list of items by the date contained in the specified property 12 | this.SortListDataByDate = function (dateProperty, listData) { 13 | // Create a temp array so that the date objects are only created once 14 | var tempArray = listData.map(function (el, i) { 15 | return { 16 | index: i, 17 | value: new Date(el[dateProperty]).getTime() 18 | }; 19 | }); 20 | 21 | // Sort the temp array 22 | tempArray.sort(function (a, b) { 23 | return b.value - a.value; 24 | }); 25 | 26 | // Get a sorted version of the original array 27 | var sortedArray = tempArray.map(function (el) { 28 | return listData[el.index]; 29 | }); 30 | 31 | // Return the sorted array 32 | return sortedArray; 33 | }; 34 | 35 | // Filter a list of items by the specified properties and 36 | // highlight the text where found 37 | this.FilterListDataByText = function (filterText, filterBy, filterResult) { 38 | filterText = filterText.toLowerCase(); 39 | return filterResult.filter(function (item) { 40 | var keepItem = false; 41 | for (var i = 0; i < filterBy.length; i++) { 42 | if (item[filterBy[i]] && item[filterBy[i]].toString().toLowerCase().indexOf(filterText) > -1) { 43 | item[filterBy[i]] = self.HighlightTextInString(filterText, item[filterBy[i]].toString()); 44 | keepItem = true; 45 | } 46 | } 47 | return keepItem; 48 | }); 49 | }; 50 | 51 | this.HighlightTextInString = function (searchText, fullText) { 52 | var startIndex; 53 | if (searchText && (startIndex = fullText.toLowerCase().indexOf(searchText)) > -1) { 54 | var beforeFound = fullText.slice(0, startIndex); 55 | var found = fullText.slice(startIndex, startIndex + searchText.length); 56 | var afterFound = self.HighlightTextInString(searchText, fullText.slice(startIndex + searchText.length)); 57 | fullText = beforeFound + "" + found + "" + afterFound; 58 | } 59 | return fullText; 60 | }; 61 | 62 | // Create a string with information about who created the commit and when 63 | this.GetCommitDateString = function (commit) { 64 | var commitDateString; 65 | 66 | if (commit.authoredDate) { 67 | commitDateString = 68 | commit.authorName + " committed on " + self.GetFormattedDateFromString(commit.authoredDate); 69 | } else { 70 | commitDateString = " "; 71 | } 72 | 73 | return commitDateString; 74 | }; 75 | 76 | // Create a string with information about who created the issue or request and when 77 | this.GetIssueOrRequestDateString = function (issueOrRequest) { 78 | var issueOrRequestDateString; 79 | 80 | if (issueOrRequest.openedDate) { 81 | issueOrRequestDateString = 82 | "#" + 83 | issueOrRequest.id + 84 | " opened by " + 85 | issueOrRequest.openedBy + 86 | " on " + 87 | self.GetFormattedDateFromString(issueOrRequest.openedDate); 88 | } else { 89 | issueOrRequestDateString = " "; 90 | } 91 | 92 | return issueOrRequestDateString; 93 | }; 94 | 95 | // Create and format a date from a string 96 | this.GetFormattedDateFromString = function (dateString) { 97 | var dateObject = new Date(dateString); 98 | return ( 99 | dateObject.toDateString() + 100 | " at " + 101 | self.FrontPadWithZeros(dateObject.getHours()) + 102 | ":" + 103 | self.FrontPadWithZeros(dateObject.getMinutes()) 104 | ); 105 | }; 106 | 107 | // Add zeros to the front if the passed in time has less than two digits 108 | this.FrontPadWithZeros = function (hoursOrMinutes) { 109 | return ("00" + hoursOrMinutes).slice(-2); 110 | }; 111 | })(); 112 | }); 113 | -------------------------------------------------------------------------------- /src/CommitLinkEncoder.js: -------------------------------------------------------------------------------- 1 | define(function () { 2 | // Return an instance so that the functions can be used as if they were static 3 | return new (function () { 4 | const base64js = require("base64-js"); 5 | const Buffer = require("buffer/").Buffer; 6 | const pako = require("pako"); 7 | 8 | // Replace some characters in the input string (needed due to the strange implementation) 9 | function base64UrlEncode(string) { 10 | string = string.replace(/\+/g, "-"); 11 | string = string.replace(/\//g, "_"); 12 | string = string.replace(/=/g, "."); 13 | return string; 14 | } 15 | 16 | // Replace some characters in the input string (needed due to the strange implementation) 17 | function base64UrlDecode(string) { 18 | string = string.replace(/-/g, "+"); 19 | string = string.replace(/_/g, "/"); 20 | string = string.replace(/\./g, "="); 21 | return string; 22 | } 23 | 24 | // Creates an encoded string from a stringified json object 25 | this.encode = function (value) { 26 | const compressed = pako.gzip(Buffer.from(value)); 27 | const encoded = base64js.fromByteArray(compressed); 28 | const urlEncoded = base64UrlEncode(encoded); 29 | return urlEncoded; 30 | }; 31 | 32 | // Creates a stringified json object from the encoded string 33 | this.decode = function (value) { 34 | const urlDecoded = base64UrlDecode(value); 35 | const compressed = base64js.toByteArray(urlDecoded); 36 | const deflated = pako.ungzip(compressed); 37 | const buffer = Buffer.from(deflated); 38 | return buffer.toString(); 39 | }; 40 | })(); 41 | }); 42 | -------------------------------------------------------------------------------- /src/FontAwesomeProvider.js: -------------------------------------------------------------------------------- 1 | define(function () { 2 | // Return an instance so that the functions can be used as if they were static 3 | return new (function () { 4 | this.FontAwesome = require("@fortawesome/fontawesome"); 5 | 6 | const FaCheck = require("@fortawesome/fontawesome-free-solid/faCheck"); 7 | const FaExclamationTriangle = require("@fortawesome/fontawesome-free-solid/faExclamationTriangle"); 8 | const FaLink = require("@fortawesome/fontawesome-free-solid/faLink"); 9 | const FaMinus = require("@fortawesome/fontawesome-free-solid/faMinus"); 10 | const FaPlus = require("@fortawesome/fontawesome-free-solid/faPlus"); 11 | const FaSpinner = require("@fortawesome/fontawesome-free-solid/faSpinner"); 12 | const FaTimes = require("@fortawesome/fontawesome-free-solid/faTimes"); 13 | const FaTrash = require("@fortawesome/fontawesome-free-solid/faTrash"); 14 | 15 | this.FontAwesome.library.add(FaCheck); 16 | this.FontAwesome.library.add(FaExclamationTriangle); 17 | this.FontAwesome.library.add(FaLink); 18 | this.FontAwesome.library.add(FaMinus); 19 | this.FontAwesome.library.add(FaPlus); 20 | this.FontAwesome.library.add(FaSpinner); 21 | this.FontAwesome.library.add(FaTimes); 22 | this.FontAwesome.library.add(FaTrash); 23 | })(); 24 | }); 25 | -------------------------------------------------------------------------------- /src/GitLabApiProvider.js: -------------------------------------------------------------------------------- 1 | define(function () { 2 | // Return an instance so that the functions can be used as if they were static 3 | return new (function () { 4 | this.GitLabApi = require("@gitbeaker/rest"); 5 | })(); 6 | }); 7 | -------------------------------------------------------------------------------- /src/theme/customMenuItems.js: -------------------------------------------------------------------------------- 1 | import CustomMenuItemsConfig from "./customMenuItemsConfig"; 2 | 3 | // This method only works for the work item menu for now. 4 | // Other menus use the _currentMenu._menu property instead of the _currentMenu._wrappedInstance (and not in the same way). 5 | // This could easily be extended to support such menus as well. 6 | 7 | // Copies the functionality in the addGroup method additionally allowing for 8 | // a groupIndex to be specified. The new group will be added at the specified groupIndex. 9 | function customAddGroup(id, title, groupIndex) { 10 | var titleFunc = 11 | title == null 12 | ? null 13 | : function () { 14 | return title; 15 | }; 16 | var group = { 17 | id: id, 18 | title: titleFunc, 19 | children: [], 20 | dynamic: false, 21 | hideIfEmpty: false 22 | }; 23 | this._groups.splice(groupIndex, 0, group); 24 | this._updateGroup(group); 25 | } 26 | 27 | // Add a new custom group from the config to the menu. 28 | // An example of a menu group is the light gray "Create Work Item" text in the "Work Items" menu. 29 | function addNewGroupToMenu(menuPopupConfig) { 30 | menuPopupConfig.addGroups.forEach(function (groupToAdd) { 31 | // Get the menu items for the group. Depending on the configuration there may not be any. 32 | var menuItems = groupToAdd.createMenuItems(); 33 | 34 | // Don't add the group if there are no menu items to put in it. 35 | // This will happen if there are no configured types or if the configured types are not 36 | // supported in the current context (project area). 37 | if (!menuItems || !menuItems.length) { 38 | return; 39 | } 40 | 41 | // Check if the custom group is already in the menu. 42 | if ( 43 | jazz.app.currentApplication.ui.navbar._pageList._menuPopupsById[ 44 | menuPopupConfig.id 45 | ]._currentMenu._wrappedInstance._groups.findIndex(function (group) { 46 | return group.id === groupToAdd.id; 47 | }) > -1 48 | ) { 49 | // Clear the group if it already exists (removes all items). 50 | jazz.app.currentApplication.ui.navbar._pageList._menuPopupsById[ 51 | menuPopupConfig.id 52 | ]._currentMenu._wrappedInstance.clearGroup(groupToAdd.id); 53 | } else { 54 | // Create a new group and add it if it's not in the menu yet. 55 | 56 | // Look for the index of the group below which the new group will be placed. 57 | var addAfterGroupIndex = jazz.app.currentApplication.ui.navbar._pageList._menuPopupsById[ 58 | menuPopupConfig.id 59 | ]._currentMenu._wrappedInstance._groups.findIndex(function (group) { 60 | return group.id === groupToAdd.addAfterGroupId; 61 | }); 62 | 63 | // Check if the group that should precede the new group is in the menu. 64 | if (addAfterGroupIndex) { 65 | // Add the new group right after the preceding group. 66 | customAddGroup.call( 67 | jazz.app.currentApplication.ui.navbar._pageList._menuPopupsById[menuPopupConfig.id]._currentMenu 68 | ._wrappedInstance, 69 | groupToAdd.id, 70 | groupToAdd.title, 71 | addAfterGroupIndex + 1 72 | ); 73 | } else { 74 | // If the configured preceding group wasn't found in the menu the new group will be added as the last group. 75 | // This should only happen if something is configured incorrectly. 76 | jazz.app.currentApplication.ui.navbar._pageList._menuPopupsById[ 77 | menuPopupConfig.id 78 | ]._currentMenu._wrappedInstance.addGroup(groupToAdd.id, groupToAdd.title); 79 | } 80 | } 81 | 82 | // Add the custom menu items to the new group. 83 | menuItems.forEach(function (newItem) { 84 | jazz.app.currentApplication.ui.navbar._pageList._menuPopupsById[ 85 | menuPopupConfig.id 86 | ]._currentMenu._wrappedInstance.addItemToGroup(groupToAdd.id, { 87 | label: newItem.label, 88 | iconClass: newItem.iconClass, 89 | href: newItem.href 90 | }); 91 | }); 92 | }); 93 | } 94 | 95 | // Add the groups from the menu popup config once the work item types are loaded. 96 | // The work item types are added to the cache when the menu first loads and remain 97 | // there across browser refreshes. 98 | function addGroupWhenTypesAreLoaded(menuPopupConfig) { 99 | (function tryToAdd(menuPopupConfig) { 100 | // Add the group to the menu if the types are loaded. 101 | if ( 102 | jazz.app.currentApplication.ui.navbar._pageList._menuPopupsById[menuPopupConfig.id]._currentMenu 103 | ._wrappedInstance.isTypesLoaded 104 | ) { 105 | addNewGroupToMenu(menuPopupConfig); 106 | } else { 107 | // Otherwise wait 100ms and run again. 108 | setTimeout(function () { 109 | tryToAdd(menuPopupConfig); 110 | }, 100); 111 | } 112 | })(menuPopupConfig); 113 | } 114 | 115 | // Connect to the dijit popup open event as soon as this script is loaded. 116 | dojo.connect(dijit.popup, "open", function (arg) { 117 | var config = CustomMenuItemsConfig.getCustomMenuItemsConfig(); 118 | 119 | if (typeof config === "undefined") { 120 | return; 121 | } 122 | 123 | // Run the setup for each configured menu popup. 124 | // This allows for multiple menus to be configured using this method. 125 | config.menuPopups.forEach(function (menuPopupConfig) { 126 | var typeofCurrentMenu; 127 | 128 | try { 129 | typeofCurrentMenu = 130 | typeof jazz.app.currentApplication.ui.navbar._pageList._menuPopupsById[menuPopupConfig.id]._currentMenu; 131 | } catch (e) { 132 | typeofCurrentMenu = "undefined"; 133 | } 134 | 135 | // Make sure that the menu that we're trying to extend actually exists and that it's the popup that caused this event to fire. 136 | if ( 137 | typeofCurrentMenu !== "undefined" && 138 | arg.popup === 139 | jazz.app.currentApplication.ui.navbar._pageList._menuPopupsById[menuPopupConfig.id]._currentMenu 140 | ) { 141 | // If the menu already has an instance we can try to add right away. 142 | if ( 143 | jazz.app.currentApplication.ui.navbar._pageList._menuPopupsById[menuPopupConfig.id]._currentMenu 144 | ._wrappedInstance 145 | ) { 146 | addGroupWhenTypesAreLoaded(menuPopupConfig); 147 | } else { 148 | // If the menu hasn't loaded yet (first time it's clicked), wait for it to load and then try to add. 149 | jazz.app.currentApplication.ui.navbar._pageList._menuPopupsById[ 150 | menuPopupConfig.id 151 | ]._currentMenu._loaded.then(function () { 152 | addGroupWhenTypesAreLoaded(menuPopupConfig); 153 | }); 154 | } 155 | } 156 | }); 157 | }); 158 | -------------------------------------------------------------------------------- /src/theme/customMenuItemsConfig.js: -------------------------------------------------------------------------------- 1 | import VisibleWorkItemTypeIds from "./visibleWorkItemTypeIds"; 2 | 3 | export default { 4 | // Gets the configuration that specifies which menu popup to customize and 5 | // which new groups should be added to the specified menu. 6 | // Also provides a method for creating the menu items. 7 | getCustomMenuItemsConfig() { 8 | return { 9 | menuPopups: [ 10 | { 11 | id: "com.ibm.team.workitem", // The id of the menu popup to customize. 12 | addGroups: [ 13 | { 14 | id: "create-workitem-from-git-group", // The id of the new group to add to the menu. 15 | title: "Create Work Item from Git Issue", // The display title for the new group. 16 | addAfterGroupId: "create-workitem-group", // The group after which the new group should be placed. 17 | createMenuItems: function () { 18 | // A function that returns an array of menu items to add. 19 | // Create the menu items from the configured work item type ids. 20 | return createGitIssueMenuItems(VisibleWorkItemTypeIds.getVisibleWorkItemTypeIds()); 21 | } 22 | } 23 | ] 24 | } 25 | ] 26 | }; 27 | } 28 | }; 29 | 30 | // Create menu items for the specified work item type ids. 31 | function createGitIssueMenuItems(visibleWorkItemTypeIds) { 32 | // Get the available work item types from the cache. 33 | var workItemTypesFromCache = getItemTypes(); 34 | 35 | // Return an empty array if there are no work item types in the cache. 36 | if (!workItemTypesFromCache) { 37 | return []; 38 | } 39 | 40 | // Filter for the work item types that are specified with the work item type ids. 41 | // Keep the results in the order used in the visibleWorkItemTypeIds file. 42 | var visibleWorkItemTypes = visibleWorkItemTypeIds.reduce(function (output, workItemTypeId) { 43 | var workItemType = workItemTypesFromCache.find(function (workItemType) { 44 | return workItemType.id === workItemTypeId; 45 | }); 46 | 47 | if (workItemType) { 48 | output.push(workItemType); 49 | } 50 | 51 | return output; 52 | }, []); 53 | 54 | // Return the items with a label, iconClass, and link. 55 | // The link contains the parameter "autoOpenRtcGitConnector" which will cause the 56 | // plugin to automatically open when clicking the link. 57 | return visibleWorkItemTypes.map(function (workItemType) { 58 | return { 59 | label: workItemType.label + " From Git Issue", 60 | iconClass: getIconClass(workItemType.iconUrl), 61 | href: 62 | jazz.app.currentApplication.ui.navbar._pageList._menuPopupsById[ 63 | "com.ibm.team.workitem" 64 | ]._currentMenu._wrappedInstance._getNewWorkItemUri(workItemType.id) + "&autoOpenRtcGitConnector=true" 65 | }; 66 | }); 67 | } 68 | 69 | // Get the work item types from the cache. Will return null if there was an error getting them. 70 | function getItemTypes() { 71 | var itemTypes; 72 | 73 | try { 74 | itemTypes = com.ibm.team.workitem.web.cache.internal.Cache.getItem("workitems/itemTypes"); 75 | } catch (e) { 76 | itemTypes = null; 77 | } 78 | 79 | return itemTypes; 80 | } 81 | 82 | // Get the icon class using the icon url. Will return an empty string if it fails. 83 | function getIconClass(iconUrl) { 84 | var iconClass; 85 | 86 | try { 87 | iconClass = com.ibm.team.rtc.foundation.web.ui.util.Sprites.cssClassName( 88 | com.ibm.team.rtc.foundation.web.ui.util.sprite.ImageSource.fromIconUri(iconUrl) 89 | ); 90 | } catch (e) { 91 | iconClass = ""; 92 | } 93 | 94 | return iconClass; 95 | } 96 | -------------------------------------------------------------------------------- /src/theme/visibleWorkItemTypeIds.js: -------------------------------------------------------------------------------- 1 | // This file contains the ids of all work item types to list in the menu under the 2 | // group "Create Work Item from Git Issue". 3 | 4 | // Adjust the list to contain the work item types that should be available in the list. 5 | // Make sure to use the ids that are configured in your setup. 6 | // Ids that are listed here but not available in the web ui will simply be left out of 7 | // the list and will not cause an error. 8 | 9 | // The menu items will be displayed in the same order as the ids in the list below. 10 | export default { 11 | getVisibleWorkItemTypeIds() { 12 | return ["defect", "com.ibm.team.workitem.workItemType.defect", "com.ibm.team.apt.workItemType.story"]; 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const RemovePlugin = require("remove-files-webpack-plugin"); 2 | const ZipPlugin = require("zip-webpack-plugin"); 3 | const moment = require("moment"); 4 | const packageJson = require("./package.json"); 5 | 6 | const version = packageJson.version + "_" + (process.env.BUILD_TIMESTAMP || moment().format("YYYYMMDD[-]HHmm")); 7 | const config = { 8 | target: ["web", "es5"], 9 | resolve: { 10 | fallback: { 11 | "https": false 12 | } 13 | }, 14 | externalsType: "var", 15 | externals: { 16 | "currencyformatter.js": "currencyformatter.js", 17 | "moment/min/moment-with-locales": "moment", 18 | "sprintf-js": "sprintf-js" 19 | }, 20 | entry: { 21 | CommitLinkEncoder: ["./src/CommitLinkEncoder.js"], 22 | FontAwesomeProvider: ["./src/FontAwesomeProvider.js"], 23 | ClipboardJS: ["clipboard"], 24 | GitHubApi: ["@octokit/rest"], 25 | GitLabApiProvider: ["./src/GitLabApiProvider.js"], 26 | Handlebars: ["handlebars"], 27 | JustHandlebarsHelpers: ["just-handlebars-helpers"], 28 | TurndownService: ["turndown"] 29 | }, 30 | output: { 31 | library: { 32 | type: "amd", 33 | name: "com.siemens.bt.jazz.workitemeditor.rtcGitConnector|dist|[name].js" 34 | }, 35 | filename: "[name].js", 36 | path: __dirname + "/resources/dist" 37 | }, 38 | optimization: { 39 | concatenateModules: false 40 | }, 41 | module: { 42 | rules: [ 43 | { 44 | test: /\.js$/, 45 | use: { 46 | loader: "babel-loader", 47 | options: { 48 | exclude: /node_modules[\\/]@babel/, 49 | presets: [["@babel/preset-env", { "modules": "amd" }]], 50 | plugins: ["@babel/plugin-transform-runtime"] 51 | } 52 | } 53 | } 54 | ] 55 | }, 56 | plugins: [ 57 | new RemovePlugin({ 58 | before: { 59 | root: __dirname, 60 | test: [ 61 | { 62 | folder: "./", 63 | method: (filePath) => { 64 | return new RegExp( 65 | /com\.siemens\.bt\.jazz\.workitemeditor\.rtcGitConnector.*\.zip$/, 66 | "i" 67 | ).test(filePath); 68 | } 69 | } 70 | ], 71 | include: ["./resources/dist"] 72 | }, 73 | after: { 74 | root: __dirname, 75 | include: ["dist"] 76 | } 77 | }) 78 | ] 79 | }; 80 | 81 | const themeConfig = { 82 | target: ["web", "es5"], 83 | entry: "./src/theme/customMenuItems.js", 84 | output: { 85 | path: __dirname + "/dist", 86 | filename: "com.siemens.bt.jazz.workitemeditor.rtcGitConnector_theme_" + version + ".js" 87 | }, 88 | module: { 89 | rules: [ 90 | { 91 | test: /\.js$/, 92 | use: { 93 | loader: "babel-loader", 94 | options: { 95 | exclude: /node_modules[\\/]@babel/, 96 | presets: [["@babel/preset-env", { "modules": "amd" }]], 97 | plugins: ["@babel/plugin-transform-runtime"] 98 | } 99 | } 100 | } 101 | ] 102 | }, 103 | plugins: [ 104 | new ZipPlugin({ 105 | path: __dirname, 106 | filename: "com.siemens.bt.jazz.workitemeditor.rtcGitConnector_theme_" + version + ".zip" 107 | }) 108 | ] 109 | }; 110 | 111 | module.exports = [config, themeConfig]; 112 | --------------------------------------------------------------------------------