├── .gitignore ├── .reuse └── dep5 ├── CONTRIBUTING.md ├── LICENSE ├── LICENSES └── Apache-2.0.txt ├── README.md ├── applications ├── BPA │ └── Approval Workflow 2 eyes_1.0.2.mtar ├── CustomProcessUpload │ ├── .gitattributes │ ├── .gitignore │ ├── .npmrc │ ├── LICENSE │ ├── README.md │ ├── codelists │ │ └── countries.js │ ├── index.js │ ├── input │ │ ├── Sample Content Public.xlsx │ │ └── diagrams │ │ │ ├── Idea-to-Market │ │ │ ├── SPF Create Detailed Design.svg │ │ │ ├── SPF Develop Requirements & Design Systems.svg │ │ │ ├── SPF Handover new Material.svg │ │ │ ├── SPF Handover to Manufacturing.bpmn2 │ │ │ └── SVF Idea to Market for Hybrid Deployment.svg │ │ │ └── Lead-to-Cash │ │ │ └── SVF Lead to Cash for Hybrid Deployment.svg │ ├── log │ │ └── README.md │ ├── modules │ │ ├── Builder.js │ │ ├── CalmAdapter.js │ │ └── UserInterface.js │ └── package.json ├── Readme.md ├── ScopingAPI │ ├── index.js │ ├── package.json │ ├── readme.MD │ ├── recording │ │ └── 2022-09-14_11-49-53.mp4 │ ├── secret.js.sample │ └── service.json ├── calm-api-consumer-java-sample │ ├── README.md │ ├── cloud-alm-api-scopes.json │ ├── documents │ │ └── cloud-alm-api-sample-architecture.jpg │ ├── manifest.yml │ ├── pom.xml │ └── src │ │ └── main │ │ ├── java │ │ └── com │ │ │ └── sap │ │ │ └── refapps │ │ │ └── cloudalm │ │ │ ├── CloudALMAPIAccessController.java │ │ │ ├── ConsumingApplication.java │ │ │ ├── EnvironmentUtil.java │ │ │ ├── JWTTokenProvider.java │ │ │ ├── JWTTokenResponse.java │ │ │ ├── JwtTokenDto.java │ │ │ ├── ProjectDto.java │ │ │ └── VcapServicesDto.java │ │ └── resources │ │ └── application.properties ├── calm-api-consumer-ui5-sample │ ├── .gitignore │ ├── Makefile_20210726123408.mta │ ├── README.md │ ├── approuter │ │ ├── package.json │ │ └── xs-app.json │ ├── mta.yaml │ ├── package.json │ ├── ui5-deploy.yaml │ ├── ui5-local.yaml │ ├── ui5.yaml │ ├── webapp │ │ ├── Component.js │ │ ├── ModelManager.js │ │ ├── controller │ │ │ ├── App.controller.js │ │ │ ├── Kanban.controller.js │ │ │ ├── PieChart.controller.js │ │ │ └── ProjectList.controller.js │ │ ├── css │ │ │ └── style.css │ │ ├── i18n │ │ │ └── i18n.properties │ │ ├── index.html │ │ ├── manifest.json │ │ ├── models │ │ │ ├── InfoTask.json │ │ │ ├── KanbanModel.json │ │ │ ├── Projects.json │ │ │ ├── Tasks.json │ │ │ └── TasksStatistics.json │ │ ├── utils │ │ │ ├── Constants.js │ │ │ └── formatter.js │ │ └── view │ │ │ ├── App.view.xml │ │ │ ├── CreateTask.fragment.xml │ │ │ ├── Kanban.view.xml │ │ │ ├── PieChart.view.xml │ │ │ └── ProjectList.view.xml │ ├── xs-app.json │ └── xs-security.json └── kanban4calm │ ├── ExampleScreenshot.png │ ├── HL_Architecture.drawio.png │ ├── README.md │ ├── manifest.yaml │ └── server │ ├── .gitignore │ ├── config.json.sample │ ├── package.json │ ├── server.js │ └── static │ └── index.html ├── jupyternotebooks ├── .gitignore ├── PmApiTest.ipynb ├── Readme.md ├── apidata_template.py ├── calm-copy-template-project.ipynb ├── calm-process-authoring-api-v1.ipynb ├── calm-project-task-2022-02-updates.ipynb ├── calm-project-task-api.ipynb └── calm-requirements-api.ipynb ├── postmancollections ├── README.md ├── SAP Cloud ALM Projects-Tasks Public Collection.postman_collection.json └── analytics │ ├── CALM Analytics Demo.postman_collection.json │ └── README.md └── spreadsheet-examples ├── spreadsheet-documents └── SAP Cloud ALM - Documents template.xlsx ├── spreadsheet-libraries ├── SAP Cloud ALM - Library Elements Template for Application.xlsx ├── SAP Cloud ALM - Library Elements Template for Configuration and Configuration Activity.xlsx ├── SAP Cloud ALM - Library Elements Template.xlsx └── readme.md ├── spreadsheet-process-authoring └── SAP Cloud ALM - Process Authoring - Solution Activities template.xlsx ├── spreadsheet-process-hierarchy └── SAP Cloud ALM - Process Hierarchy template.xlsx ├── spreadsheet-task-creation ├── Example_Template_Create_Tasks.xlsx ├── Example_Template_Update_Tasks.xlsx ├── Excel_Template_Requirement_Hierarchy_upload.xlsx ├── Excel_upload_template_Defects.xlsx ├── Excel_upload_template_Requirements.xlsx ├── Excel_upload_template_tasks.xlsx └── README.md └── spreadsheet-testcases └── SAP Cloud ALM - Test Cases template.xlsx /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | #Config Files for node.js applications ( provide config.json.sample instead) 9 | config.json 10 | 11 | #MacOs 12 | .DS_Store 13 | 14 | # Runtime data 15 | pids 16 | *.pid 17 | *.seed 18 | *.pid.lock 19 | 20 | # Directory for instrumented libs generated by jscoverage/JSCover 21 | lib-cov 22 | 23 | # Coverage directory used by tools like istanbul 24 | coverage 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | package-lock.json 45 | 46 | # TypeScript v1 declaration files 47 | typings/ 48 | 49 | # Optional npm cache directory 50 | .npm 51 | 52 | # Optional eslint cache 53 | .eslintcache 54 | 55 | # Optional REPL history 56 | .node_repl_history 57 | 58 | # Output of 'npm pack' 59 | *.tgz 60 | 61 | # Yarn Integrity file 62 | .yarn-integrity 63 | 64 | # dotenv environment variables file 65 | .env 66 | 67 | # parcel-bundler cache (https://parceljs.org/) 68 | .cache 69 | 70 | # next.js build output 71 | .next 72 | 73 | # nuxt.js build output 74 | .nuxt 75 | 76 | # vuepress build output 77 | .vuepress/dist 78 | 79 | # Serverless directories 80 | .serverless 81 | 82 | # FuseBox cache 83 | .fusebox/ 84 | .vscode/spellright.dict 85 | .vscode/settings.json 86 | 87 | .vscode/ -------------------------------------------------------------------------------- /.reuse/dep5: -------------------------------------------------------------------------------- 1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: cloud-alm-api-examples 3 | Upstream-Contact: michael.kloeffer@sap.com 4 | Source: https://github.com/sap-samples/cloud-alm-api-examples 5 | Disclaimer: The code in this project may include calls to APIs (“API Calls”) of 6 | SAP or third-party products or services developed outside of this project 7 | (“External Products”). 8 | “APIs” means application programming interfaces, as well as their respective 9 | specifications and implementing code that allows software to communicate with 10 | other software. 11 | API Calls to External Products are not licensed under the open source license 12 | that governs this project. The use of such API Calls and related External 13 | Products are subject to applicable additional agreements with the relevant 14 | provider of the External Products. In no event shall the open source license 15 | that governs this project grant any rights in or to any External Products,or 16 | alter, expand or supersede any terms of the applicable additional agreements. 17 | If you have a valid license agreement with SAP for the use of a particular SAP 18 | External Product, then you may make use of any API Calls included in this 19 | project’s code for that SAP External Product, subject to the terms of such 20 | license agreement. If you do not have a valid license agreement for the use of 21 | a particular SAP External Product, then you may only make use of any API Calls 22 | in this project for that SAP External Product for your internal, non-productive 23 | and non-commercial test and evaluation of such API Calls. Nothing herein grants 24 | you any rights to use or access any SAP External Product, or provide any third 25 | parties the right to use of access any SAP External Product, through API Calls. 26 | 27 | Files: * 28 | Copyright: 2021 SAP SE or an SAP affiliate company and cloud-alm-api-examples contributors 29 | License: Apache-2.0 30 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to API examples for SAP Cloud ALM 2 | 3 | You want to contribute to our API examples? Welcome! Please read this document to understand what you can do: 4 | 5 | ## Contribute Code 6 | 7 | You are welcome to contribute code to this repository in order to help others using the APIs. 8 | 9 | There are three important things to know: 10 | 11 | 1. You must be aware of the Apache License (which describes contributions) and **agree to the Developer Certificate of Origin**. This is common practice in all major Open Source projects. To make this process as simple as possible, we are using *[CLA assistant](https://cla-assistant.io/)*. CLA assistant is an open source tool that integrates with GitHub very well and enables a one-click-experience for accepting the DCO. See the respective section below for details. 12 | 2. **Not all proposed contributions can be accepted**. Some features may e.g. just fit a third-party add-on better. The code must fit the overall direction of SAP Cloud ALM and really improve it, so there should be some "bang for the byte". 13 | 14 | ### Developer Certificate of Origin (DCO) 15 | 16 | Due to legal reasons, contributors will be asked to accept a DCO before they submit the first pull request to this project. SAP uses [the standard DCO text of the Linux Foundation](https://developercertificate.org/). 17 | This happens in an automated fashion during the submission process: the CLA assistant tool will add a comment to the pull request. Click it to check the DCO, then accept it on the following screen. CLA assistant will save this decision for upcoming contributions. 18 | 19 | This DCO replaces the previously used CLA ("Contributor License Agreement") as well as the "Corporate Contributor License Agreement" with new terms which are well-known standards and hence easier to approve by legal departments. Contributors who had already accepted the CLA in the past may be asked once to accept the new DCO. 20 | 21 | ### Contribution Content Guidelines 22 | 23 | Contributed content can be accepted if it is useful for others and helps to improve the API examples. 24 | In addition there are a few more rules that we would like you to follow: 25 | 26 | - Apply a clean coding style adapted to the surrounding code, even though we are aware the existing code is not fully clean 27 | - Use tabs for indentation (except if the modified file consistently uses spaces) 28 | - Only access public APIs of other entities 29 | - Comment your code where it gets non-trivial and remember to keep the public JSDoc documentation up-to-date 30 | - Keep an eye on performance and memory consumption, properly destroy objects when not used anymore (e.g. avoid ancestor selectors in CSS) 31 | - Try to write slim and "modern" HTML and CSS, avoid using images and affecting any non-UI5 content in the page/app 32 | - Always consider the developer who USES your control/code! 33 | - Think about what code and how much code he/she will need to write to use your feature 34 | - Think about what she/he expects your control/feature to do 35 | 36 | ### How to contribute - the Process 37 | 38 | 1. Make sure the change would be welcome (e.g. a useful example of an API call); best do so by proposing it in a GitHub issue 39 | 2. Create a branch forking the examples repository and do your change 40 | 3. Commit and push your changes on that branch 41 | - When you have several commits, squash them into one (see [this explanation](http://davidwalsh.name/squash-commits-git)) - this also needs to be done when additional changes are required after the code review 42 | 4. Create a Pull Request to github.com/SAP-samples/cloud-alm-api-examples 43 | 5. Follow the link posted by the CLA assistant to your pull request and accept the Developer Certificate of Origin, as described in detail above. 44 | 6. Wait for our code review and approval, possibly enhancing your change on request 45 | 7. Once the change has been approved we will inform you in a comment 46 | 8. We will close the pull request, feel free to delete the now obsolete branch 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSES/Apache-2.0.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![REUSE status](https://api.reuse.software/badge/github.com/SAP-samples/cloud-alm-api-examples)](https://api.reuse.software/info/github.com/SAP-samples/cloud-alm-api-examples) 2 | # API examples for SAP Cloud ALM 3 | 4 | In this Github repository we provide open source examples for APIs that get delivered within SAP Cloud ALM. 5 | The actual APIs for SAP Cloud ALM are [documented in the SAP API Hub](https://api.sap.com/package/SAPCloudALM/rest). Here we want to provide you with help on getting started with the APIs, some sample applications, code snippets,... 6 | 7 | If you want to learn more about SAP Cloud ALM: 8 | - you can look at our page in [SAP Support Portal](https://support.sap.com/en/alm/sap-cloud-alm.html) 9 | - check out the [blogs in the community](https://blogs.sap.com/tags/73554900100700002361/) 10 | - or watch some [videos on YouTube on SAP Cloud ALM]( 11 | https://www.youtube.com/playlist?list=PLFrwZZeBUtfiJyWpJ2nmokXOFSue_Z7sQ) 12 | 13 | Currently we provide the following assets: 14 | - [Sample Applications](applications) 15 | - [Jupyter Notebooks](jupyternotebooks) 16 | - [Postman collections](postmancollections) 17 | 18 | ## How to obtain support 19 | 20 | [Create an issue](https://github.com/SAP-samples/cloud-alm-api-examples/issues) in this repository if you find a bug or have questions about the content. 21 | 22 | For additional support, [ask a question in SAP Community](https://answers.sap.com/questions/ask.html). 23 | 24 | ## Contributing 25 | 26 | Check out [CONTRIBUTING.md](CONTRIBUTING.md) 27 | 28 | ## License 29 | Copyright (c) 2021 SAP SE or an SAP affiliate company. All rights reserved. This project is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](LICENSES/Apache-2.0.txt) file. 30 | -------------------------------------------------------------------------------- /applications/BPA/Approval Workflow 2 eyes_1.0.2.mtar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAP-samples/cloud-alm-api-examples/0503338e01aad752dcf1f9d7dee8660e7a1e275d/applications/BPA/Approval Workflow 2 eyes_1.0.2.mtar -------------------------------------------------------------------------------- /applications/CustomProcessUpload/.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /applications/CustomProcessUpload/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | /log/*.json -------------------------------------------------------------------------------- /applications/CustomProcessUpload/.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.com -------------------------------------------------------------------------------- /applications/CustomProcessUpload/LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /applications/CustomProcessUpload/README.md: -------------------------------------------------------------------------------- 1 | # SAP Cloud ALM Custom Content Upload Demo 2 | 3 | This is an example to illustrate how you can use SAP Cloud ALM Custom Processes API. The code is not production ready and you use it on your own risk. :-) 4 | Even though the Scoping API is actually built as batch oriented REST API, this example implements UI interactions. Overall the API is not intended to build UIs but rather synchronization scenarios. 5 | 6 | You can find all the details about the SAP Cloud ALM Custom Processes API on the [SAP API Business Hub](https://api.sap.com/api/CALM_PMGE/overview). 7 | 8 | ## Install libraries 9 | 10 | [Sad but true.](https://www.monkeyuser.com/2017/npm-delivery/) 11 | 12 | Navigate to CustomProcessUpload folder, and then 13 | 14 | > npm install 15 | > 16 | ## Credentials 17 | 18 | Please make sure you have sufficient authorizations to call the APIs. 19 | The required authorisation scopes, to run the sample, are: 20 | 21 | * calm-api.processauthoring.read: Read process authoring entities. 22 | * calm-api.processauthoring.write: Write process authoring entities. 23 | * calm-api.projects.read: to view projects. 24 | 25 | To learn how generate needed client id and secret please check this [blog](https://blogs.sap.com/2021/08/12/sap-cloud-alm-extend-with-api-get-started-with-sap-cloud-alm-api/). 26 | 27 | ## Run the script 28 | 29 | > node index.js 30 | 31 | Please check the logs in the log directory. 32 | 33 | Have fun. -------------------------------------------------------------------------------- /applications/CustomProcessUpload/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /******************************************************************************* 4 | * index.js main program 5 | *******************************************************************************/ 6 | import UserInterface from './modules/UserInterface.js' 7 | import Builder from './modules/Builder.js' 8 | import CalmAdapter from './modules/CalmAdapter.js' 9 | import { writeFileSync } from 'fs' 10 | import loading from 'loading-indicator'; 11 | 12 | /****************************************************************************** 13 | * Main program 14 | */ 15 | 16 | (async () => { 17 | 18 | const userInterface = new UserInterface() 19 | userInterface.welcomeScreen() 20 | const excelFile = await userInterface.getExcelFileDialog() 21 | 22 | const importer = loading.start('Importing...') 23 | const builder = new Builder() 24 | const excelContent = builder.readExcelContent(excelFile) 25 | 26 | try { 27 | builder.validateExcelContent(excelContent) 28 | } catch (error) { 29 | console.log(error) 30 | process.exit(1) 31 | } 32 | 33 | const calmAdapter = new CalmAdapter(builder.getCustomerData(excelContent.customer)) 34 | 35 | const businessProcesses = await calmAdapter.postBusinessProcesses(builder.getBusinessProcesses(excelContent.solutionProcesses)) 36 | writeFileSync('./log/businessProcesses.json', JSON.stringify(businessProcesses), { encoding: 'utf8', flag: 'w' }) 37 | 38 | const solutionProcesses = await calmAdapter.postSolutionProcesses(builder.getSolutionProcesses(excelContent.solutionProcesses, businessProcesses)) 39 | writeFileSync('./log/solutionProcesses.json', JSON.stringify(solutionProcesses), { encoding: 'utf8', flag: 'w' }) 40 | 41 | const accelerators = await calmAdapter.postAccelerators(builder.getAccelerators(excelContent.accelerators, solutionProcesses)) 42 | writeFileSync('./log/accelerators.json', JSON.stringify(accelerators), { encoding: 'utf8', flag: 'w' }) 43 | 44 | const svfDiagrams = await calmAdapter.postSolutionValueFlows(builder.getDiagrams(excelContent.diagrams, 'Solution Value Flow Diagram'), solutionProcesses) 45 | writeFileSync('./log/svfDiagrams.json', JSON.stringify(svfDiagrams), { encoding: 'utf8', flag: 'w' }) 46 | 47 | const spfDiagrams = await calmAdapter.postSolutionProcessFlowDiagrams(builder.getDiagrams(excelContent.diagrams, 'Solution Process Flow Diagram'), await calmAdapter.findSolutionProcessFlowsBySolutionProcesses(solutionProcesses)) 48 | writeFileSync('./log/spfDiagrams.json', JSON.stringify(spfDiagrams), { encoding: 'utf8', flag: 'w' }) 49 | 50 | const activatedProcesses = await calmAdapter.activateSolutionProcesses(solutionProcesses) 51 | writeFileSync('./log/activation.json', JSON.stringify(activatedProcesses), { encoding: 'utf8', flag: 'w' }) 52 | 53 | loading.stop(importer); 54 | })() -------------------------------------------------------------------------------- /applications/CustomProcessUpload/input/Sample Content Public.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAP-samples/cloud-alm-api-examples/0503338e01aad752dcf1f9d7dee8660e7a1e275d/applications/CustomProcessUpload/input/Sample Content Public.xlsx -------------------------------------------------------------------------------- /applications/CustomProcessUpload/input/diagrams/Idea-to-Market/SPF Handover to Manufacturing.bpmn2: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | a2ff7a1f4-8809-4c0f-bced-222fa843fdce 10 | a4ceffb7f-b0b8-4054-961c-a6f0fa676d6f 11 | c5ad60db-cc49-4a7b-a3c0-f0dc37f0b246 12 | a1597b06d-5bf7-4111-86a4-d54dad32432b 13 | ef792b4b-1205-45fc-adce-efbf711878bf 14 | e0263f8a-8fc9-4a9d-8b19-07dcf5c325b3 15 | a467153f9-ef77-4157-a7fd-d14165652f19 16 | a3979bc0-94a6-4abb-8501-d92c176eba96 17 | a3bd002ce-b28e-4aff-9326-de8fec3146c0 18 | a5f3ac6bf-c763-4421-bcce-70b21f10e4bb 19 | 20 | 21 | 22 | a88601bdb-a70d-48c0-8754-912c7274c82f 23 | 24 | 25 | a88601bdb-a70d-48c0-8754-912c7274c82f 26 | fd54343b-f7ed-4948-943b-2c5dfd61711a 27 | 28 | 29 | fd54343b-f7ed-4948-943b-2c5dfd61711a 30 | a7c9e8b05-2e10-45e4-ac45-c6b62dcb1140 31 | 32 | 33 | a61231daa-332e-48aa-a431-d05fd6842b38 34 | a793f7e98-0364-4bae-9caf-dce6aa2b4887 35 | 36 | 37 | e85c9903-a38f-480f-8f0a-14a7d604614c 38 | c948b71c-a711-4fe9-bc12-7986de63ab10 39 | 40 | 41 | a2f798216-c590-41fb-a3dc-4356bb3e3d92 42 | a5b58d77f-fba0-4c5a-b327-720ca873e11f 43 | 44 | 45 | a7c9e8b05-2e10-45e4-ac45-c6b62dcb1140 46 | a2f798216-c590-41fb-a3dc-4356bb3e3d92 47 | a61231daa-332e-48aa-a431-d05fd6842b38 48 | 49 | 50 | a793f7e98-0364-4bae-9caf-dce6aa2b4887 51 | a5b58d77f-fba0-4c5a-b327-720ca873e11f 52 | a5d9511cd-ef12-49a3-8e2a-2d260dafce77 53 | 54 | 55 | a5d9511cd-ef12-49a3-8e2a-2d260dafce77 56 | e85c9903-a38f-480f-8f0a-14a7d604614c 57 | a3b5a1f88-011b-44e6-aafc-0e98a16400cd 58 | 59 | 60 | c948b71c-a711-4fe9-bc12-7986de63ab10 61 | a3b5a1f88-011b-44e6-aafc-0e98a16400cd 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | -------------------------------------------------------------------------------- /applications/CustomProcessUpload/log/README.md: -------------------------------------------------------------------------------- 1 | # Logs 2 | All upload logs goes to the log folder. -------------------------------------------------------------------------------- /applications/CustomProcessUpload/modules/Builder.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /******************************************************************************* 4 | * Builder Utility 5 | *******************************************************************************/ 6 | 7 | import { read, utils } from "xlsx"; 8 | import Path from 'path' 9 | import { fileURLToPath } from 'url' 10 | import loadsh from 'loadsh' 11 | import CountryMaster from '../codelists/countries.js' 12 | import { readFileSync } from 'node:fs' 13 | 14 | export default function Builder() { 15 | const requiredColumnsCustomerSheet = ['Property', 'Value'] 16 | const requiredRowsCustomerSheet = ['Customer', 'SAP Cloud ALM Tenant URL', 'SAP Cloud ALM Authorization URL', 'Client ID', 'Client Secret'] 17 | const requiredColumnsDiagramSheet = ['Solution Process ID', 'Type', 'Diagram Name', 'SVG'] 18 | const requiredColumnsSolutionProcessSheet = ['External ID', 'Name', 'Business Process', 'Description', 'Localization'] 19 | const requiredColumnsAccelerators = ['Solution Process ID', 'Name', 'Language', 'Host', 'Path'] 20 | 21 | function _hasRequriedColumns(obj, columns) { 22 | return columns.every(column => Object.keys(obj).includes(column)) 23 | } 24 | 25 | /** 26 | * TODO: Implement Row check for customer 27 | */ 28 | function _hasRequiredRows() { } 29 | 30 | function _hasDuplicates(arr, key) { 31 | const uniqueElements = new Set() 32 | arr.map(el => { 33 | uniqueElements.add(el[key]) 34 | }) 35 | const duplicates = arr.filter(el => { 36 | if (uniqueElements.has(el[key])) { 37 | uniqueElements.delete(el[key]) 38 | } else { 39 | return el 40 | } 41 | }) 42 | return duplicates 43 | } 44 | 45 | function _isSubset(parentArray, parentKey, subsetArray, subsetKey) { 46 | const parentArraySet = new Set() 47 | parentArray.map(el => { 48 | parentArraySet.add(el[parentKey]) 49 | }) 50 | const subsetArraySet = new Set() 51 | subsetArray.map(el => { 52 | subsetArraySet.add(el[subsetKey]) 53 | }) 54 | return [...subsetArraySet].every((el) => { 55 | return [...parentArraySet].includes(el) 56 | }) 57 | } 58 | 59 | function _hasValidCountries(arr, key) { 60 | const masterCountries = new Set() 61 | CountryMaster.map(element => { 62 | masterCountries.add(element['ID']) 63 | }) 64 | const usedCountries = new Set() 65 | arr.map(element => { 66 | const extractedCountries = element[key].split(",").map(country => country.trim()) 67 | extractedCountries.map(country => usedCountries.add(country)) 68 | }) 69 | return [...usedCountries].every((el) => { 70 | return [...masterCountries].includes(el) 71 | }) 72 | } 73 | 74 | function _hasExactlyOneCountry(arr, key) { 75 | let result = true 76 | arr.map(element => { 77 | const extractedCountries = element[key].split(",").map(country => country.trim()) 78 | if (extractedCountries.length != 1) result = false 79 | }) 80 | return result 81 | } 82 | 83 | return { 84 | readExcelContent: function (file) { 85 | const result = {} 86 | let excel = undefined 87 | try { 88 | const buf = readFileSync(file); 89 | excel = read(buf); 90 | result.customer = utils.sheet_to_json(excel.Sheets['Customer']) 91 | result.solutionProcesses = utils.sheet_to_json(excel.Sheets['Solution Processes']) 92 | result.diagrams = utils.sheet_to_json(excel.Sheets['Diagrams']) 93 | result.accelerators = utils.sheet_to_json(excel.Sheets['Accelerators']) 94 | return result 95 | } catch (error) { 96 | throw new Error(error.message) 97 | } 98 | }, 99 | 100 | validateExcelContent: function (excelContent) { 101 | try { 102 | if (!excelContent.customer.length || !excelContent.solutionProcesses.length || !excelContent.diagrams.length || !excelContent.accelerators.length) { 103 | throw new Error('Could not find needed sheets or content.') 104 | } 105 | 106 | if (excelContent.solutionProcesses.length < 1) { 107 | throw new Error('Could not find any Solution Processes in the Excel sheet.') 108 | } 109 | 110 | if (!_hasRequriedColumns(excelContent.customer[0], requiredColumnsCustomerSheet)) { 111 | throw new Error('Could not find needed columns in sheet "Customer".') 112 | } 113 | 114 | if (!_hasRequriedColumns(excelContent.solutionProcesses[0], requiredColumnsSolutionProcessSheet)) { 115 | throw new Error('Could not find needed columns in sheet "Solution Processes".') 116 | } 117 | 118 | if (!_hasRequriedColumns(excelContent.diagrams[0], requiredColumnsDiagramSheet)) { 119 | throw new Error('Could not find needed columns in sheet "Diagrams".') 120 | } 121 | 122 | if (!_hasRequriedColumns(excelContent.accelerators[0], requiredColumnsAccelerators)) { 123 | throw new Error('Could not find needed columns in sheet "Accelerators".') 124 | } 125 | 126 | if (_hasDuplicates(excelContent.solutionProcesses, 'External ID').length > 0) { 127 | throw new Error('Solution Processes has duplicates.') 128 | } 129 | 130 | if (!_isSubset(excelContent.solutionProcesses, 'External ID', excelContent.diagrams, 'Solution Process ID')) { 131 | throw new Error('Diagrams reference non-existing Solution Processes.') 132 | } 133 | 134 | if (!_isSubset(excelContent.solutionProcesses, 'External ID', excelContent.accelerators, 'Solution Process ID')) { 135 | throw new Error('Accelerators reference non-existing Solution Processes.') 136 | } 137 | 138 | if (!_hasValidCountries(excelContent.solutionProcesses, 'Localization')) { 139 | throw new Error('Solution Processes contains invalid Countries/Regions.') 140 | } 141 | 142 | if (!_hasValidCountries(excelContent.accelerators, 'Language')) { 143 | throw new Error('Accelerators contains invalid Languages.') 144 | } 145 | 146 | if (!_hasExactlyOneCountry(excelContent.accelerators, 'Language')) { 147 | throw new Error('Accelerators has malformed or too many languages. One Accelerator must have exactly one language.') 148 | } 149 | 150 | } catch (error) { 151 | throw new Error(error) 152 | } 153 | }, 154 | 155 | getCustomerData: function (rawCustomerData) { 156 | const customerData = { 157 | customer: '', 158 | baseURL: '', 159 | authorizationURL: '', 160 | clientId: '', 161 | clientSecret: '' 162 | } 163 | 164 | rawCustomerData.map(element => { 165 | switch (element.Property) { 166 | case 'Customer': 167 | customerData.customer = element.Value 168 | break 169 | case 'SAP Cloud ALM Tenant URL': 170 | customerData.apiBaseUrl = element.Value 171 | break 172 | case 'SAP Cloud ALM Authorization URL': 173 | customerData.authenticationUrl = element.Value 174 | break 175 | case 'Client ID': 176 | customerData.clientId = element.Value 177 | break 178 | case 'Client Secret': 179 | customerData.clientSecret = element.Value 180 | break 181 | default: 182 | break 183 | } 184 | }) 185 | return customerData 186 | }, 187 | 188 | getBusinessProcesses: function (rawSolutionProcesses) { 189 | const businessProcessSet = new Set() 190 | rawSolutionProcesses.map(solutionProcess => { 191 | businessProcessSet.add(solutionProcess['Business Process']) 192 | }) 193 | return [...businessProcessSet] 194 | }, 195 | 196 | getSolutionProcesses: function (rawSolutionProcesses, businessProcesses) { 197 | const solutionProcesses = [] 198 | rawSolutionProcesses.map(solutionProcess => { 199 | const description = solutionProcess['Description'].length >= 5000 ? solutionProcess['Description'].substring(0, 4999) : solutionProcess['Description'] 200 | const sp = { 201 | name: `${solutionProcess['Name']} (${solutionProcess['External ID']})`, 202 | description: description, 203 | businessProcess: businessProcesses.find(businessProcess => { 204 | solutionProcess['Business Process'] === businessProcess['name'] 205 | return businessProcess 206 | }), 207 | countries: solutionProcess['Localization'], 208 | externalId: solutionProcess['External ID'], 209 | } 210 | delete sp['businessProcess']['name'] 211 | delete sp['businessProcess']['description'] 212 | solutionProcesses.push(sp) 213 | }) 214 | 215 | return solutionProcesses 216 | }, 217 | 218 | getAccelerators: function (rawAccelerators, solutionProcesses) { 219 | const accelerators = [] 220 | const groupedByProcessId = loadsh.groupBy(rawAccelerators, 'Solution Process ID') 221 | for (const process in groupedByProcessId) { 222 | const solutionProcessFound = solutionProcesses.find(solution_process => solution_process.externalId === process) 223 | const groupedByName = loadsh.groupBy(groupedByProcessId[process], 'Name') 224 | for (const name in groupedByName) { 225 | const accelerator = { 226 | name: name, 227 | countryUrl: [], 228 | solutionProcess: solutionProcessFound.id, 229 | } 230 | groupedByName[name].map((link) => { 231 | const url = { 232 | url: Path.join(link.Host, link.Path), 233 | country: link.Language 234 | } 235 | accelerator.countryUrl.push(url) 236 | }) 237 | accelerators.push(accelerator) 238 | } 239 | } 240 | return accelerators 241 | }, 242 | 243 | getDiagrams: function (rawDiagrams, type) { 244 | const __filename = fileURLToPath(import.meta.url); 245 | const __dirname = Path.dirname(__filename); 246 | const result = [] 247 | const diagrams = rawDiagrams.filter(diagram => diagram['Type'] === type) 248 | diagrams.map(diagram => { 249 | let svgData = null 250 | let bpmnData = null 251 | try { 252 | if(diagram.BPMN){ 253 | bpmnData = readFileSync(Path.join(__dirname, diagram.BPMN), 'utf-8') 254 | } 255 | if(diagram.SVG){ 256 | svgData = readFileSync(Path.join(__dirname, diagram.SVG), 'utf-8') 257 | } 258 | result.push({ 259 | parentId: diagram['Solution Process ID'], 260 | name: diagram['Diagram Name'], 261 | svg: svgData, 262 | bpmn: bpmnData 263 | }) 264 | } catch (error) { 265 | throw new Error(error) 266 | } 267 | }) 268 | return result 269 | } 270 | } // return 271 | } 272 | -------------------------------------------------------------------------------- /applications/CustomProcessUpload/modules/CalmAdapter.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /******************************************************************************* 4 | * CALM Adapter 5 | *******************************************************************************/ 6 | import axios from 'axios' 7 | import oauth from 'axios-oauth-client' 8 | import tokenProvider from 'axios-token-interceptor' 9 | import axiosThrottle from 'axios-request-throttle' 10 | 11 | export default function CalmAdapter(secret) { 12 | 13 | /****************************************************************************** 14 | * AXIOS Authenticate and Throttle 15 | */ 16 | 17 | axiosThrottle.use(axios, { requestsPerSecond: 100 }) 18 | 19 | const getClientCredentials = oauth.clientCredentials( 20 | axios.create({ 21 | baseURL: secret.authenticationUrl, 22 | }), 23 | '/oauth/token', 24 | secret.clientId, 25 | secret.clientSecret 26 | ) 27 | 28 | const instance = axios.create({ 29 | baseURL: secret.apiBaseUrl, 30 | }) 31 | 32 | const cache = tokenProvider.tokenCache( 33 | () => getClientCredentials().then(response => response), 34 | { getMaxAge: body => body.expires_in * 1000 } 35 | ) 36 | 37 | instance.interceptors.request.use(tokenProvider({ 38 | getToken: cache, 39 | headerFormatter: body => 'Bearer ' + body.access_token, 40 | })) 41 | 42 | /****************************************************************************** 43 | * Private functions 44 | */ 45 | 46 | async function _getSolutionProcessFlows(top = 100, skip = 0) { 47 | const url = `/api/calm-processauthoring/v1/solutionProcessFlows?$top=${top}&$skip=${skip}` 48 | try { 49 | const businessProcesses = await instance.get(url) 50 | return businessProcesses.data.value 51 | } catch (error) { 52 | throw new Error(error) 53 | } 54 | } 55 | 56 | async function _getbusinessProcesses(top = 100, skip = 0) { 57 | const url = `/api/calm-processauthoring/v1/businessProcesses?$top=${top}&$skip=${skip}` 58 | try { 59 | const businessProcesses = await instance.get(url) 60 | return businessProcesses.data.value 61 | } catch (error) { 62 | throw new Error(error) 63 | } 64 | } 65 | 66 | 67 | async function _getBusinessProcessCount() { 68 | const url = '/api/calm-processauthoring/v1/businessProcesses/$count' 69 | try { 70 | const response = await instance.get(url) 71 | return response.data 72 | } catch (error) { 73 | throw new Error(error) 74 | } 75 | } 76 | 77 | async function _getSolutionProcessFlowCount() { 78 | const url = '/api/calm-processauthoring/v1/solutionProcessFlows/$count' 79 | try { 80 | const response = await instance.get(url) 81 | return response.data 82 | } catch (error) { 83 | throw new Error(error) 84 | } 85 | } 86 | 87 | async function _createBusinessProcess(name, description = '') { 88 | const url = '/api/calm-processauthoring/v1/businessProcesses' 89 | if (!name) 90 | throw new Error({ code: '', message: 'BUSINESS_PROCESS_NAME_EMPTY' }) 91 | const body = { 92 | name: name, 93 | description: description, 94 | } 95 | 96 | try { 97 | const response = await instance.post(url, body) 98 | return response.data 99 | } catch (error) { 100 | throw new Error(error.data.error) 101 | } 102 | } 103 | 104 | async function _findSolutionProcessByExternalId(externalId) { 105 | const urlByExternalId = `/api/calm-processauthoring/v1/solutionProcesses?externalId=${externalId}` 106 | try { 107 | const responseExternalId = await instance.get(urlByExternalId) 108 | if (responseExternalId.data.value.length > 0) { 109 | return responseExternalId.data.value 110 | } 111 | else { 112 | return false 113 | } 114 | } catch (error) { 115 | throw new error 116 | } 117 | } 118 | 119 | async function _createSolutionProcess(body) { 120 | const url = '/api/calm-processauthoring/v1/solutionProcesses' 121 | try { 122 | const response = await instance.post(url, body) 123 | return response.data 124 | } catch (error) { 125 | throw new Error(error) 126 | } 127 | } 128 | 129 | async function _patchSolutionProcess(solutionProcessId, body) { 130 | const url = `/api/calm-processauthoring/v1/solutionProcesses/${solutionProcessId}` 131 | try { 132 | const solutionProcess = await instance.get(url) 133 | if (solutionProcess.data.status === 'ACTIVE') { 134 | const createDraftUrl = `/api/calm-processauthoring/v1/createDraftSolutionProcess/${solutionProcessId}` 135 | const createdDraft = await instance.post(createDraftUrl, {}, { headers: { 'if-match': solutionProcess.headers.etag } }) 136 | const draftUrl = `/api/calm-processauthoring/v1/solutionProcesses/${createdDraft.data.id}` 137 | const solutionProcessAfterDraft = await instance.get(draftUrl) 138 | const patchUrl = `/api/calm-processauthoring/v1/solutionProcesses/${solutionProcessAfterDraft.data.id}` 139 | const response = await instance.patch(patchUrl, body, { headers: { 'if-match': solutionProcessAfterDraft.headers.etag } }) 140 | return response.data 141 | } else { 142 | const response = await instance.patch(url, body, { headers: { 'if-match': solutionProcess.headers.etag } }) 143 | return response.data 144 | } 145 | } catch (error) { 146 | throw new Error(error) 147 | } 148 | } 149 | 150 | async function _createAccelerator(solutionProcessId, body) { 151 | const url = `/api/calm-processauthoring/v1/solutionProcesses/${solutionProcessId}/assets` 152 | try { 153 | const response = await instance.post(url, body) 154 | return response.data 155 | } catch (error) { 156 | throw new Error(error) 157 | } 158 | } 159 | 160 | async function _patchSolutionProcessValueFlowDiagram(solutionProcessId, body) { 161 | const readUrl = `/api/calm-processauthoring/v1/solutionProcesses/${solutionProcessId}/solutionValueFlowDiagram` 162 | try { 163 | const svfd = await instance.get(readUrl) 164 | const patchUrl = `/api/calm-processauthoring/v1/solutionValueFlowDiagrams/${svfd.data.id}` 165 | const response = await instance.patch(patchUrl, body, { headers: { 'if-match': svfd.headers.etag } }) 166 | return response.data 167 | } catch (error) { 168 | throw new Error(error) 169 | } 170 | } 171 | 172 | async function _createSolutionProcessFlowSVGDiagram(solutionProcessFlowId, body) { 173 | const url = `/api/calm-processauthoring/v1/solutionProcessFlows/${solutionProcessFlowId}/solutionProcessFlowDiagrams` 174 | try { 175 | const response = await instance.post(url, body) 176 | return response.data 177 | } catch (error) { 178 | throw new Error(error) 179 | } 180 | } 181 | 182 | async function _createSolutionProcessFlowBPMNDiagram(solutionProcessFlowId, body) { 183 | const url = `/api/calm-processauthoring/v1/solutionProcessFlows/${solutionProcessFlowId}/solutionProcessFlowDiagrams/bpmn` 184 | try { 185 | const response = await instance.post(url, body) 186 | return response.data 187 | } catch (error) { 188 | throw new Error(error) 189 | } 190 | } 191 | 192 | async function _activateSolutionProcess(solutionProcessId) { 193 | const readUrl = `/api/calm-processauthoring/v1/solutionProcesses/${solutionProcessId}` 194 | const postUrl = `/api/calm-processauthoring/v1/publishSolutionProcess/${solutionProcessId}` 195 | try { 196 | const preRead = await instance.get(readUrl) 197 | const response = await instance.post(postUrl, {}, { headers: { 'if-match': preRead.headers.etag } }) 198 | return response.data 199 | } catch (error) { 200 | throw new Error(error) 201 | } 202 | } 203 | 204 | function _calculateIterations(entries, limit) { 205 | let iterations = Math.floor(entries / limit) 206 | if (entries % limit > 0) iterations++ 207 | return iterations 208 | } 209 | 210 | /****************************************************************************** 211 | * Public functions 212 | */ 213 | return { 214 | 215 | postBusinessProcesses: async function (neededBusinessProcesses, top = 100) { 216 | try { 217 | const businessProcessCount = await _getBusinessProcessCount() 218 | const iterations = _calculateIterations(businessProcessCount, top) 219 | 220 | const iterationResults = [] 221 | for (let i = 1; i <= iterations; i++) { 222 | iterationResults.push({ top: top, skip: (i - 1) * top }) 223 | } 224 | 225 | const resultArraysPromise = iterationResults.map(businessProcesses => { 226 | return _getbusinessProcesses(businessProcesses['top'], businessProcesses['skip']) 227 | }) 228 | const resultArraysResult = await Promise.all(resultArraysPromise) 229 | 230 | let existingBusinessProcesses = [] 231 | resultArraysResult.map((array) => { 232 | existingBusinessProcesses = existingBusinessProcesses.concat(array) 233 | }) 234 | 235 | const businessProcesses = neededBusinessProcesses.map(neededBusinessProcess => { 236 | let matchingBusinessProcess = existingBusinessProcesses.find(existingBusinessProcess => existingBusinessProcess.name === neededBusinessProcess) 237 | if (!matchingBusinessProcess) matchingBusinessProcess = neededBusinessProcess 238 | 239 | return matchingBusinessProcess 240 | }) 241 | 242 | const complementedPromise = businessProcesses.filter((businessProcess) => businessProcess.id === undefined).map(async (businessProcess) => { 243 | let complementedBusinessProcess = await _createBusinessProcess(businessProcess) 244 | 245 | return complementedBusinessProcess 246 | }) 247 | const complementedPromiseResult = await Promise.all(complementedPromise) 248 | 249 | complementedPromiseResult.map(created => { 250 | businessProcesses[businessProcesses.indexOf(created['name'])] = created 251 | }) 252 | 253 | return businessProcesses 254 | } catch (error) { 255 | throw new Error(error) 256 | } 257 | }, 258 | 259 | postSolutionProcesses: async function (solutionProcesses) { 260 | try { 261 | const solutionProcessesPromise = solutionProcesses.map(async solutionProcess => { 262 | const result = await _findSolutionProcessByExternalId(solutionProcess.externalId) 263 | if (!result) { 264 | solutionProcess._ACTION = 'CREATE' 265 | } else { 266 | if (result.length <= 2) { 267 | result.find(process => { 268 | if (process.status === 'DRAFT') { 269 | solutionProcess.id = process.id 270 | solutionProcess._ACTION = 'PATCH' 271 | } 272 | else { 273 | solutionProcess.id = process.id 274 | solutionProcess._ACTION = 'PATCH' 275 | } 276 | }) 277 | } else { 278 | throw new error('External ID doesn\'t seem unique in your SAP Cloud ALM tenant.') 279 | } 280 | } 281 | return solutionProcess 282 | }) 283 | const SolutionProcessesPromiseResult = await Promise.all(solutionProcessesPromise) 284 | 285 | const solutionProcessesCreate = SolutionProcessesPromiseResult.filter(solutionProcess => solutionProcess._ACTION === 'CREATE') 286 | const solutionProcessesCreatePromise = solutionProcessesCreate.map(solutionProcess => { 287 | delete solutionProcess._ACTION 288 | const result = _createSolutionProcess(solutionProcess) 289 | return result 290 | }) 291 | const solutionProcessesCreatePromiseResult = await Promise.all(solutionProcessesCreatePromise) 292 | 293 | const solutionProcessesPatch = SolutionProcessesPromiseResult.filter(solutionProcess => solutionProcess._ACTION === 'PATCH') 294 | const solutionProcessesPatchPromise = solutionProcessesPatch.map(solutionProcess => { 295 | let body = JSON.parse(JSON.stringify(solutionProcess)) 296 | delete body._ACTION 297 | delete body.id 298 | const result = _patchSolutionProcess(solutionProcess.id, body) 299 | return result 300 | }) 301 | const solutionProcessesPatchPromiseResult = await Promise.all(solutionProcessesPatchPromise) 302 | 303 | const result = [...solutionProcessesCreatePromiseResult, ...solutionProcessesPatchPromiseResult] 304 | return result 305 | } catch (error) { 306 | throw new Error(error); 307 | } 308 | }, 309 | 310 | postAccelerators: async function (accelerators) { 311 | const acceleratorPromise = accelerators.map(async accelerator => { 312 | try { 313 | const body = JSON.parse(JSON.stringify(accelerator)) 314 | delete body.solutionProcess 315 | const asset = await _createAccelerator(accelerator.solutionProcess, body) 316 | return asset 317 | } catch (error) { 318 | throw new Error(error); 319 | } 320 | }); 321 | const acceleratorPromiseResult = await Promise.all(acceleratorPromise) 322 | return acceleratorPromiseResult 323 | }, 324 | 325 | findSolutionProcessFlowsBySolutionProcesses: async function (solutionProcesses, top = 100) { 326 | try { 327 | const solutionProcessFlowCount = await _getSolutionProcessFlowCount() 328 | const iterations = _calculateIterations(solutionProcessFlowCount, top) 329 | 330 | const iterationsArray = [] 331 | for (let i = 1; i <= iterations; i++) { 332 | iterationsArray.push({ top: top, skip: (i - 1) * top }) 333 | } 334 | 335 | const solutionProcessFlowPromise = iterationsArray.map(iteration => { 336 | return _getSolutionProcessFlows(iteration.top, iteration.skip) 337 | }) 338 | 339 | const solutionProcessFlowPromiseResult = await Promise.all(solutionProcessFlowPromise) 340 | let mergedSolutionProcessFlows = [] 341 | solutionProcessFlowPromiseResult.map(array => mergedSolutionProcessFlows.push(...array)) 342 | 343 | solutionProcesses.map(solutionProcess => { 344 | mergedSolutionProcessFlows.find(solutionProcessFlow => { 345 | if (solutionProcess.id === solutionProcessFlow.solutionProcess.id) { 346 | solutionProcess.solutionProcessFlow = solutionProcessFlow 347 | return solutionProcess 348 | } 349 | }) 350 | }) 351 | return solutionProcesses 352 | } catch (error) { 353 | throw new Error(error); 354 | } 355 | }, 356 | 357 | postSolutionValueFlows: async function (solutionValueFlowDiagrams, solutionProcesses) { 358 | try { 359 | const solutionValueFlowDiagramPromise = solutionValueFlowDiagrams.map(async diagram => { 360 | const solutionProcessFound = solutionProcesses.find(solutionProcess => solutionProcess.externalId === diagram.parentId) 361 | delete diagram.parentId 362 | const result = await _patchSolutionProcessValueFlowDiagram(solutionProcessFound.id, diagram) 363 | return result 364 | }) 365 | const solutionValueFlowDiagramPromiseResult = Promise.all(solutionValueFlowDiagramPromise) 366 | return solutionValueFlowDiagramPromiseResult 367 | } catch (error) { 368 | throw new Error(error); 369 | } 370 | }, 371 | 372 | postSolutionProcessFlowDiagrams: async function (solutionProcessFlowDiagrams, solutionProcessFlows) { 373 | const solutionProcessFlowDiagramsPromise = solutionProcessFlowDiagrams.map(async diagram => { 374 | const solutionProcess = solutionProcessFlows.find(solutionProcessFlow => diagram.parentId === solutionProcessFlow.externalId) 375 | delete diagram.parentId 376 | let solutionProcessFlowDiagram = null 377 | if(diagram.bpmn){ 378 | delete diagram.svg 379 | solutionProcessFlowDiagram = await _createSolutionProcessFlowBPMNDiagram(solutionProcess.solutionProcessFlow.id, diagram) 380 | }else{ 381 | delete diagram.bpmn 382 | solutionProcessFlowDiagram = await _createSolutionProcessFlowSVGDiagram(solutionProcess.solutionProcessFlow.id, diagram) 383 | } 384 | return solutionProcessFlowDiagram 385 | }) 386 | const solutionProcessFlowDiagramsPromiseResult = Promise.all(solutionProcessFlowDiagramsPromise) 387 | return solutionProcessFlowDiagramsPromiseResult 388 | }, 389 | 390 | activateSolutionProcesses: async function (solutionProcesses) { 391 | try { 392 | const solutionProcessesActivationPromise = solutionProcesses.map(async solutionProcess => { 393 | const activation = await _activateSolutionProcess(solutionProcess.id) 394 | return activation 395 | }) 396 | const solutionProcessActivationPromiseResult = Promise.all(solutionProcessesActivationPromise) 397 | return solutionProcessActivationPromiseResult 398 | } catch (error) { 399 | throw new error 400 | } 401 | }, 402 | } // return 403 | } -------------------------------------------------------------------------------- /applications/CustomProcessUpload/modules/UserInterface.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /******************************************************************************* 4 | * User Interface Module 5 | *******************************************************************************/ 6 | import clear from 'clear' 7 | import chalk from 'chalk' 8 | import figlet from 'figlet' 9 | import inquirer from 'inquirer' 10 | import inquirerFileTreeSelection from 'inquirer-file-tree-selection-prompt' 11 | 12 | export default function UserInterface() { 13 | 14 | return { 15 | welcomeScreen: function() { 16 | clear() 17 | console.log() 18 | console.log( 19 | chalk.yellow( 20 | figlet.textSync('Process Upload', { 21 | horizontalLayout: 'full', 22 | }) 23 | ) 24 | ) 25 | console.log( 26 | chalk.yellow(' Upload Excel to SAP Cloud ALM') 27 | ) 28 | console.log() 29 | }, 30 | 31 | getExcelFileDialog: async function () { 32 | inquirer.registerPrompt('file-tree-selection', inquirerFileTreeSelection) 33 | let confirmed = false 34 | let file = undefined 35 | 36 | do { 37 | const answer = await inquirer.prompt([ 38 | { 39 | type: 'file-tree-selection', 40 | root: './input', 41 | name: 'file', 42 | }, 43 | ]) 44 | 45 | const confirmation = await inquirer.prompt([ 46 | { 47 | type: 'confirm', 48 | name: 'confirmed', 49 | message: `Are you sure you want to import this file? ${answer['file']}`, 50 | default: false, 51 | }, 52 | ]) 53 | 54 | if (confirmation['confirmed']) { 55 | confirmed = true 56 | file = answer['file'] 57 | } 58 | } while (!confirmed) 59 | return file 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /applications/CustomProcessUpload/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "customprocessesupload", 3 | "version": "1.0.0", 4 | "description": "Process Upload for Excel ", 5 | "type": "module", 6 | "main": "index.js", 7 | "scripts": { 8 | "start": "node index.js", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "author": "", 12 | "license": "Apache-2.0", 13 | "licenses": [ 14 | { 15 | "type": "Apache-2.0", 16 | "url": "http://www.apache.org/licenses/LICENSE-2.0" 17 | } 18 | ], 19 | "dependencies": { 20 | "axios": "^1.4.0", 21 | "axios-oauth-client": "^2.0.2", 22 | "axios-request-throttle": "^1.0.0", 23 | "axios-token-interceptor": "^0.2.0", 24 | "chalk": "^5.2.0", 25 | "clear": "^0.1.0", 26 | "figlet": "^1.6.0", 27 | "inquirer": "^9.2.7", 28 | "inquirer-file-tree-selection-prompt": "^2.0.5", 29 | "loading-indicator": "^2.0.0", 30 | "loadsh": "^0.0.4", 31 | "path": "^0.12.7", 32 | "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.2/xlsx-0.20.2.tgz" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /applications/Readme.md: -------------------------------------------------------------------------------- 1 | ## Sample Applications for the SAP Cloud ALM APIs 2 | 3 | 4 | In the subdirectories here you find some sample applications for the SAP Cloud ALM APIs 5 | 6 | ### Kanban Board 7 | Simple Kanban style board using JQuery, Bootstrap and SortableJS on frontend with a server side application that manages the OAuth2 authorization against the customers authentication service. The server side application is built in NodeJS and deployable to the CloudFoundry environment on the customer SAP BTP account. 8 | 9 | 10 | ### calm-api-consumer-java-sample 11 | SAPUI5 custom application developed on the SAP BTP platform with the SAP Cloud ALM Rest APIs for Projects and Tasks. 12 | 13 | 14 | ### calm-api-consumer-ui5-sample 15 | Application developed with SAPUI5 to serve as example of what it is possible to achieve with the SAP Cloud ALM Rest APIs. 16 | In this app, we connect to the Rest APIs "project" and "task" of SAP CALM APIs to display the tasks of a project in an interactive kanban. 17 | 18 | 19 | ### Scoping API 20 | Simple console app that show cases how you can interact with SAP Cloud ALM Scopes. The example demos how you can manage scopes, add/remove Solution Scenarios, and how you can set processes in/out of scope. 21 | 22 | 23 | ### Content Upload 24 | Simple console app that show cases how you can upload customer or partner content. The example demos how you can use Excel to define customer or partner practices, and how the Customer Processes API can be used to upload the content. -------------------------------------------------------------------------------- /applications/ScopingAPI/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scopingapi", 3 | "version": "1.0.0", 4 | "description": "This is an example to illustrate how you can use SAP Cloud ALM Scoping API. The code is not production ready and you use it on your own risk. :-) Even though the Scoping API is actually built as batch oriented REST API, this example implements UI interactions. Overall the API is not intended to build UIs but rather synchronization scenarios.", 5 | "main": "index.js", 6 | "type": "module", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.tools.sap/D033027/ScopingAPI.git" 13 | }, 14 | "dependencies": { 15 | "axios": "^0.27.2", 16 | "axios-oauth-client": "^1.5.0", 17 | "axios-token-interceptor": "^0.2.0", 18 | "chalk": "^5.0.1", 19 | "clear": "^0.1.0", 20 | "figlet": "^1.5.2", 21 | "inquirer": "^9.1.1", 22 | "inquirer-search-checkbox": "^1.0.0", 23 | "winston": "^3.8.2" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /applications/ScopingAPI/readme.MD: -------------------------------------------------------------------------------- 1 | # SAP Cloud ALM Scoping API Sample 2 | 3 | This is an example to illustrate how you can use SAP Cloud ALM Scoping API. The code is not production ready and you use it on your own risk. :-) 4 | Even though the Scoping API is actually built as batch oriented REST API, this example implements UI interactions. Overall the API is not intended to build UIs but rather synchronization scenarios. 5 | 6 | You can find all the details about the SAP Cloud ALM Scoping API on the [SAP API Business Hub](https://api.sap.com/api/CALM_PM/overview). 7 | 8 | ## Demo 9 | 10 | Please find a recording [here](recording/2022-09-14_11-49-53.mp4). 11 | ## Install libraries 12 | 13 | [Sad but true.](https://www.monkeyuser.com/2017/npm-delivery/) 14 | 15 | Navigate to ScopingAPI folder, and then 16 | 17 | > mkdir log && npm install 18 | 19 | ## Maintain credentials 20 | 21 | You need to have a "secret.js"-file in the form as follows with valid data. 22 | 23 | ``` 24 | export default { 25 | //no trailing "/" 26 | "apiBaseUrl": "", 27 | "authenticationUrl": "", 28 | "clientId": "", 29 | "clientSecret": "", 30 | } 31 | ``` 32 | Please make sure you have sufficient authorizations to call the APIs. 33 | The required authorisation scopes, to run the sample, are: 34 | 35 | * calm-api.processmanagement.read: to read process management entities. 36 | * calm-api.processmanagement.write: to write process management entities. 37 | * calm-api.processmanagement.delete: to delete process management entities. 38 | * calm-api.projects.read: to view projects. 39 | 40 | 41 | You can use the [service.json](service.json) example to generate keys in the BPT Cockpit. To learn how generate the needed client id and secret please check this [blog](https://blogs.sap.com/2021/08/12/sap-cloud-alm-extend-with-api-get-started-with-sap-cloud-alm-api/). 42 | 43 | ## Run the script 44 | 45 | > node index.js 46 | 47 | Please check the logs in the log directory to get insights in service timings. 48 | 49 | Have fun. -------------------------------------------------------------------------------- /applications/ScopingAPI/recording/2022-09-14_11-49-53.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAP-samples/cloud-alm-api-examples/0503338e01aad752dcf1f9d7dee8660e7a1e275d/applications/ScopingAPI/recording/2022-09-14_11-49-53.mp4 -------------------------------------------------------------------------------- /applications/ScopingAPI/secret.js.sample: -------------------------------------------------------------------------------- 1 | export default { 2 | //no trailing "/" 3 | "apiBaseUrl": "", 4 | "authenticationUrl": "", 5 | "clientId": "", 6 | "clientSecret": "", 7 | } -------------------------------------------------------------------------------- /applications/ScopingAPI/service.json: -------------------------------------------------------------------------------- 1 | { 2 | "xs-security": { 3 | "xsappname": "sap_cloud_alm_api", 4 | "authorities": [ 5 | "$XSMASTERAPPNAME.calm-api.projects.write", 6 | "$XSMASTERAPPNAME.calm-api.projects.read", 7 | "$XSMASTERAPPNAME.calm-api.tasks.write", 8 | "$XSMASTERAPPNAME.calm-api.tasks.read", 9 | "$XSMASTERAPPNAME.calm-api.processmanagement.read", 10 | "$XSMASTERAPPNAME.calm-api.processmanagement.write" 11 | ] 12 | } 13 | } -------------------------------------------------------------------------------- /applications/calm-api-consumer-java-sample/README.md: -------------------------------------------------------------------------------- 1 | # SAP Cloud ALM Public API Reference Consumer Application 2 | 3 | 4 | 5 | SAP Cloud ALM Public API reference application is built to showcase the method of developing a single code-line multi cloud application consuming public APIs of SAP Cloud ALM on SAP Business Technology Platform (SAP BTP) Cloud Foundry Environment. 6 | 7 | ## Description 8 | SAP Cloud ALM offers public APIs for Task Management and Project Management and many more functionalities of SAP Cloud ALM 9 | #### Features of the Application 10 | 11 | • The sample application provides RESTful endpoints to read projects from SAP Cloud ALM. 12 | 13 | • It calls SAP Cloud ALM's public API endpoints to perform the above operations on the provisioned customer tenant of SAP Clod ALM SaaS Application. 14 | 15 | ## Architecture 16 | 17 | ![Alt text](./documents/cloud-alm-api-sample-architecture.jpg "Architecture") 18 | 19 | A single REST controller accepts the request (GET). 20 | 21 | 22 | ## Requirements 23 | - [Java 8](https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html) 24 | - [Apache Maven 3.3+](https://maven.apache.org/download.cgi) 25 | - [Cloud Foundry CLI](https://github.com/cloudfoundry/cli#downloads) 26 | - SAP Business Technology Platform Global account 27 | - [Provision SAP Cloud ALM API Service](https://discovery-center.cloud.sap/#/serviceCatalog/object-store-service). 28 | - A Cloud Foundry user with SpaceDeveloper role to deploy the application 29 | 30 | ## Download and Installation 31 | 32 | #### Build the Application 33 | - [Clone](https://help.github.com/articles/cloning-a-repository/) the application `cloudalm-api-examples` to your system 34 | 35 | Clone URL : `https://github.com/SAP-samples/cloud-alm-api-examples.git` 36 | - Navigate to the root folder of the application and run the below maven command to build the application: 37 | ``` 38 | mvn clean install 39 | ``` 40 | 41 | #### Prerequisites 42 | - SAP Business Technology Platform with CloudFoundry Runtime enabled 43 | - Subscription to SAP Cloud ALM 44 | #### Deploy the Application on Cloud Foundry 45 | 46 | 1. Logon to the Cloud Foundry environment using the following commands on the command prompt: 47 | ``` 48 | cf api 49 | cf login 50 | ``` 51 | `api` - [URL of the Cloud Foundry landscape](https://help.sap.com/viewer/65de2977205c403bbc107264b8eccf4b/Cloud/en-US/350356d1dc314d3199dca15bd2ab9b0e.html) that you are trying to connect to. 52 | 53 | Enter username, password, org and space when prompted to. [Please click here for more information](https://help.sap.com/viewer/65de2977205c403bbc107264b8eccf4b/Cloud/en-US/75125ef1e60e490e91eb58fe48c0f9e7.html#loio4ef907afb1254e8286882a2bdef0edf4). 54 | 55 | 56 | 57 | 2. Create the Cloud Foundry SAP Cloud ALM API Service Instance 58 | 59 | - To run the application create a service by executing the below command: 60 | 61 | `cf create-service sap-cloud-alm-api standard calm-public-api-service -c cloud-alm-api-scopes.json 62 | 63 | Note:`A sample scopes file is provided [here](https://github.com/SAP-samples/cloud-alm-api-examples/blob/main/applications/calm-api-consumer-java-sample/cloud-alm-api-scopes.json) 64 | 65 | 66 | 67 | 3. Edit manifest.yml file. Replace the `` placeholder with any unique string. You can use your *User ID* or some unique ID so that the host name is unique in the CF landscape. 68 | ~~~ 69 | 70 | --- 71 | applications: 72 | - name: calm-public-api-consumer-sample-svc 73 | ------------------------------------------ 74 | | host: -cloud-alm-api-sample-svc | 75 | ------------------------------------------ 76 | memory: 2G 77 | buildpack: https://github.com/cloudfoundry/java-buildpack.git 78 | path: target/cloudalm-api-sample-1.0.0.jar 79 | services: 80 | - calm-public-api-service 81 | 82 | --- 83 | 84 | ~~~ 85 | 86 | 4. To deploy the application, navigate to the root of the application and execute the below command: 87 | ``` 88 | cf push 89 | ``` 90 | 91 | #### Test the Application 92 | 93 | [Postman Client](https://www.getpostman.com/apps) can be used to test / access the REST API endpoints. 94 | 95 | Replace the `` placeholder in the below steps with the URL of the application you deployed. 96 | 97 | 98 | 99 | 100 | ##### List all the tasks 101 | 102 | GET 103 | 104 | To get the list of all projects hit the below endpoint url. 105 | 106 | EndPoint URL : `https:///cloud-alm/projects` 107 | 108 | Content-Type : `application/json` 109 | 110 | A successful upload operation gives the following response : 111 | 112 | Status: 200 113 | 114 | Response Body: 115 | ~~~ 116 | 117 | ~~~ 118 | 119 | ## How to obtain support 120 | 121 | In case you find a bug, or you need additional support, please open an issue here in GitHub. 122 | 123 | ## Known Issues 124 | 125 | ## License 126 | 127 | Copyright (c) 2020 SAP SE or an SAP affiliate company. All rights reserved. This project is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](LICENSES/Apache-2.0.txt)file. 128 | -------------------------------------------------------------------------------- /applications/calm-api-consumer-java-sample/cloud-alm-api-scopes.json: -------------------------------------------------------------------------------- 1 | { 2 | "xs-security": { 3 | "xsappname": "sapCloudALM_Demo", 4 | "authorities": [ 5 | "$XSMASTERAPPNAME.calm-api.projects.read", 6 | "$XSMASTERAPPNAME.calm-api.tasks.read", 7 | "$XSMASTERAPPNAME.calm-api.projects.write", 8 | "$XSMASTERAPPNAME.calm-api.tasks.write" 9 | ] 10 | } 11 | } -------------------------------------------------------------------------------- /applications/calm-api-consumer-java-sample/documents/cloud-alm-api-sample-architecture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAP-samples/cloud-alm-api-examples/0503338e01aad752dcf1f9d7dee8660e7a1e275d/applications/calm-api-consumer-java-sample/documents/cloud-alm-api-sample-architecture.jpg -------------------------------------------------------------------------------- /applications/calm-api-consumer-java-sample/manifest.yml: -------------------------------------------------------------------------------- 1 | --- 2 | applications: 3 | - name: calm-public-api-consumer-sample-svc 4 | host: calm-public-api-consumer-sample-svc 5 | memory: 2G 6 | buildpack: https://github.com/cloudfoundry/java-buildpack.git 7 | path: target/cloudalm-api-sample-1.0.0.jar 8 | services: 9 | - calm-public-api-service 10 | -------------------------------------------------------------------------------- /applications/calm-api-consumer-java-sample/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.sap.refapps.cloudalm 7 | cloudalm-api-sample 8 | 1.0.0 9 | jar 10 | 11 | cloudalm-api-sample 12 | SAP Cloud ALM API reference consumer application 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-parent 17 | 2.2.4.RELEASE 18 | 19 | 20 | 21 | 22 | UTF-8 23 | UTF-8 24 | 1.8 25 | 2.3.0 26 | 3.1.1 27 | 28 | 29 | 30 | 31 | 32 | 33 | org.springframework.boot 34 | spring-boot-starter-web 35 | 36 | 37 | org.springframework.boot 38 | spring-boot-configuration-processor 39 | true 40 | 41 | 42 | org.springframework.boot 43 | spring-boot-starter-test 44 | 45 | 46 | com.fasterxml.jackson.core 47 | jackson-databind 48 | 49 | 50 | 51 | 52 | 53 | org.springframework.boot 54 | spring-boot-maven-plugin 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /applications/calm-api-consumer-java-sample/src/main/java/com/sap/refapps/cloudalm/CloudALMAPIAccessController.java: -------------------------------------------------------------------------------- 1 | package com.sap.refapps.cloudalm; 2 | 3 | import com.fasterxml.jackson.core.JsonProcessingException; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.beans.factory.annotation.Value; 7 | import org.springframework.boot.web.client.RestTemplateBuilder; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.core.env.Environment; 10 | import org.springframework.http.*; 11 | import org.springframework.web.bind.annotation.RestController; 12 | import org.springframework.web.bind.annotation.RequestMapping; 13 | import org.springframework.web.client.RestTemplate; 14 | 15 | @RestController 16 | public class CloudALMAPIAccessController { 17 | 18 | @Autowired 19 | private Environment env; 20 | @Value("${sap.cloudalm.public.api.url.eu10}") 21 | private String cloudAlmApiUrl; 22 | @RequestMapping("/cloud-alm/projects") 23 | public String getCloudALMProjects(RestTemplate restTemplate) throws JsonProcessingException { 24 | JWTTokenProvider jwtTokenProvider = new JWTTokenProvider(restTemplate); 25 | String accessToken = jwtTokenProvider.generateAccessToken(); 26 | 27 | if (accessToken != null) { 28 | System.out.println("Request Successful"); 29 | HttpHeaders headers = new HttpHeaders(); 30 | headers.setContentType(MediaType.APPLICATION_JSON); 31 | headers.set("Authorization", "Bearer "+accessToken); 32 | 33 | HttpEntity requestEntity = new HttpEntity(null,headers); 34 | ResponseEntity responseEntity = restTemplate.exchange(cloudAlmApiUrl, HttpMethod.GET, requestEntity, ProjectDto[].class); 35 | ObjectMapper mapper = new ObjectMapper(); 36 | String jsonString = mapper.writeValueAsString(responseEntity); 37 | System.out.println(jsonString); 38 | return jsonString; 39 | } else { 40 | System.out.println("Request Failed"); 41 | } 42 | return "Sorry could not fetch Access Token!"; 43 | } 44 | @Bean 45 | public RestTemplate restTemplate(RestTemplateBuilder builder) { 46 | return builder.build(); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /applications/calm-api-consumer-java-sample/src/main/java/com/sap/refapps/cloudalm/ConsumingApplication.java: -------------------------------------------------------------------------------- 1 | package com.sap.refapps.cloudalm; 2 | 3 | 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import org.springframework.boot.CommandLineRunner; 6 | import org.springframework.boot.SpringApplication; 7 | import org.springframework.boot.autoconfigure.SpringBootApplication; 8 | import org.springframework.boot.web.client.RestTemplateBuilder; 9 | import org.springframework.context.annotation.Bean; 10 | import org.springframework.http.*; 11 | import org.springframework.web.client.RestTemplate; 12 | 13 | @SpringBootApplication 14 | public class ConsumingApplication { 15 | 16 | 17 | public static void main(String[] args) { 18 | SpringApplication.run(ConsumingApplication.class, args); 19 | } 20 | 21 | 22 | 23 | } 24 | -------------------------------------------------------------------------------- /applications/calm-api-consumer-java-sample/src/main/java/com/sap/refapps/cloudalm/EnvironmentUtil.java: -------------------------------------------------------------------------------- 1 | package com.sap.refapps.cloudalm; 2 | 3 | 4 | import java.io.IOException; 5 | import java.util.Optional; 6 | 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import com.fasterxml.jackson.databind.JsonNode; 11 | import com.fasterxml.jackson.databind.ObjectMapper; 12 | 13 | public class EnvironmentUtil { 14 | 15 | private static final Logger logger = LoggerFactory.getLogger(EnvironmentUtil.class); 16 | 17 | private static String activeProfile; 18 | 19 | private static final String VCAP_SERVICE = "VCAP_SERVICES"; 20 | private static final String SAPCLOUDALMAPIs = "SAPCloudALMAPIs"; 21 | private static final String UAA = "uaa"; 22 | private static final String URL = "url"; 23 | private static final String CLIENT_ID = "clientid"; 24 | private static final String CLIENT_SECRET = "clientsecret"; 25 | private static final String CREDENTIALS = "credentials"; 26 | 27 | 28 | 29 | /** 30 | * This method is used to parse the service plan name from VCAP_SERVICES 31 | * 32 | * @return service plan name 33 | */ 34 | public static VcapServicesDto getVcapService() { 35 | Optional xsuaaUrl = Optional.empty(); 36 | Optional clientID = Optional.empty(); 37 | Optional clientSecret = Optional.empty(); 38 | VcapServicesDto vcapServicesDto = new VcapServicesDto(); 39 | 40 | final String jsonString = System.getenv(VCAP_SERVICE); 41 | if (jsonString != null) { 42 | System.out.println(jsonString); 43 | try { 44 | ObjectMapper mapper = new ObjectMapper(); 45 | JsonNode root = mapper.readTree(jsonString); 46 | JsonNode credentialsNode = root.path(SAPCLOUDALMAPIs).path(0).path(CREDENTIALS); 47 | 48 | 49 | xsuaaUrl = Optional.of(credentialsNode.path(UAA).path(URL).asText()); 50 | vcapServicesDto.setXsuaaURL(xsuaaUrl.get()); 51 | clientID = Optional.of(credentialsNode.path(UAA).path(CLIENT_ID).asText()); 52 | vcapServicesDto.setClientId(clientID.get()); 53 | clientSecret = Optional.of(credentialsNode.path(UAA).path(CLIENT_SECRET).asText()); 54 | vcapServicesDto.setClientSecret(clientSecret.get()); 55 | 56 | 57 | } catch (IOException e) { 58 | logger.error("Exception occurred: " + e); 59 | } 60 | } 61 | return vcapServicesDto; 62 | } 63 | 64 | 65 | } 66 | -------------------------------------------------------------------------------- /applications/calm-api-consumer-java-sample/src/main/java/com/sap/refapps/cloudalm/JWTTokenProvider.java: -------------------------------------------------------------------------------- 1 | package com.sap.refapps.cloudalm; 2 | 3 | 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.beans.factory.annotation.Value; 6 | import org.springframework.http.ResponseEntity; 7 | import org.springframework.http.client.support.BasicAuthorizationInterceptor; 8 | import org.springframework.web.client.RestTemplate; 9 | 10 | 11 | public class JWTTokenProvider { 12 | private final String xsuaaUrl; 13 | 14 | 15 | private static final String RETRIEVE_JWT_TOKEN_PATH = "/oauth/token?grant_type=client_credentials&response_type=token"; 16 | 17 | 18 | private RestTemplate restTemplate; 19 | 20 | @Autowired 21 | public JWTTokenProvider(RestTemplate restTemplate) { 22 | VcapServicesDto vcapServicesDto = EnvironmentUtil.getVcapService(); 23 | this.xsuaaUrl = vcapServicesDto.getXsuaaURL(); 24 | this.restTemplate = restTemplate; 25 | setupRestClient(vcapServicesDto.getClientId(), vcapServicesDto.getClientSecret()); 26 | } 27 | 28 | private void setupRestClient(String uaaClientId, String uaaClientSecret) { 29 | restTemplate.getInterceptors() 30 | .add(new BasicAuthorizationInterceptor(uaaClientId, uaaClientSecret)); 31 | } 32 | 33 | 34 | public String generateAccessToken() { 35 | ResponseEntity jwtTokenResponseEntity = restTemplate 36 | .getForEntity(xsuaaUrl + RETRIEVE_JWT_TOKEN_PATH, JwtTokenDto.class); 37 | 38 | JwtTokenDto retrievedToken = jwtTokenResponseEntity.getBody(); 39 | 40 | return retrievedToken.getAccessToken(); 41 | } 42 | } -------------------------------------------------------------------------------- /applications/calm-api-consumer-java-sample/src/main/java/com/sap/refapps/cloudalm/JWTTokenResponse.java: -------------------------------------------------------------------------------- 1 | package com.sap.refapps.cloudalm; 2 | 3 | public class JWTTokenResponse { 4 | public void setAccess_token(String access_token) { 5 | this.access_token = access_token; 6 | } 7 | 8 | public String getAccess_token() { 9 | return access_token; 10 | } 11 | 12 | public String access_token; 13 | } 14 | -------------------------------------------------------------------------------- /applications/calm-api-consumer-java-sample/src/main/java/com/sap/refapps/cloudalm/JwtTokenDto.java: -------------------------------------------------------------------------------- 1 | package com.sap.refapps.cloudalm; 2 | 3 | 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | 6 | public class JwtTokenDto { 7 | 8 | private final String accessToken; 9 | 10 | public JwtTokenDto(@JsonProperty("access_token") String accessToken) { 11 | this.accessToken = accessToken; 12 | } 13 | 14 | public String getAccessToken() { 15 | return accessToken; 16 | } 17 | 18 | 19 | } 20 | 21 | -------------------------------------------------------------------------------- /applications/calm-api-consumer-java-sample/src/main/java/com/sap/refapps/cloudalm/ProjectDto.java: -------------------------------------------------------------------------------- 1 | package com.sap.refapps.cloudalm; 2 | 3 | public class ProjectDto { 4 | public String name; 5 | 6 | public String getName() { 7 | return name; 8 | } 9 | 10 | public void setName(String name) { 11 | this.name = name; 12 | } 13 | 14 | public String getId() { 15 | return id; 16 | } 17 | 18 | public void setId(String id) { 19 | this.id = id; 20 | } 21 | 22 | public String getStatus() { 23 | return status; 24 | } 25 | 26 | public void setStatus(String status) { 27 | this.status = status; 28 | } 29 | 30 | public String id; 31 | public String status; 32 | } 33 | -------------------------------------------------------------------------------- /applications/calm-api-consumer-java-sample/src/main/java/com/sap/refapps/cloudalm/VcapServicesDto.java: -------------------------------------------------------------------------------- 1 | package com.sap.refapps.cloudalm; 2 | 3 | public class VcapServicesDto { 4 | public String xsuaaURL; 5 | 6 | public String getXsuaaURL() { 7 | return xsuaaURL; 8 | } 9 | 10 | public void setXsuaaURL(String xsuaaURL) { 11 | this.xsuaaURL = xsuaaURL; 12 | } 13 | 14 | public String getClientId() { 15 | return clientId; 16 | } 17 | 18 | public void setClientId(String clientId) { 19 | this.clientId = clientId; 20 | } 21 | 22 | public String getClientSecret() { 23 | return clientSecret; 24 | } 25 | 26 | public void setClientSecret(String clientSecret) { 27 | this.clientSecret = clientSecret; 28 | } 29 | 30 | public String clientId; 31 | public String clientSecret; 32 | 33 | 34 | 35 | } 36 | -------------------------------------------------------------------------------- /applications/calm-api-consumer-java-sample/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | #server port 2 | server.port=5555 3 | 4 | #spring.servlet.multipart.max-file-size=2050MB 5 | #spring.servlet.multipart.maxRequestSize=2050MB 6 | spring.servlet.multipart.max-file-size=-1 7 | spring.servlet.multipart.maxRequestSize=-1 8 | spring.servlet.multipart.enabled=false 9 | sap.cloudalm.public.api.url.eu10=https://eu10.alm.cloud.sap/api/calm-projects/v1/projects 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /applications/calm-api-consumer-ui5-sample/.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | node_modules 3 | dist 4 | mta_archives 5 | resources 6 | -------------------------------------------------------------------------------- /applications/calm-api-consumer-ui5-sample/Makefile_20210726123408.mta: -------------------------------------------------------------------------------- 1 | # Generated with Cloud MTA Build Tool version 1.2.1 2 | version=0.0.1 3 | MBT=mbt 4 | ifndef p 5 | $(error platform flag is expected. e.g. use make -f makefile.mta p=cf) 6 | endif 7 | target_provided=true 8 | ifndef t 9 | t="$(CURDIR)" 10 | target_provided=false 11 | endif 12 | ifndef strict 13 | strict=true 14 | endif 15 | ifndef mtar 16 | mtar="*" 17 | endif 18 | modules := $(shell $(MBT) provide modules -d=dev) 19 | modules := $(subst ],,$(subst [,,$(modules))) 20 | # List of all the recipes to be executed during the build process 21 | .PHONY: all pre_validate pre_build validate $(modules) post_build meta mtar cleanup 22 | # Default target compile all 23 | all: pre_validate pre_build validate $(modules) post_build meta mtar cleanup 24 | # Validate mta.yaml 25 | pre_validate: 26 | @$(MBT) validate -r=${strict} -x="paths" 27 | pre_build: pre_validate 28 | @$(MBT) project build -p=pre 29 | 30 | 31 | # Execute module build 32 | define build_rule 33 | $(1): validate 34 | @$(MBT) module build -m=$(1) -p=${p} -t=${t} 35 | endef 36 | 37 | $(foreach mod,$(modules),$(eval $(call build_rule,$(mod))))# Create META-INF folder with MANIFEST.MF & mtad.yaml 38 | meta: $(modules) post_build 39 | @$(MBT) gen meta -p=${p} -t=${t} 40 | 41 | post_build: $(modules) 42 | @$(MBT) project build -p=post -t=${t} 43 | 44 | # Validate mta.yaml 45 | validate: pre_build 46 | @$(MBT) validate -r=${strict} 47 | 48 | # Pack as MTAR artifact 49 | mtar: $(modules) meta 50 | @$(MBT) gen mtar --mtar=${mtar} --target_provided=${target_provided} -t=${t} 51 | 52 | cleanup: mtar 53 | # Remove tmp folder 54 | @$(MBT) clean -t=${t} -------------------------------------------------------------------------------- /applications/calm-api-consumer-ui5-sample/README.md: -------------------------------------------------------------------------------- 1 | # SAP Cloud ALM Extend with APIs 2 | 3 | 4 | ## Description 5 | This SAPUI5 custom application has been developed on SAP BTP platform and serves as an example of what it is possible to achieve with the [SAP Cloud ALM Rest APIs](https://api.sap.com/package/SAPCloudALM/rest). Indeed, only two of the four offered APIs ([Projects](https://api.sap.com/api/CALM_PJM/overview) and [Tasks](https://api.sap.com/api/CALM_TKM/overview)) are used to realize this application, but many other possibilities are available with the full set of provided APIs. 6 | 7 | With this application, the user will be able to consult all projects that are defined in SAP Cloud ALM for Implementation. He will be able to manage all tasks part of a project: 8 | - display tasks within a Kanban view. 9 | - add a task to a project, modify its content, or even delete it. 10 | - change a task status using drag and drop capabilities of SAPUI5. 11 | An additional feature offered by this application is a statistical view that gives a task distribution overview chart (for a given project) based on the task due date (On time vs Overdue tasks). Figures are computed on-the-fly and asynchronously when retrieving the tasks list from the Rest APIs. 12 | 13 | **You will find in this README.md file all the information you need to run this app.** 14 | 15 | ## Prerequisites 16 | *Skip those parts if you already have everything, you can go to [Download and installation](#IMPORT)* 17 | - Have a working SAP BTP Cockpit environment 18 | - Have a working SAP Business Application Studio *(see explanation below)* 19 | - [Have a valid Cloud ALM instance with API Keys](https://blogs.sap.com/2021/08/12/sap-cloud-alm-extend-with-api-get-started-with-sap-cloud-alm-api/) 20 | 21 | #### Subscribe and run the Dev Space Manager in SAP Business Application Studio 22 | To open the SAP Business Application Studio, go in your SAP BTP Cockpit: 23 | 24 | access_ sap_ business_ app_ studio 25 | 26 | From that, create a dev space by clicking on the *Create Dev Space* button and choosing a space for Full Stack Cloud Applications. You can now start it with the associated button you will see on the right. It can take some time but when it displays `STARTED`, its name becomes clickable and you can enter it. 27 | 28 | When it is opened, do not forget to connect to your Cloud Foundry account. Here are the steps to do it: 29 | - Open the preferences (bottom left of the screen) 30 | - Search “cloud foundry” in the search bar 31 | - Check the “Display the current Cloud Foundry target information in the status bar” checkbox 32 | - Click on “Connect to CF” in the bottom left corner 33 | - Fill in the successive fields with the right information for your subaccount (you can find the endpoint in the cockpit from your subaccount overview) 34 | - On the bottom left corner is now written your BTP tenant. 35 | 36 | 37 | ## Download and installation 38 | - [Create destinations in your BTP Cockpit](#destinations) 39 | - [Clone this repository in your workspace](#importProject) 40 | - [Install dependencies and build](#builds) 41 | - [Run locally](#local) 42 | - [Deploy and run in the BTP Cockpit](#deploy) 43 | 44 | 45 | #### 1. Create destinations in your BTP Cockpit 46 | 47 | As explained before, you need to create two specific destinations to run the application correctly. Connect to your SAP BTP Cockpit account, select one of your subaccounts and click on *Destinations* in the ***Connectivity*** section of the left menu. Click on *"New Destination"* to create each destination as in the images below : 48 | 49 | cloudalmapi_sandbox 50 | cloudalmapi 51 | 52 | *The URL field should follow this template : https://{region}.alm.cloud.sap/ with the {region} parameter corresponding to the acronym of the region of the servers that host your cloud foundry account*. 53 | 54 | #### 2. Clone this repository in your workspace 55 | Open a Dev Space in your Dev Space Manager and then, from the "Welcome" menu, click on "Clone from git". You can now fill the field at the top (*"Provide Repository URL"*) and fill the needed fields until it is done. 56 | welcome menu 57 | #### 3. Install dependencies and build 58 | In a terminal, go in the root directory, and run: 59 | ``` 60 | npm install 61 | ``` 62 | Then, among the NPM Scripts (on the left of your screen in the SAP Business Application Studio), right click on the ***build*** script and run it. 63 | Once the task is finished, you can also run the ***build:mta*** script. It builds a MTA Archive that you can deploy later in your BTP Cockpit if you want to. 64 | 65 | #### 4. Run locally 66 | Among the NPM Scripts, you can run the ***start*** script. It will launch the application in a new tab. 67 | 68 | #### 5. Deploy and run in the BTP Cockpit 69 | In the file explorer (on the left of your screen in the SAP Business Application Studio), open the ***mta_archives*** directory and right click on the *archive.mtar* file. Then, select *Deploy MTA Archive*. Wait for the task to finish and you can now find your app in your BTP Cockpit. 70 | -------------------------------------------------------------------------------- /applications/calm-api-consumer-ui5-sample/approuter/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "html5-apps-approuter", 3 | "description": "Node.js based application router service for html5-apps", 4 | "dependencies": { 5 | "@sap/approuter": "^6.8.0" 6 | }, 7 | "scripts": { 8 | "start": "node node_modules/@sap/approuter/approuter.js" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /applications/calm-api-consumer-ui5-sample/approuter/xs-app.json: -------------------------------------------------------------------------------- 1 | { 2 | "welcomeFile": "sapuidemocloudalmapi/index.html", 3 | "authenticationMethod": "none", 4 | "routes": [ ] 5 | } -------------------------------------------------------------------------------- /applications/calm-api-consumer-ui5-sample/mta.yaml: -------------------------------------------------------------------------------- 1 | _schema-version: "3.2" 2 | ID: sap-btp-sapui5 3 | description: A Fiori application. 4 | version: 0.0.1 5 | modules: 6 | - name: sap-btp-sapui5-app-router 7 | type: approuter.nodejs 8 | path: approuter 9 | parameters: 10 | disk-quota: 256M 11 | memory: 256M 12 | requires: 13 | - name: sap-btp-sapui5-repo-runtime 14 | - name: sap-btp-sapui5-dest-srv 15 | - name: sap-btp-sapui5-uaa 16 | - name: sap-btp-sapui5-dest-content 17 | type: com.sap.application.content 18 | requires: 19 | - name: sap-btp-sapui5-dest-srv 20 | parameters: 21 | content-target: true 22 | - name: sap-btp-sapui5-repo-host 23 | parameters: 24 | service-key: 25 | name: sap-btp-sapui5-repo-host-key 26 | - name: sap-btp-sapui5-uaa 27 | parameters: 28 | service-key: 29 | name: sap-btp-sapui5-uaa-key 30 | parameters: 31 | content: 32 | instance: 33 | destinations: 34 | - Name: sap-btp-sapui5_repo_host 35 | ServiceInstanceName: sap-btp-sapui5-html5-srv 36 | ServiceKeyName: sap-btp-sapui5-repo-host-key 37 | sap.cloud.service: sap-btp-sapui5 38 | - Authentication: OAuth2UserTokenExchange 39 | Name: sap-btp-sapui5_uaa 40 | ServiceInstanceName: sap-btp-sapui5-xsuaa-srv 41 | ServiceKeyName: sap-btp-sapui5-uaa-key 42 | sap.cloud.service: sap-btp-sapui5 43 | existing_destinations_policy: ignore 44 | build-parameters: 45 | no-source: true 46 | - name: sap-btp-sapui5-app-content 47 | type: com.sap.application.content 48 | path: . 49 | requires: 50 | - name: sap-btp-sapui5-repo-host 51 | parameters: 52 | content-target: true 53 | build-parameters: 54 | build-result: resources 55 | requires: 56 | - artifacts: 57 | - sapbtpsapui5.zip 58 | name: sapbtpsapui5 59 | target-path: resources/ 60 | - name: sapbtpsapui5 61 | type: html5 62 | path: . 63 | build-parameters: 64 | build-result: dist 65 | builder: custom 66 | commands: 67 | - npm install 68 | - npm run build:cf 69 | supported-platforms: [] 70 | resources: 71 | - name: sap-btp-sapui5-dest-srv 72 | type: org.cloudfoundry.managed-service 73 | parameters: 74 | config: 75 | HTML5Runtime_enabled: true 76 | init_data: 77 | instance: 78 | destinations: 79 | - Authentication: NoAuthentication 80 | Name: ui5 81 | ProxyType: Internet 82 | Type: HTTP 83 | URL: https://ui5.sap.com 84 | existing_destinations_policy: update 85 | version: 1.0.0 86 | service: destination 87 | service-name: sap-btp-sapui5-dest-srv 88 | service-plan: lite 89 | - name: sap-btp-sapui5-uaa 90 | type: org.cloudfoundry.managed-service 91 | parameters: 92 | path: ./xs-security.json 93 | service: xsuaa 94 | service-name: sap-btp-sapui5-xsuaa-srv 95 | service-plan: application 96 | - name: sap-btp-sapui5-repo-host 97 | type: org.cloudfoundry.managed-service 98 | parameters: 99 | service: html5-apps-repo 100 | service-name: sap-btp-sapui5-html5-srv 101 | service-plan: app-host 102 | - name: sap-btp-sapui5-repo-runtime 103 | type: org.cloudfoundry.managed-service 104 | parameters: 105 | service: html5-apps-repo 106 | service-plan: app-runtime 107 | parameters: 108 | deploy_mode: html5-repo 109 | enable-parallel-deployments: true 110 | 111 | -------------------------------------------------------------------------------- /applications/calm-api-consumer-ui5-sample/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sapui5", 3 | "version": "0.0.1", 4 | "private": true, 5 | "description": "A Fiori application.", 6 | "keywords": [ 7 | "ui5", 8 | "openui5", 9 | "sapui5" 10 | ], 11 | "main": "webapp/index.html", 12 | "scripts": { 13 | "start": "fiori run --open 'index.html'", 14 | "start-local": "fiori run --config ./ui5-local.yaml --open 'index.html'", 15 | "start-noflp": "fiori run --open 'index.html'", 16 | "build": "ui5 build -a --clean-dest --include-task=generateManifestBundle generateCachebusterInfo", 17 | "deploy": "cf deploy mta_archives/archive.mtar", 18 | "deploy-config": "fiori add deploy-config", 19 | "unit-tests": "fiori run --open test/unit/unitTests.qunit.html", 20 | "int-tests": "fiori run --open test/integration/opaTests.qunit.html", 21 | "build:cf": "ui5 build preload --clean-dest --config ui5-deploy.yaml --include-task=generateManifestBundle generateCachebusterInfo", 22 | "build:mta": "rimraf resources mta_archives && mbt build --mtar archive", 23 | "undeploy": "cf undeploy sap-btp-sapui5 --delete-services --delete-service-keys" 24 | }, 25 | "devDependencies": { 26 | "@sap/ui5-builder-webide-extension": "1.0.x", 27 | "@sap/ux-ui5-tooling": "^1.2.5", 28 | "@ui5/cli": "^2.11.1", 29 | "@ui5/fs": "^2.0.6", 30 | "@ui5/logger": "^2.0.1", 31 | "mbt": "^1.0.15", 32 | "rimraf": "3.0.2", 33 | "ui5-task-zipper": "^0.3.1" 34 | }, 35 | "ui5": { 36 | "dependencies": [ 37 | "@sap/ux-ui5-tooling", 38 | "@sap/ui5-builder-webide-extension", 39 | "ui5-task-zipper", 40 | "mbt" 41 | ] 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /applications/calm-api-consumer-ui5-sample/ui5-deploy.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://sap.github.io/ui5-tooling/schema/ui5.yaml.json 2 | specVersion: '1.0' 3 | metadata: 4 | name: sap.btp.sapui5 5 | type: application 6 | ui5Version: 7 | ui5Theme: sap_fiori_3 8 | resources: 9 | configuration: 10 | propertiesFileSourceEncoding: UTF-8 11 | builder: 12 | resources: 13 | excludes: 14 | - "/test/**" 15 | - "/localService/**" 16 | customTasks: 17 | - name: webide-extension-task-updateManifestJson 18 | beforeTask: generateManifestBundle 19 | configuration: 20 | appFolder: webapp 21 | destDir: dist 22 | - name: ui5-task-zipper 23 | afterTask: generateCachebusterInfo 24 | configuration: 25 | archiveName: sapbtpsapui5 26 | additionalFiles: 27 | - xs-app.json 28 | -------------------------------------------------------------------------------- /applications/calm-api-consumer-ui5-sample/ui5-local.yaml: -------------------------------------------------------------------------------- 1 | specVersion: '2.0' 2 | metadata: 3 | name: 'sapui5' 4 | type: application 5 | framework: 6 | name: SAPUI5 7 | version: '1.91.0' 8 | libraries: 9 | - name: sap.f 10 | - name: sap.m 11 | - name: sap.suite.ui.generic.template 12 | - name: sap.ui.comp 13 | - name: sap.ui.core 14 | - name: sap.ui.generic.app 15 | - name: sap.ui.table 16 | - name: sap.ushell 17 | - name: themelib_sap_fiori_3 18 | server: 19 | customMiddleware: 20 | - name: fiori-tools-proxy 21 | afterMiddleware: compression 22 | configuration: 23 | ignoreCertError: false # If set to true, certificate errors will be ignored. E.g. self-signed certificates will be accepted 24 | backend: 25 | - path: /V2 26 | url: https://services.odata.org 27 | - name: fiori-tools-appreload 28 | afterMiddleware: compression 29 | configuration: 30 | port: 35729 31 | path: webapp 32 | -------------------------------------------------------------------------------- /applications/calm-api-consumer-ui5-sample/ui5.yaml: -------------------------------------------------------------------------------- 1 | specVersion: '1.0' 2 | metadata: 3 | name: 'sapui5' 4 | type: application 5 | ui5Theme: sap_fiori_3 6 | server: 7 | customMiddleware: 8 | - name: fiori-tools-proxy 9 | afterMiddleware: compression 10 | configuration: 11 | debug: true 12 | ignoreCertError: true # If set to true, certificate errors will be ignored. E.g. self-signed certificates will be accepted 13 | backend: 14 | - path: /V3 15 | url: https://services.odata.org 16 | - path: /SAPCALM 17 | destination: cloudalmapi_sandbox 18 | - path: /api 19 | destination: cloudalmapi 20 | ui5: 21 | path: 22 | - /resources 23 | - /test-resources 24 | url: https://ui5.sap.com 25 | version: # The UI5 version, for instance, 1.78.1. Empty means latest version 26 | - name: fiori-tools-appreload 27 | afterMiddleware: compression 28 | configuration: 29 | port: 35729 30 | path: webapp 31 | -------------------------------------------------------------------------------- /applications/calm-api-consumer-ui5-sample/webapp/Component.js: -------------------------------------------------------------------------------- 1 | sap.ui.define([ 2 | "sap/ui/core/UIComponent", 3 | "sap/ui/demo/cloudalmapi/ModelManager", 4 | "./utils/Constants" 5 | ], function (UIComponent, ModelManager, Constants) { 6 | "use strict"; 7 | 8 | 9 | return UIComponent.extend("sap.ui.demo.cloudalmapi.Component", { 10 | 11 | init : function () { 12 | // call the init function of the parent 13 | UIComponent.prototype.init.apply(this, arguments); 14 | this.modelManager = new ModelManager(this); 15 | this.modelManager.init(); 16 | 17 | // set constants model 18 | var oConstantsModel = new sap.ui.model.json.JSONModel(); 19 | oConstantsModel.setData(Constants); 20 | this.setModel(oConstantsModel,"constants"); 21 | }, 22 | 23 | getModelManager : function() { 24 | return this.modelManager; 25 | } 26 | 27 | 28 | }); 29 | 30 | }); -------------------------------------------------------------------------------- /applications/calm-api-consumer-ui5-sample/webapp/controller/App.controller.js: -------------------------------------------------------------------------------- 1 | sap.ui.define([ 2 | "sap/ui/core/mvc/Controller" 3 | ], function (Controller) { 4 | "use strict"; 5 | 6 | return Controller.extend("sap.ui.demo.cloudalmapi.controller.App", { 7 | 8 | onInit : function() { 9 | this.getView().byId("grid").setSnapToRow(true); 10 | this.mModelManager = this.getOwnerComponent().getModelManager(); 11 | 12 | }, 13 | 14 | /** 15 | * Set the destination in the ModelManager 16 | * Load and display the projects 17 | * @param {Event} oEvent - Selection of an item from the "Select a Destination" dropdown 18 | */ 19 | onRESTCall: function(oEvent){ 20 | var destination = oEvent.getParameter("item").getKey(); 21 | this.mModelManager.setDestination(destination); 22 | this.mModelManager.attachRequestLoadProjectsEnd( 23 | function(){this.getView().byId("menuBTN").setText("Connected: " + destination);}.bind(this) 24 | ); 25 | this.mModelManager.attachRequestLoadProjectsError( 26 | function(){this.getView().byId("menuBTN").setText("Select a destination");}.bind(this) 27 | ); 28 | this.mModelManager.getProjects(); 29 | 30 | } 31 | }); 32 | 33 | }); -------------------------------------------------------------------------------- /applications/calm-api-consumer-ui5-sample/webapp/controller/PieChart.controller.js: -------------------------------------------------------------------------------- 1 | sap.ui.define([ 2 | "sap/ui/core/mvc/Controller", 3 | "sap/ui/model/Filter", 4 | "sap/ui/model/FilterOperator" 5 | ], function(Controller, Filter, FilterOperator) { 6 | "use strict"; 7 | return Controller.extend("sap.ui.demo.cloudalmapi.controller.PieChart", { 8 | 9 | onInit: function () { 10 | this.mModelManager = this.getOwnerComponent().getModelManager(); 11 | // register event 12 | this.mModelManager.attachRequestLoadTasksStart(this.enableBusyIndicator.bind(this)); 13 | this.mModelManager.attachRequestLoadTasksEnd(this.disableBusyIndicator.bind(this)); 14 | }, 15 | 16 | enableBusyIndicator : function() { 17 | this.byId("idVizFrame").setBusy(true); 18 | }, 19 | 20 | disableBusyIndicator : function() { 21 | this.byId("idVizFrame").setBusy(false); 22 | } 23 | }) 24 | }) -------------------------------------------------------------------------------- /applications/calm-api-consumer-ui5-sample/webapp/controller/ProjectList.controller.js: -------------------------------------------------------------------------------- 1 | sap.ui.define([ 2 | "sap/ui/core/mvc/Controller", 3 | "sap/ui/model/Filter", 4 | "sap/ui/model/FilterOperator" 5 | ], function (Controller, Filter, FilterOperator) { 6 | "use strict"; 7 | return Controller.extend("sap.ui.demo.cloudalmapi.controller.ProjectList", { 8 | 9 | onInit : function(){ 10 | var oTable = this.byId("projectList"); 11 | this.mModelManager = this.getOwnerComponent().getModelManager(); 12 | 13 | // Hide the ugly selector, now the user can select by clicking anywhere on the row 14 | oTable.setSelectionBehavior("RowOnly"); 15 | 16 | // Register to the events 17 | this.mModelManager.attachRequestLoadProjectsStart(this.enableBusyIndicator.bind(this)); 18 | this.mModelManager.attachRequestLoadProjectsEnd(this.disableBusyIndicator.bind(this)); 19 | }, 20 | 21 | enableBusyIndicator : function() { 22 | this.byId("tablehbox").setBusy(true); 23 | }, 24 | 25 | disableBusyIndicator : function() { 26 | this.byId("tablehbox").setBusy(false); 27 | }, 28 | 29 | /** 30 | * Filter the projects from the search bar 31 | * Called everytime a letter is typed or deleted in the search bar 32 | * @param {Event} oEvent - Change of the content of the search bar 33 | */ 34 | onFilterProjects : function (oEvent) { 35 | 36 | // Build a filter array 37 | var aFilter = []; 38 | 39 | // Add the needed filter 40 | var sQuery = this.getView().byId("input").getValue(); 41 | if (sQuery) { 42 | aFilter.push(new Filter("name", FilterOperator.Contains, sQuery)); 43 | } 44 | 45 | // Bind the filter to the rows of the table 46 | var oList = this.byId("projectList"); 47 | var oBinding = oList.getBinding("rows"); 48 | oBinding.filter(aFilter); 49 | }, 50 | 51 | /** 52 | * Load the Task model and dispay the tasks in the Kanban and the Pie Chart 53 | * Called everytime the user selects or unselects a row in the Project List Table 54 | * @param {Event} oEvent - Project selection 55 | */ 56 | showTasks : function(oEvent) { 57 | if(this.byId("projectList").getSelectedIndices().length != 0){ 58 | this.mModelManager.getTasks( oEvent.getParameter("rowContext").getObject().id); 59 | } else { 60 | // No project selected so no tasks to show 61 | this.mModelManager.flushTasks(); 62 | } 63 | }, 64 | 65 | /** 66 | * Reload all the projects 67 | * Triggered by a button at the top right corner of the table 68 | */ 69 | onRefreshProjects : function() { 70 | this.mModelManager.getProjects(); 71 | this.mModelManager.flushTasks(); 72 | } 73 | }); 74 | }); -------------------------------------------------------------------------------- /applications/calm-api-consumer-ui5-sample/webapp/css/style.css: -------------------------------------------------------------------------------- 1 | html[dir="ltr"] .myAppDemoWT .myCustomButton.sapMBtn { 2 | margin-right: 0.125rem 3 | } 4 | 5 | html[dir="rtl"] .myAppDemoWT .myCustomButton.sapMBtn { 6 | margin-left: 0.125rem 7 | } 8 | 9 | .myAppDemoWT .myCustomText { 10 | display: inline-block; 11 | font-weight: bold; 12 | } 13 | 14 | [data-iconcolor='darkred'] .sapUiIcon{ 15 | color: darkred !important; 16 | } 17 | 18 | [data-iconcolor='red'] .sapUiIcon{ 19 | color: red !important; 20 | } 21 | 22 | [data-iconcolor='orange'] .sapUiIcon{ 23 | color: orange !important; 24 | } 25 | 26 | [data-iconcolor='green'] .sapUiIcon{ 27 | color: green !important; 28 | } 29 | -------------------------------------------------------------------------------- /applications/calm-api-consumer-ui5-sample/webapp/i18n/i18n.properties: -------------------------------------------------------------------------------- 1 | # App Descriptor 2 | appTitle=SAPUI5 Cloud ALM API 3 | appDescription=A simple app to consume Cloud ALM APIs 4 | 5 | # Project List 6 | projectListTitle=Projects 7 | homePageTitle=SAPUI5 Cloud ALM APIs 8 | 9 | # Error 10 | errorMsgProjects=Please check the Destination configuration in your BTP Cockpit or try using another Destination. -------------------------------------------------------------------------------- /applications/calm-api-consumer-ui5-sample/webapp/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | SAPUI5 Cloud ALM API 7 | 18 | 19 | 20 |
21 | 22 | -------------------------------------------------------------------------------- /applications/calm-api-consumer-ui5-sample/webapp/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "_version": "1.21.0", 3 | "sap.app": { 4 | "id": "sap.ui.demo.cloudalmapi", 5 | "type": "application", 6 | "i18n": { 7 | "bundleUrl": "i18n/i18n.properties", 8 | "supportedLocales": [ 9 | "" 10 | ], 11 | "fallbackLocale": "" 12 | }, 13 | "title": "{{appTitle}}", 14 | "description": "{{appDescription}}", 15 | "applicationVersion": { 16 | "version": "1.0.0" 17 | }, 18 | "dataSources": { 19 | "projectRemote": { 20 | "uri": "/SAPCALM/calm-projects/v1/projects", 21 | "type": "JSON" 22 | } 23 | } 24 | }, 25 | "sap.ui": { 26 | "technology": "UI5", 27 | "deviceTypes": { 28 | "desktop": true, 29 | "tablet": true, 30 | "phone": true 31 | } 32 | }, 33 | "sap.ui5": { 34 | "rootView": { 35 | "viewName": "sap.ui.demo.cloudalmapi.view.App", 36 | "type": "XML", 37 | "async": true, 38 | "id": "app" 39 | }, 40 | "dependencies": { 41 | "minUI5Version": "1.60", 42 | "libs": { 43 | "sap.m": {} 44 | } 45 | }, 46 | "models": { 47 | "i18n": { 48 | "type": "sap.ui.model.resource.ResourceModel", 49 | "settings": { 50 | "bundleName": "sap.ui.demo.cloudalmapi.i18n.i18n", 51 | "supportedLocales": [ 52 | "" 53 | ], 54 | "fallbackLocale": "" 55 | } 56 | }, 57 | "tasksStatistic": { 58 | "type": "sap.ui.model.json.JSONModel", 59 | "uri": "models/TasksStatistics.json" 60 | }, 61 | "kanbanModel": { 62 | "type": "sap.ui.model.json.JSONModel", 63 | "uri": "models/KanbanModel.json" 64 | }, 65 | "project": { 66 | "type": "sap.ui.model.json.JSONModel", 67 | "uri": "models/Projects.json" 68 | }, 69 | "task": { 70 | "type": "sap.ui.model.json.JSONModel", 71 | "uri": "models/Tasks.json" 72 | }, 73 | "infoTask": { 74 | "type": "sap.ui.model.json.JSONModel", 75 | "uri": "models/InfoTask.json" 76 | } 77 | 78 | }, 79 | "resources": { 80 | "css": [ 81 | { 82 | "uri": "css/style.css" 83 | } 84 | ] 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /applications/calm-api-consumer-ui5-sample/webapp/models/InfoTask.json: -------------------------------------------------------------------------------- 1 | [ 2 | 3 | ] -------------------------------------------------------------------------------- /applications/calm-api-consumer-ui5-sample/webapp/models/KanbanModel.json: -------------------------------------------------------------------------------- 1 | { 2 | "open": [], 3 | "blocked": [], 4 | "in_progress": [], 5 | "done": [] 6 | } 7 | -------------------------------------------------------------------------------- /applications/calm-api-consumer-ui5-sample/webapp/models/Projects.json: -------------------------------------------------------------------------------- 1 | 2 | [ 3 | 4 | ] 5 | -------------------------------------------------------------------------------- /applications/calm-api-consumer-ui5-sample/webapp/models/Tasks.json: -------------------------------------------------------------------------------- 1 | [ 2 | 3 | ] -------------------------------------------------------------------------------- /applications/calm-api-consumer-ui5-sample/webapp/models/TasksStatistics.json: -------------------------------------------------------------------------------- 1 | [ 2 | 3 | ] 4 | -------------------------------------------------------------------------------- /applications/calm-api-consumer-ui5-sample/webapp/utils/Constants.js: -------------------------------------------------------------------------------- 1 | sap.ui.define([], function () { 2 | "use strict"; 3 | 4 | return { 5 | TASKS_STATUS: { 6 | TASKOPEN: "OPEN", 7 | TASKBLOCKED: "BLK", 8 | TASKINPROGRESS: "INP", 9 | TASKDONE: "CLOSE" 10 | } 11 | }; 12 | }); -------------------------------------------------------------------------------- /applications/calm-api-consumer-ui5-sample/webapp/utils/formatter.js: -------------------------------------------------------------------------------- 1 | sap.ui.define([], function () { 2 | "use strict"; 3 | return { 4 | priorityTextToId: function (priority) { 5 | switch (priority) { 6 | case "VeryHigh": 7 | return 10; 8 | case "High": 9 | return 20; 10 | case "Medium": 11 | return 30; 12 | case "Low": 13 | return 40; 14 | default: 15 | return 0; 16 | } 17 | }, 18 | 19 | priorityColorScheme: function(priorityId){ 20 | switch (priorityId) { 21 | case 10: 22 | return "darkred"; 23 | case 20: 24 | return "red"; 25 | case 30: 26 | return "orange"; 27 | default: 28 | return "green"; 29 | } 30 | }, 31 | 32 | priorityIcon: function(priorityId){ 33 | switch (priorityId) { 34 | case 10: 35 | return "sap-icon://collapse-group"; 36 | case 20: 37 | return "sap-icon://navigation-up-arrow"; 38 | case 30: 39 | return "sap-icon://less"; 40 | default: 41 | return "sap-icon://navigation-down-arrow"; 42 | } 43 | } 44 | }; 45 | }); -------------------------------------------------------------------------------- /applications/calm-api-consumer-ui5-sample/webapp/view/App.view.xml: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /applications/calm-api-consumer-ui5-sample/webapp/view/CreateTask.fragment.xml: -------------------------------------------------------------------------------- 1 | 4 | 11 | 12 | 13 | 23 | 24 | 25 | 35 | 36 | 37 | 48 | 49 | 50 | 61 | 62 | 63 |