├── .abapgit.xml ├── .editorconfig ├── .gitattributes ├── .github └── workflows │ ├── ci.yml │ ├── downport702.yml │ ├── playwright.yml │ └── preview-deployment.yml ├── .gitignore ├── .npmrc ├── .vscode └── extensions.json ├── LICENSE ├── README.md ├── abap_transpile.json ├── abaplint-app.jsonc ├── abaplint-downport.jsonc ├── abaplint-onprem.jsonc ├── abaplint.jsonc ├── cf ├── cf-build.yml ├── mta.yaml └── sed.sh ├── package-lock.json ├── package.json ├── playwright.config.js ├── src ├── package.devc.xml ├── zcl_otm_table_maintenance.clas.abap └── zcl_otm_table_maintenance.clas.xml ├── test ├── index.html ├── index.mjs ├── playwright.spec.mjs ├── setup.mjs ├── web.mjs ├── zcl_http_handler.clas.abap ├── zcl_otm_table_maintenance.clas.testclasses.abap └── zopentest.tabl.xml └── webpack.config.js /.abapgit.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | E 6 | /src/ 7 | PREFIX 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # 1 space indentation for xml files 7 | [*.xml] 8 | charset = utf-8 9 | end_of_line = lf 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | indent_style = space 13 | indent_size = 1 14 | 15 | # match the format used by abapGit 16 | [*.{abap,js,json,html,css}] 17 | charset = utf-8 18 | end_of_line = lf 19 | insert_final_newline = true 20 | trim_trailing_whitespace = true 21 | indent_size = 2 22 | indent_style = space -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # newlines 2 | * text=auto eol=lf 3 | 4 | *.xml text eol=lf 5 | *.abap text eol=lf 6 | *.md text eol=lf 7 | *.txt text eol=lf 8 | *.yml text eol=lf 9 | *.yaml text eol=lf 10 | *.js text eol=lf 11 | *.json text eol=lf 12 | *.html text eol=lf 13 | *.css text eol=lf 14 | *.svg text eol=lf 15 | *.sh text eol=lf 16 | 17 | *.jpg binary 18 | *.gif binary 19 | *.png binary 20 | *.jpeg binary -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v4 10 | - uses: actions/setup-node@v4 11 | - run: npm ci 12 | - run: npm test 13 | -------------------------------------------------------------------------------- /.github/workflows/downport702.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | downport702: 7 | runs-on: ubuntu-latest 8 | timeout-minutes: 5 9 | steps: 10 | - uses: actions/checkout@v4 11 | - uses: actions/setup-node@v4 12 | - run: npm ci 13 | - run: npm run lint 14 | - run: npm run downport 15 | - run: rm src/* 16 | - run: cp downport/* src/ 17 | - uses: actions/upload-artifact@v4 18 | with: 19 | name: downport702 20 | path: | 21 | src/* 22 | .abapgit.xml -------------------------------------------------------------------------------- /.github/workflows/playwright.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | playwright: 7 | runs-on: ubuntu-latest 8 | timeout-minutes: 5 9 | steps: 10 | - uses: actions/checkout@v4 11 | - uses: actions/setup-node@v4 12 | - run: npm ci 13 | - run: npm run start-local & 14 | - run: sleep 30 15 | - run: npx playwright install 16 | - run: npm run playwright 17 | - run: ls 18 | - uses: actions/upload-artifact@v4 19 | with: 20 | name: screenshot 21 | path: screenshot.png -------------------------------------------------------------------------------- /.github/workflows/preview-deployment.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | 6 | permissions: 7 | contents: read 8 | pull-requests: write 9 | 10 | jobs: 11 | preview-deployment: 12 | runs-on: ubuntu-latest 13 | timeout-minutes: 10 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: actions/setup-node@v4 17 | - name: npm install 18 | run: | 19 | npm install 20 | npm test 21 | npm run web:build 22 | 23 | - name: deploy-merged-build 24 | uses: peaceiris/actions-gh-pages@v3 25 | with: 26 | deploy_key: ${{ secrets.PREVIEW_DEPLOYMENTS_WRITE }} 27 | external_repository: open-abap/preview-deployments 28 | user_name: 'github-actions[bot]' 29 | user_email: 'github-actions[bot]@users.noreply.github.com' 30 | publish_branch: main 31 | publish_dir: ./build 32 | destination_dir: ${{ github.sha }} 33 | 34 | - name: Find Comment 35 | uses: peter-evans/find-comment@v2 36 | id: fc 37 | with: 38 | issue-number: ${{ github.event.pull_request.number }} 39 | comment-author: 'github-actions[bot]' 40 | body-includes: preview-deployment 41 | 42 | - name: Create or update comment 43 | uses: peter-evans/create-or-update-comment@v2 44 | with: 45 | comment-id: ${{ steps.fc.outputs.comment-id }} 46 | issue-number: ${{ github.event.pull_request.number }} 47 | body: | 48 | preview-deployment: 49 | https://open-abap.github.io/preview-deployments/${{ github.sha }}/ 50 | edit-mode: replace -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | output 2 | node_modules 3 | downport 4 | screenshot.png 5 | mta_archives/ 6 | build -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | ignore-scripts=false -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "larshp.vscode-abaplint" 4 | ] 5 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 open-abap 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # open-table-maintenance 2 | 3 | Works with: 4 | * [open-abap](https://github.com/open-abap/open-abap) via `npm install && npm run start-local` 5 | * [Steampunk](https://blogs.sap.com/2019/08/20/its-steampunk-now/) 6 | * [Embedded Steampunk](https://blogs.sap.com/2021/09/30/steampunk-is-going-all-in/) 7 | * On-Premise v740sp05 and up 8 | * (Code is automatically downported on build, so it should also work with 702) 9 | * SAP Business Technology Platform (BTP) Cloud Foundry environment 10 | 11 | Install with [abapGit](https://abapgit.org), or copy paste into a system 12 | 13 | Warning: only hardcode the table name, don't use with important tables, use at own risk! 14 | 15 | ## Use-case: On-prem shim 16 | 17 | ```abap 18 | METHOD if_http_extension~handle_request. 19 | 20 | DATA(result) = NEW zcl_otm_table_maintenance( 'ZOPENTEST' )->serve( VALUE #( 21 | method = server->request->get_method( ) 22 | path = server->request->get_header_field( '~path' ) 23 | body = server->request->get_data( ) ) ). 24 | 25 | server->response->set_data( result-body ). 26 | server->response->set_content_type( result-content_type ). 27 | server->response->set_status( 28 | code = result-status 29 | reason = CONV #( result-status ) ). 30 | 31 | ENDMETHOD. 32 | ``` 33 | 34 | ## Use-case: Steampunk shim 35 | 36 | ```abap 37 | METHOD if_http_service_extension~handle_request. 38 | 39 | DATA(result) = NEW zcl_otm_table_maintenance( 'ZOPENTEST' )->serve( VALUE #( 40 | method = request->get_method( ) 41 | path = request->get_header_field( '~path' ) 42 | body = request->get_binary( ) ) ). 43 | 44 | response->set_binary( result-body ). 45 | response->set_header_field( 46 | i_name = 'Content-Type' 47 | i_value = result-content_type ). 48 | response->set_status( 49 | i_code = result-status 50 | i_reason = CONV #( result-status ) ). 51 | 52 | ENDMETHOD. 53 | ``` 54 | 55 | ## Deploy to SAP Business Technology Platform (BTP) Cloud Foundry environment 56 | 57 | ### Prerequisites 58 | 59 | * Cloud Foundry CLI 60 | * Multi Target App build tool (mbt) 61 | 62 | ### Installation 63 | 64 | Login to your SAP BTP Cloud Foundry environment 65 | 66 | ```bash 67 | npm run cf:build 68 | npm run cf:deploy 69 | ``` 70 | 71 | Then you can start the app by adding /abap/ at the end of the URL. 72 | 73 | ### Removal 74 | 75 | ```bash 76 | npm run cf:undeploy 77 | ``` 78 | -------------------------------------------------------------------------------- /abap_transpile.json: -------------------------------------------------------------------------------- 1 | { 2 | "input_folder": "downport", 3 | "input_filter": [], 4 | "output_folder": "output", 5 | "libs": [ 6 | { 7 | "url": "https://github.com/open-abap/open-abap-core" 8 | }, 9 | { 10 | "url": "https://github.com/open-abap/express-icf-shim" 11 | } 12 | ], 13 | "write_unit_tests": true, 14 | "write_source_map": true, 15 | "options": { 16 | "ignoreSyntaxCheck": false, 17 | "addFilenames": true, 18 | "addCommonJS": true, 19 | "extraSetup": "../test/setup.mjs" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /abaplint-app.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": { 3 | "default": { 4 | "filename": "./abaplint.jsonc" 5 | }, 6 | "v740sp05": { 7 | "filename": "./abaplint-onprem.jsonc" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /abaplint-downport.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "global": { 3 | "files": "/downport/**/*.*" 4 | }, 5 | "dependencies": [ 6 | { 7 | "url": "https://github.com/open-abap/open-abap-core", 8 | "files": "/src/**/*.*" 9 | } 10 | ], 11 | "syntax": { 12 | "version": "v702", 13 | "errorNamespace": "." 14 | }, 15 | "rules": { 16 | "downport": true, 17 | 18 | "begin_end_names": true, 19 | "check_ddic": true, 20 | "check_include": true, 21 | "check_syntax": true, 22 | "global_class": true, 23 | "implement_methods": true, 24 | "definitions_top": true, 25 | "method_implemented_twice": true, 26 | "parser_error": true, 27 | "parser_missing_space": true, 28 | "superclass_final": true, 29 | "unknown_types": true, 30 | "xml_consistency": true 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /abaplint-onprem.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "global": { 3 | "files": "/src/**/*.*" 4 | }, 5 | "dependencies": [ 6 | { 7 | "url": "https://github.com/abaplint/deps", 8 | "files": "/src/**/*.*" 9 | } 10 | ], 11 | "syntax": { 12 | "version": "v740sp05", 13 | "errorNamespace": "^(Z|Y|LCL_|TY_|LIF_)" 14 | }, 15 | "rules": { 16 | "begin_end_names": true, 17 | "check_ddic": true, 18 | "check_include": true, 19 | "check_syntax": true, 20 | "global_class": true, 21 | "implement_methods": true, 22 | "method_implemented_twice": true, 23 | "parser_error": true, 24 | "parser_missing_space": true, 25 | "superclass_final": true, 26 | "unknown_types": true, 27 | "xml_consistency": true 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /abaplint.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "global": { 3 | "files": "/src/**/*.*" 4 | }, 5 | "dependencies": [ 6 | { 7 | "url": "https://github.com/abapedia/steampunk-2302-api", 8 | "files": "/src/**/*.*" 9 | } 10 | ], 11 | "syntax": { 12 | "version": "Cloud", 13 | "errorNamespace": "." 14 | }, 15 | "rules": { 16 | "cds_legacy_view": true, 17 | "cds_parser_error": true, 18 | "classic_exceptions_overlap": true, 19 | "local_testclass_consistency": true, 20 | "no_aliases": true, 21 | "no_external_form_calls": true, 22 | "no_inline_in_optional_branches": false, 23 | "nrob_consistency": true, 24 | "omit_preceding_zeros": true, 25 | "pragma_style": true, 26 | "prefer_corresponding": true, 27 | "slow_parameter_passing": true, 28 | "superfluous_value": true, 29 | "unnecessary_pragma": true, 30 | "7bit_ascii": true, 31 | "abapdoc": false, 32 | "align_parameters": true, 33 | "allowed_object_naming": true, 34 | "allowed_object_types": { 35 | "exclude": [], 36 | "severity": "Error", 37 | "allowed": [] 38 | }, 39 | "ambiguous_statement": true, 40 | "avoid_use": { 41 | "exclude": [], 42 | "severity": "Error", 43 | "define": true, 44 | "testSeams": true, 45 | "statics": true, 46 | "defaultKey": true, 47 | "break": true, 48 | "describeLines": true 49 | }, 50 | "begin_end_names": true, 51 | "begin_single_include": true, 52 | "call_transaction_authority_check": true, 53 | "chain_mainly_declarations": { 54 | "exclude": [], 55 | "severity": "Error", 56 | "definitions": true, 57 | "write": true, 58 | "move": true, 59 | "refresh": true, 60 | "unassign": true, 61 | "clear": true, 62 | "hide": true, 63 | "free": true, 64 | "include": true, 65 | "check": true, 66 | "sort": true 67 | }, 68 | "check_abstract": true, 69 | "check_comments": { 70 | "exclude": [], 71 | "severity": "Error", 72 | "allowEndOfLine": false 73 | }, 74 | "check_ddic": true, 75 | "check_include": true, 76 | "check_subrc": { 77 | "exclude": [], 78 | "severity": "Error", 79 | "openDataset": true, 80 | "authorityCheck": true, 81 | "selectSingle": true, 82 | "selectTable": true, 83 | "updateDatabase": true, 84 | "insertDatabase": true, 85 | "modifyDatabase": true, 86 | "readTable": true, 87 | "assign": true, 88 | "find": true 89 | }, 90 | "check_syntax": true, 91 | "check_text_elements": true, 92 | "check_transformation_exists": true, 93 | "class_attribute_names": { 94 | "patternKind": "required", 95 | "ignoreNames": [], 96 | "ignorePatterns": [], 97 | "ignoreExceptions": true, 98 | "ignoreLocal": true, 99 | "ignoreInterfaces": false, 100 | "statics": "^G._.+$", 101 | "instance": "^M._.+$", 102 | "constants": "" 103 | }, 104 | "cloud_types": true, 105 | "colon_missing_space": true, 106 | "commented_code": { 107 | "allowIncludeInFugr": true 108 | }, 109 | "constant_classes": true, 110 | "constructor_visibility_public": true, 111 | "contains_tab": { 112 | "exclude": [], 113 | "severity": "Error", 114 | "spaces": 1 115 | }, 116 | "cyclic_oo": true, 117 | "cyclomatic_complexity": true, 118 | "dangerous_statement": { 119 | "execSQL": true, 120 | "kernelCall": true, 121 | "systemCall": true, 122 | "exportDynpro": true, 123 | "insertReport": true, 124 | "generateDynpro": true, 125 | "generateReport": true, 126 | "generateSubroutine": true, 127 | "deleteReport": true, 128 | "deleteTextpool": true, 129 | "deleteDynpro": true, 130 | "dynamicSQL": false 131 | }, 132 | "db_operation_in_loop": true, 133 | "definitions_top": true, 134 | "description_empty": true, 135 | "double_space": { 136 | "exclude": [], 137 | "severity": "Error", 138 | "keywords": true, 139 | "startParen": true, 140 | "endParen": true, 141 | "afterColon": true 142 | }, 143 | "downport": true, 144 | "empty_line_in_statement": { 145 | "exclude": [], 146 | "severity": "Error", 147 | "allowChained": false 148 | }, 149 | "empty_statement": true, 150 | "empty_structure": { 151 | "exclude": [], 152 | "severity": "Error", 153 | "loop": true, 154 | "if": true, 155 | "while": true, 156 | "case": true, 157 | "when": true, 158 | "select": true, 159 | "do": true, 160 | "at": true, 161 | "try": true 162 | }, 163 | "exit_or_check": { 164 | "exclude": [], 165 | "severity": "Error", 166 | "allowExit": false, 167 | "allowCheck": false 168 | }, 169 | "exporting": true, 170 | "forbidden_identifier": { 171 | "exclude": [], 172 | "severity": "Error", 173 | "check": [] 174 | }, 175 | "forbidden_pseudo_and_pragma": { 176 | "exclude": [], 177 | "severity": "Error", 178 | "pseudo": [], 179 | "pragmas": [], 180 | "ignoreGlobalClassDefinition": false, 181 | "ignoreGlobalInterface": false 182 | }, 183 | "forbidden_void_type": { 184 | "exclude": [], 185 | "severity": "Error", 186 | "check": [] 187 | }, 188 | "form_tables_obsolete": true, 189 | "fully_type_constants": { 190 | "exclude": [], 191 | "severity": "Error", 192 | "checkData": true 193 | }, 194 | "function_module_recommendations": { 195 | "exclude": [], 196 | "severity": "Error", 197 | "recommendations": [ 198 | { 199 | "name": "SUBST_GET_FILE_LIST", 200 | "replace": "see note 1686357" 201 | }, 202 | { 203 | "name": "ROUND", 204 | "replace": "use built in function: round()" 205 | }, 206 | { 207 | "name": "SO_NEW_DOCUMENT_ATT_SEND_API1", 208 | "replace": "use CL_BCS" 209 | }, 210 | { 211 | "name": "ECATT_CONV_XSTRING_TO_STRING", 212 | "replace": "use CL_BINARY_CONVERT" 213 | }, 214 | { 215 | "name": "SCMS_STRING_TO_XSTRING", 216 | "replace": "use CL_BINARY_CONVERT" 217 | }, 218 | { 219 | "name": "JOB_CREATE", 220 | "replace": "use CL_BP_ABAP_JOB" 221 | }, 222 | { 223 | "name": "JOB_SUBMIT", 224 | "replace": "use CL_BP_ABAP_JOB" 225 | }, 226 | { 227 | "name": "GUI_DOWNLOAD", 228 | "replace": "use CL_GUI_FRONTEND_SERVICES" 229 | }, 230 | { 231 | "name": "GUI_UPLOAD", 232 | "replace": "use CL_GUI_FRONTEND_SERVICES" 233 | }, 234 | { 235 | "name": "WS_FILENAME_GET", 236 | "replace": "use CL_GUI_FRONTEND_SERVICES" 237 | }, 238 | { 239 | "name": "F4_FILENAME", 240 | "replace": "use CL_GUI_FRONTEND_SERVICES" 241 | }, 242 | { 243 | "name": "SAPGUI_PROGRESS_INDICATOR", 244 | "replace": "use CL_PROGRESS_INDICATOR" 245 | }, 246 | { 247 | "name": "GUID_CREATE", 248 | "replace": "use CL_SYSTEM_UUID" 249 | }, 250 | { 251 | "name": "SSFC_BASE64_DECODE", 252 | "replace": "use class CL_HTTP_UTILITY methods" 253 | }, 254 | { 255 | "name": "SSFC_BASE64_ENCODE", 256 | "replace": "use class CL_HTTP_UTILITY methods" 257 | }, 258 | { 259 | "name": "SCMS_BASE64_DECODE_STR", 260 | "replace": "use class CL_HTTP_UTILITY methods" 261 | }, 262 | { 263 | "name": "POPUP_TO_DECIDE", 264 | "replace": "use POPUP_TO_CONFIRM" 265 | }, 266 | { 267 | "name": "REUSE_ALV_GRID_DISPLAY", 268 | "replace": "use CL_SALV_TABLE=>FACTORY or CL_GUI_ALV_GRID" 269 | }, 270 | { 271 | "name": "CALCULATE_HASH_FOR_RAW", 272 | "replace": "use CL_ABAP_HMAC" 273 | }, 274 | { 275 | "name": "FUNCTION_EXISTS", 276 | "replace": "surround with try-catch CX_SY_DYN_CALL_ILLEGAL_METHOD instead" 277 | } 278 | ] 279 | }, 280 | "functional_writing": { 281 | "exclude": [], 282 | "severity": "Error", 283 | "ignoreExceptions": true 284 | }, 285 | "global_class": true, 286 | "identical_conditions": true, 287 | "identical_contents": true, 288 | "identical_descriptions": true, 289 | "identical_form_names": true, 290 | "if_in_if": true, 291 | "implement_methods": true, 292 | "in_statement_indentation": { 293 | "exclude": [], 294 | "severity": "Error", 295 | "blockStatements": 2, 296 | "ignoreExceptions": true 297 | }, 298 | "indentation": { 299 | "exclude": [], 300 | "severity": "Error", 301 | "ignoreExceptions": true, 302 | "alignTryCatch": false, 303 | "selectionScreenBlockIndentation": false, 304 | "globalClassSkipFirst": false, 305 | "ignoreGlobalClassDefinition": false, 306 | "ignoreGlobalInterface": false 307 | }, 308 | "inline_data_old_versions": true, 309 | "intf_referencing_clas": { 310 | "exclude": [], 311 | "severity": "Error", 312 | "allow": [] 313 | }, 314 | "keep_single_parameter_on_one_line": { 315 | "exclude": [], 316 | "severity": "Error", 317 | "length": 120 318 | }, 319 | "keyword_case": { 320 | "exclude": [], 321 | "severity": "Error", 322 | "style": "upper", 323 | "ignoreExceptions": true, 324 | "ignoreLowerClassImplmentationStatement": true, 325 | "ignoreGlobalClassDefinition": false, 326 | "ignoreGlobalInterface": false, 327 | "ignoreFunctionModuleName": false, 328 | "ignoreGlobalClassBoundaries": false, 329 | "ignoreKeywords": [] 330 | }, 331 | "line_break_multiple_parameters": { 332 | "exclude": [], 333 | "severity": "Error", 334 | "count": 1 335 | }, 336 | "line_break_style": false, 337 | "line_length": { 338 | "exclude": [], 339 | "severity": "Error", 340 | "length": 120 341 | }, 342 | "line_only_punc": { 343 | "exclude": [], 344 | "severity": "Error", 345 | "ignoreExceptions": true 346 | }, 347 | "local_class_naming": { 348 | "exclude": [], 349 | "severity": "Error", 350 | "patternKind": "required", 351 | "ignoreNames": [], 352 | "ignorePatterns": [], 353 | "local": "^LCL_.+$", 354 | "exception": "^LCX_.+$", 355 | "test": "^LTCL_.+$" 356 | }, 357 | "local_variable_names": false, 358 | "main_file_contents": true, 359 | "many_parentheses": true, 360 | "max_one_method_parameter_per_line": true, 361 | "max_one_statement": true, 362 | "message_exists": true, 363 | "method_implemented_twice": true, 364 | "method_length": { 365 | "exclude": [], 366 | "severity": "Error", 367 | "statements": 100, 368 | "errorWhenEmpty": true, 369 | "ignoreTestClasses": false, 370 | "checkForms": true 371 | }, 372 | "method_overwrites_builtin": true, 373 | "method_parameter_names": false, 374 | "mix_returning": true, 375 | "modify_only_own_db_tables": { 376 | "exclude": [], 377 | "severity": "Error", 378 | "reportDynamic": false, 379 | "ownTables": "^[yz]" 380 | }, 381 | "msag_consistency": true, 382 | "names_no_dash": true, 383 | "nesting": { 384 | "exclude": [], 385 | "severity": "Error", 386 | "depth": 5 387 | }, 388 | "newline_between_methods": { 389 | "exclude": [], 390 | "severity": "Error", 391 | "count": 3, 392 | "logic": "less" 393 | }, 394 | "no_chained_assignment": true, 395 | "no_public_attributes": true, 396 | "no_yoda_conditions": true, 397 | "object_naming": { 398 | "exclude": [], 399 | "severity": "Error", 400 | "patternKind": "required", 401 | "ignoreNames": [], 402 | "ignorePatterns": [], 403 | "clas": "^ZC(L|X)", 404 | "intf": "^ZIF", 405 | "prog": "^Z", 406 | "fugr": "^Z", 407 | "tabl": "^Z", 408 | "ttyp": "^Z", 409 | "dtel": "^Z", 410 | "doma": "^Z", 411 | "msag": "^Z", 412 | "tran": "^Z", 413 | "enqu": "^EZ", 414 | "auth": "^Z", 415 | "pinf": "^Z", 416 | "idoc": "^Z", 417 | "xslt": "^Z", 418 | "ssfo": "^Z", 419 | "ssst": "^Z", 420 | "shlp": "^Z" 421 | }, 422 | "obsolete_statement": { 423 | "refresh": true, 424 | "compute": true, 425 | "clientSpecified": true, 426 | "formDefinition": true, 427 | "formImplementation": true, 428 | "add": true, 429 | "subtract": true, 430 | "multiply": true, 431 | "divide": true, 432 | "occurences": true, 433 | "move": true, 434 | "requested": true, 435 | "occurs": true, 436 | "setExtended": true, 437 | "withHeaderLine": true, 438 | "fieldSymbolStructure": true, 439 | "typePools": true, 440 | "load": true, 441 | "parameter": true, 442 | "ranges": true, 443 | "communication": true, 444 | "pack": true, 445 | "selectWithoutInto": true, 446 | "freeMemory": true, 447 | "exitFromSQL": true, 448 | "sortByFS": true, 449 | "callTransformation": true, 450 | "regex": true 451 | }, 452 | "omit_parameter_name": true, 453 | "omit_receiving": true, 454 | "parser_702_chaining": true, 455 | "parser_error": true, 456 | "parser_missing_space": true, 457 | "prefer_inline": false, 458 | "prefer_is_not": true, 459 | "prefer_raise_exception_new": true, 460 | "prefer_returning_to_exporting": true, 461 | "prefer_xsdbool": false, // no, xsdbool introduced in 740sp08 462 | "preferred_compare_operator": { 463 | "exclude": [], 464 | "severity": "Error", 465 | "badOperators": [ 466 | "EQ", 467 | "><", 468 | "NE", 469 | "GE", 470 | "GT", 471 | "LT", 472 | "LE" 473 | ] 474 | }, 475 | "prefix_is_current_class": { 476 | "exclude": [], 477 | "severity": "Error", 478 | "omitMeInstanceCalls": true 479 | }, 480 | "reduce_string_templates": true, 481 | "release_idoc": true, 482 | "remove_descriptions": { 483 | "exclude": [], 484 | "severity": "Error", 485 | "ignoreExceptions": false, 486 | "ignoreWorkflow": true 487 | }, 488 | "rfc_error_handling": true, 489 | "select_add_order_by": true, 490 | "select_performance": false, 491 | "selection_screen_naming": true, 492 | "sequential_blank": true, 493 | "short_case": { 494 | "exclude": [], 495 | "severity": "Error", 496 | "length": 1, 497 | "allow": [] 498 | }, 499 | "sicf_consistency": true, 500 | "space_before_colon": true, 501 | "space_before_dot": { 502 | "exclude": [], 503 | "severity": "Error", 504 | "ignoreGlobalDefinition": true, 505 | "ignoreExceptions": true 506 | }, 507 | "sql_escape_host_variables": true, 508 | "start_at_tab": true, 509 | "static_call_via_instance": true, 510 | "superclass_final": true, 511 | "sy_modification": true, 512 | "tabl_enhancement_category": true, 513 | "try_without_catch": true, 514 | "type_form_parameters": true, 515 | "types_naming": { 516 | "exclude": [], 517 | "severity": "Error", 518 | "pattern": "^TY_.+$" 519 | }, 520 | "uncaught_exception": true, 521 | "unknown_types": true, 522 | "unnecessary_chaining": false, 523 | "unreachable_code": true, 524 | "unsecure_fae": true, 525 | "unused_ddic": true, 526 | "unused_methods": true, 527 | "unused_types": { 528 | "exclude": [], 529 | "severity": "Error", 530 | "skipNames": [] 531 | }, 532 | "unused_variables": { 533 | "exclude": [], 534 | "severity": "Error", 535 | "skipNames": [] 536 | }, 537 | "use_bool_expression": true, 538 | "use_class_based_exceptions": true, 539 | "use_line_exists": false, // downport missing some scenarios 540 | "use_new": true, 541 | "when_others_last": true, 542 | "whitespace_end": true, 543 | "xml_consistency": true 544 | } 545 | } 546 | -------------------------------------------------------------------------------- /cf/cf-build.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | cf-build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | - uses: actions/setup-node@v2 11 | with: 12 | node-version: '14' 13 | - run: npm ci 14 | - name: Install MTA Build Tool 15 | run: npm install --no-save mbt 16 | - name: Build MTA 17 | run: npm run cf:build 18 | - name: Upload Artifact 19 | uses: actions/upload-artifact@master 20 | with: 21 | name: mta 22 | path: ./cf/mta_archives/open-table-maintenance.mtar 23 | cf-test-run: 24 | needs: cf-build 25 | runs-on: ubuntu-latest 26 | steps: 27 | - uses: actions/setup-node@v2 28 | with: 29 | node-version: '14' 30 | - name: Download Artifact 31 | uses: actions/download-artifact@master 32 | with: 33 | name: mta 34 | path: ./ 35 | - name: Extract MTAR 36 | run: unzip open-table-maintenance.mtar 37 | - name: Start application 38 | run: | 39 | cd open-table-maintenance 40 | unzip -q data.zip 41 | npm start & 42 | sleep 3 43 | - run: curl http://localhost:3000/abap -------------------------------------------------------------------------------- /cf/mta.yaml: -------------------------------------------------------------------------------- 1 | ID: open-table-maintenance 2 | _schema-version: 3.2.0 3 | description: ABAP table maintenance 4 | version: 0.0.1 5 | parameters: 6 | enable-parallel-deployments: true 7 | build-parameters: 8 | before-all: 9 | - builder: custom 10 | commands: 11 | - npm run lint 12 | - npm run downport 13 | - npm run transpile 14 | - cp ../package.json ../output/ 15 | - cp ../test/index.mjs ../output/start.mjs 16 | - mkdir ../output/test/ 17 | - cp ../test/setup.mjs ../output/test/ 18 | - ./sed.sh 's/..\/output\//.\//g' -i ../output/start.mjs 19 | modules: 20 | - name: open-table-maintenance 21 | type: nodejs 22 | path: ../output 23 | parameters: 24 | disk-quota: 512M 25 | memory: 128M 26 | -------------------------------------------------------------------------------- /cf/sed.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | if [[ "$OSTYPE" == "darwin"* ]]; then 3 | exec "gsed" "$@" 4 | else 5 | exec "sed" "$@" 6 | fi -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "open-table-maintenance", 3 | "private": true, 4 | "version": "1.0.0", 5 | "description": "open-table-maintenance", 6 | "scripts": { 7 | "lint": "abaplint", 8 | "transpile": "rm -rf output && abap_transpile", 9 | "unit": "npm run transpile && echo RUNNING && node output/index.mjs", 10 | "test": "npm run lint && npm run downport && npm run unit", 11 | "downport": "rm -rf downport && cp -r src downport && cp test/*.abap downport && cp test/*.xml downport && abaplint --fix abaplint-downport.jsonc", 12 | "playwright": "npx playwright test", 13 | "start-local": "npm run lint && npm run downport && npm run transpile && node test/index.mjs", 14 | "cf:build": "mbt build -s cf/ --mtar open-table-maintenance.mtar", 15 | "cf:deploy": "cf deploy cf/mta_archives/open-table-maintenance.mtar", 16 | "web": "webpack serve --mode development --env development", 17 | "web:build": "webpack --mode development --env development", 18 | "cf:undeploy": "cf undeploy open-table-maintenance" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/open-abap/open-table-maintenance.git" 23 | }, 24 | "author": "", 25 | "license": "ISC", 26 | "bugs": { 27 | "url": "https://github.com/open-abap/open-table-maintenance/issues" 28 | }, 29 | "homepage": "https://github.com/open-abap/open-table-maintenance#readme", 30 | "dependencies": { 31 | "@abaplint/cli": "^2.113.110", 32 | "@abaplint/database-sqlite": "^2.10.24", 33 | "@abaplint/runtime": "^2.10.49", 34 | "@abaplint/transpiler-cli": "^2.10.49", 35 | "@playwright/test": "^1.52.0", 36 | "buffer": "^6.0.3", 37 | "web-encoding": "^1.1.5", 38 | "copy-webpack-plugin": "^13.0.0", 39 | "crypto-browserify": "^3.12.1", 40 | "express": "^4.21.2", 41 | "html-webpack-plugin": "^5.6.3", 42 | "path-browserify": "^1.0.1", 43 | "webpack-cli": "^6.0.1", 44 | "webpack-dev-server": "^5.2.1" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /playwright.config.js: -------------------------------------------------------------------------------- 1 | // playwright.config.js 2 | // @ts-check 3 | 4 | /** @type {import('@playwright/test').PlaywrightTestConfig} */ 5 | const config = { 6 | testDir: 'test', 7 | timeout: 30000, 8 | forbidOnly: !!process.env.CI, 9 | retries: 0, 10 | // Limit the number of workers on CI, use default locally 11 | workers: process.env.CI ? 2 : undefined, 12 | use: { 13 | // Configure browser and context here 14 | }, 15 | }; 16 | 17 | module.exports = config; 18 | -------------------------------------------------------------------------------- /src/package.devc.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | open-table-maintenance 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/zcl_otm_table_maintenance.clas.abap: -------------------------------------------------------------------------------- 1 | CLASS zcl_otm_table_maintenance DEFINITION 2 | PUBLIC 3 | CREATE PUBLIC . 4 | 5 | PUBLIC SECTION. 6 | * MIT License, Copyright (c) 2021 open-abap 7 | * https://github.com/open-abap/open-table-maintenance 8 | 9 | TYPES: 10 | BEGIN OF ty_request, 11 | method TYPE string, 12 | path TYPE string, 13 | body TYPE xstring, 14 | END OF ty_request. 15 | TYPES: 16 | BEGIN OF ty_http, 17 | status TYPE i, 18 | content_type TYPE string, 19 | body TYPE xstring, 20 | END OF ty_http . 21 | 22 | METHODS constructor 23 | IMPORTING 24 | !iv_table TYPE tabname . 25 | METHODS serve 26 | IMPORTING 27 | !is_request TYPE ty_request 28 | RETURNING 29 | VALUE(rs_http) TYPE ty_http . 30 | PROTECTED SECTION. 31 | PRIVATE SECTION. 32 | 33 | TYPES: 34 | ty_names TYPE STANDARD TABLE OF abap_compname WITH EMPTY KEY . 35 | TYPES: 36 | * there is no common released type for both steampunk and on-prem, workaround: 37 | BEGIN OF ty_fixvalue, 38 | low TYPE c LENGTH 10, 39 | high TYPE c LENGTH 10, 40 | option TYPE c LENGTH 2, 41 | ddlanguage TYPE c LENGTH 1, 42 | ddtext TYPE c LENGTH 60, 43 | END OF ty_fixvalue . 44 | TYPES: 45 | ty_fixvalues TYPE STANDARD TABLE OF ty_fixvalue WITH EMPTY KEY . 46 | TYPES: 47 | BEGIN OF ty_fielddata, 48 | name TYPE abap_compname, 49 | key TYPE abap_bool, 50 | type_kind TYPE abap_typekind, 51 | length TYPE i, 52 | fixvalues TYPE ty_fixvalues, 53 | END OF ty_fielddata . 54 | TYPES: 55 | ty_metadata TYPE STANDARD TABLE OF ty_fielddata WITH EMPTY KEY . 56 | 57 | DATA mv_table TYPE tabname . 58 | 59 | METHODS from_xstring 60 | IMPORTING 61 | !xstring TYPE xstring 62 | RETURNING 63 | VALUE(string) TYPE string . 64 | METHODS get_html 65 | RETURNING 66 | VALUE(rv_html) TYPE string . 67 | METHODS read_table 68 | RETURNING 69 | VALUE(rv_json) TYPE string . 70 | METHODS save_table 71 | IMPORTING 72 | !iv_json TYPE string 73 | RAISING 74 | cx_sy_conversion_data_loss . 75 | METHODS to_json 76 | IMPORTING 77 | !ref TYPE REF TO data 78 | RETURNING 79 | VALUE(rv_json) TYPE string . 80 | METHODS to_xstring 81 | IMPORTING 82 | !string TYPE string 83 | RETURNING 84 | VALUE(xstring) TYPE xstring . 85 | METHODS list_key_fields 86 | RETURNING 87 | VALUE(names) TYPE ty_names . 88 | METHODS build_metadata 89 | RETURNING 90 | VALUE(rt_metadata) TYPE ty_metadata . 91 | ENDCLASS. 92 | 93 | 94 | 95 | CLASS zcl_otm_table_maintenance IMPLEMENTATION. 96 | 97 | 98 | METHOD build_metadata. 99 | DATA lv_key TYPE abap_bool. 100 | DATA lo_element TYPE REF TO cl_abap_elemdescr. 101 | DATA lt_values TYPE ty_fixvalues. 102 | 103 | DATA(lt_key_fields) = list_key_fields( ). 104 | DATA(lt_components) = CAST cl_abap_structdescr( cl_abap_typedescr=>describe_by_name( 105 | mv_table ) )->get_components( ). 106 | 107 | LOOP AT lt_components INTO DATA(ls_component). 108 | 109 | lo_element ?= ls_component-type. 110 | lt_values = lo_element->get_ddic_fixed_values( ). 111 | 112 | READ TABLE lt_key_fields WITH KEY table_line = ls_component-name TRANSPORTING NO FIELDS. 113 | lv_key = boolc( sy-subrc = 0 ). 114 | APPEND VALUE #( 115 | name = ls_component-name 116 | key = lv_key 117 | type_kind = ls_component-type->type_kind 118 | length = ls_component-type->length 119 | fixvalues = lt_values 120 | ) TO rt_metadata. 121 | ENDLOOP. 122 | 123 | ENDMETHOD. 124 | 125 | 126 | METHOD constructor. 127 | ASSERT iv_table IS NOT INITIAL. 128 | mv_table = iv_table. 129 | ENDMETHOD. 130 | 131 | 132 | METHOD from_xstring. 133 | 134 | DATA conv TYPE REF TO object. 135 | 136 | TRY. 137 | CALL METHOD ('CL_ABAP_CONV_CODEPAGE')=>create_in 138 | RECEIVING 139 | instance = conv. 140 | 141 | CALL METHOD conv->('IF_ABAP_CONV_IN~CONVERT') 142 | EXPORTING 143 | source = xstring 144 | RECEIVING 145 | result = string. 146 | CATCH cx_sy_dyn_call_illegal_class. 147 | DATA(conv_in_class) = 'CL_ABAP_CONV_IN_CE'. 148 | CALL METHOD (conv_in_class)=>create 149 | EXPORTING 150 | encoding = 'UTF-8' 151 | RECEIVING 152 | conv = conv. 153 | 154 | CALL METHOD conv->('CONVERT') 155 | EXPORTING 156 | input = xstring 157 | IMPORTING 158 | data = string. 159 | ENDTRY. 160 | 161 | ENDMETHOD. 162 | 163 | 164 | METHOD get_html. 165 | rv_html = |\n| && 166 | |\n| && 167 | |\n| && 168 | |open-table-maintenance\n| && 169 | |\n| && 170 | |\n| && 171 | |\n| && 172 | |\n| && 173 | |\n| && 229 | |\n| && 230 | |\n| && 231 | |

open-table-maintenance

\n| && 232 | |
\n| && 233 | |
loading

\n| && 234 | |\n| && 235 | ||. 236 | ENDMETHOD. 237 | 238 | 239 | METHOD list_key_fields. 240 | DATA obj TYPE REF TO object. 241 | DATA lv_tabname TYPE c LENGTH 16. 242 | DATA lr_ddfields TYPE REF TO data. 243 | FIELD-SYMBOLS TYPE any. 244 | FIELD-SYMBOLS TYPE simple. 245 | FIELD-SYMBOLS TYPE ANY TABLE. 246 | 247 | * convert to correct type, 248 | lv_tabname = mv_table. 249 | 250 | TRY. 251 | CALL METHOD ('XCO_CP_ABAP_DICTIONARY')=>database_table 252 | EXPORTING 253 | iv_name = lv_tabname 254 | RECEIVING 255 | ro_database_table = obj. 256 | ASSIGN obj->('IF_XCO_DATABASE_TABLE~FIELDS->IF_XCO_DBT_FIELDS_FACTORY~KEY') TO . 257 | ASSERT sy-subrc = 0. 258 | obj = . 259 | CALL METHOD obj->('IF_XCO_DBT_FIELDS~GET_NAMES') 260 | RECEIVING 261 | rt_names = names. 262 | CATCH cx_sy_dyn_call_illegal_class. 263 | DATA(workaround) = 'DDFIELDS'. 264 | CREATE DATA lr_ddfields TYPE (workaround). 265 | ASSIGN lr_ddfields->* TO . 266 | ASSERT sy-subrc = 0. 267 | = CAST cl_abap_structdescr( cl_abap_typedescr=>describe_by_name( 268 | lv_tabname ) )->get_ddic_field_list( ). 269 | LOOP AT ASSIGNING . 270 | ASSIGN COMPONENT 'KEYFLAG' OF STRUCTURE TO . 271 | IF sy-subrc <> 0 OR <> abap_true. 272 | CONTINUE. 273 | ENDIF. 274 | ASSIGN COMPONENT 'FIELDNAME' OF STRUCTURE TO . 275 | ASSERT sy-subrc = 0. 276 | APPEND TO names. 277 | ENDLOOP. 278 | ENDTRY. 279 | 280 | ENDMETHOD. 281 | 282 | 283 | METHOD read_table. 284 | 285 | FIELD-SYMBOLS TYPE STANDARD TABLE. 286 | DATA dref TYPE REF TO data. 287 | CREATE DATA dref TYPE STANDARD TABLE OF (mv_table) WITH DEFAULT KEY. 288 | ASSIGN dref->* TO . 289 | ASSERT sy-subrc = 0. 290 | 291 | " dont check SUBRC, the table might be empty 292 | SELECT * FROM (mv_table) ORDER BY PRIMARY KEY INTO TABLE @ ##SUBRC_OK. 293 | 294 | rv_json = to_json( dref ). 295 | 296 | ENDMETHOD. 297 | 298 | 299 | METHOD save_table. 300 | 301 | FIELD-SYMBOLS TYPE STANDARD TABLE. 302 | DATA dref TYPE REF TO data. 303 | CREATE DATA dref TYPE STANDARD TABLE OF (mv_table) WITH DEFAULT KEY. 304 | ASSIGN dref->* TO . 305 | ASSERT sy-subrc = 0. 306 | 307 | CALL TRANSFORMATION id SOURCE XML iv_json RESULT data = . 308 | 309 | MODIFY (mv_table) FROM TABLE @ ##SUBRC_OK. 310 | 311 | ENDMETHOD. 312 | 313 | 314 | METHOD serve. 315 | 316 | TRY. 317 | rs_http-status = 200. 318 | IF is_request-path CP '*/rest'. 319 | IF is_request-method = 'GET'. 320 | DATA(lv_body) = read_table( ). 321 | rs_http-content_type = 'application/json'. 322 | ELSEIF is_request-method = 'POST'. 323 | save_table( from_xstring( is_request-body ) ). 324 | ELSE. 325 | ASSERT 1 = 2. 326 | ENDIF. 327 | ELSE. 328 | lv_body = get_html( ). 329 | rs_http-content_type = 'text/html'. 330 | ENDIF. 331 | rs_http-body = to_xstring( lv_body ). 332 | CATCH cx_root. 333 | rs_http-status = 500. 334 | rs_http-body = to_xstring( |Exception occurred| ). 335 | ENDTRY. 336 | 337 | ENDMETHOD. 338 | 339 | 340 | METHOD to_json. 341 | 342 | FIELD-SYMBOLS TYPE STANDARD TABLE. 343 | ASSIGN ref->* TO . 344 | ASSERT sy-subrc = 0. 345 | 346 | DATA(meta) = build_metadata( ). 347 | DATA(writer) = cl_sxml_string_writer=>create( if_sxml=>co_xt_json ). 348 | CALL TRANSFORMATION id 349 | SOURCE 350 | data = 351 | meta = meta 352 | tablename = mv_table 353 | sy = sy 354 | RESULT XML writer. 355 | rv_json = from_xstring( writer->get_output( ) ). 356 | 357 | ENDMETHOD. 358 | 359 | 360 | METHOD to_xstring. 361 | 362 | DATA conv TYPE REF TO object. 363 | 364 | TRY. 365 | CALL METHOD ('CL_ABAP_CONV_CODEPAGE')=>create_out 366 | RECEIVING 367 | instance = conv. 368 | 369 | CALL METHOD conv->('IF_ABAP_CONV_OUT~CONVERT') 370 | EXPORTING 371 | source = string 372 | RECEIVING 373 | result = xstring. 374 | CATCH cx_sy_dyn_call_illegal_class. 375 | DATA(conv_out_class) = 'CL_ABAP_CONV_OUT_CE'. 376 | CALL METHOD (conv_out_class)=>create 377 | EXPORTING 378 | encoding = 'UTF-8' 379 | RECEIVING 380 | conv = conv. 381 | 382 | CALL METHOD conv->('CONVERT') 383 | EXPORTING 384 | data = string 385 | IMPORTING 386 | buffer = xstring. 387 | ENDTRY. 388 | 389 | ENDMETHOD. 390 | ENDCLASS. 391 | -------------------------------------------------------------------------------- /src/zcl_otm_table_maintenance.clas.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | ZCL_OTM_TABLE_MAINTENANCE 7 | E 8 | open-table-maintenance 9 | 1 10 | X 11 | X 12 | X 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | web 5 | 6 | 7 | loading 8 | 9 | 10 | -------------------------------------------------------------------------------- /test/index.mjs: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import {initializeABAP} from "../output/init.mjs"; 3 | import {cl_express_icf_shim} from "../output/cl_express_icf_shim.clas.mjs"; 4 | await initializeABAP(); 5 | 6 | const PORT = process.env.PORT || 3000; 7 | 8 | const app = express(); 9 | app.disable('x-powered-by'); 10 | app.set('etag', false); 11 | app.use(express.raw({type: "*/*"})); 12 | 13 | // ------------------ 14 | 15 | app.get('/', function (req, res) { 16 | res.send('path: /'); 17 | }); 18 | 19 | // ------------------ 20 | 21 | app.all(["/abap", "/abap*"], async function (req, res) { 22 | await cl_express_icf_shim.run({req, res, class: "ZCL_HTTP_HANDLER"}); 23 | }); 24 | 25 | app.listen(PORT); 26 | console.log("Listening on port http://localhost:" + PORT + "/abap"); 27 | -------------------------------------------------------------------------------- /test/playwright.spec.mjs: -------------------------------------------------------------------------------- 1 | import { test, expect } from '@playwright/test'; 2 | 3 | test('test', async ({ page }) => { 4 | await page.goto('http://localhost:3000/abap'); 5 | 6 | await page.dblclick('text=1_ >> :nth-match(td, 3)'); 7 | await page.fill('input', 'foo'); 8 | await page.press('input', 'Enter', {delay: 100}); 9 | 10 | await page.dblclick('tr:nth-child(2) td:nth-child(3)'); 11 | await page.fill('input', 'bar'); 12 | 13 | await page.dblclick('tr:nth-child(2) td:nth-child(2)'); 14 | await page.fill('input', 'sdf'); 15 | 16 | await page.click('text=Save', {delay: 100}); 17 | page.once('dialog', dialog => { 18 | console.log(`Dialog message: ${dialog.message()}`); 19 | dialog.dismiss().catch(() => {}); 20 | }); 21 | 22 | await page.goto('http://localhost:3000/abap', {waitUntil: 'domcontentloaded'}); 23 | await page.waitForTimeout(100); 24 | expect(page.locator('tr:nth-child(2) td:nth-child(3)')).toHaveText('bar'); 25 | 26 | await page.screenshot({ path: 'screenshot.png' }); 27 | }); 28 | -------------------------------------------------------------------------------- /test/setup.mjs: -------------------------------------------------------------------------------- 1 | import {SQLiteDatabaseClient} from "@abaplint/database-sqlite"; 2 | 3 | export async function setup(abap, schemas, insert) { 4 | abap.context.databaseConnections["DEFAULT"] = new SQLiteDatabaseClient(); 5 | await abap.context.databaseConnections["DEFAULT"].connect(); 6 | await abap.context.databaseConnections["DEFAULT"].execute(schemas.sqlite); 7 | await abap.context.databaseConnections["DEFAULT"].execute(insert); 8 | } -------------------------------------------------------------------------------- /test/web.mjs: -------------------------------------------------------------------------------- 1 | import {initializeABAP} from "../output/_init.mjs"; 2 | await initializeABAP(); 3 | 4 | async function redirectFetch(url, options) { 5 | let data = ""; 6 | 7 | let res = { 8 | append: (d) => { 9 | console.dir("append2: " + d); }, 10 | send: (d) => { 11 | console.dir("send2"); 12 | data = Buffer.from(d).toString(); 13 | }, 14 | status: (status) => { 15 | console.dir("status2: " + status); 16 | return res; }, 17 | } 18 | 19 | const method = options?.method || "GET"; 20 | const body = options?.body || ""; 21 | 22 | const req = { 23 | body: Buffer.from(body).toString("hex"), 24 | method: method, 25 | path: url, 26 | url: url, 27 | }; 28 | console.dir(req); 29 | await abap.Classes["CL_EXPRESS_ICF_SHIM"].run({req: req, res, class: "ZCL_HTTP_HANDLER"}) 30 | console.log("redirectFetch RESPONSE,"); 31 | console.dir(data); 32 | return { json: async () => JSON.parse(data)}; 33 | } 34 | 35 | async function run() { 36 | let res = { 37 | append: (data) => { 38 | console.dir("append: " + data); }, 39 | send: (data) => { 40 | console.dir("send"); 41 | let r = Buffer.from(data).toString(); 42 | 43 | // document.write() doesnt work when loaded from async script 44 | document.documentElement.innerHTML = r; 45 | 46 | // and setting innerHTML does not automatically load/initialize the scripts 47 | const scripts = Array.from(document.getElementsByTagName("script")); 48 | { 49 | var myScript = document.createElement('script'); 50 | myScript.src = scripts[0].src; 51 | document.head.appendChild(myScript); 52 | } 53 | { 54 | var myScript = document.createElement('script'); 55 | myScript.src = scripts[1].src; 56 | document.head.appendChild(myScript); 57 | } 58 | { 59 | var myScript = document.createElement('script'); 60 | myScript.textContent = scripts[2].textContent; 61 | document.head.appendChild(myScript); 62 | } 63 | 64 | globalThis.fetch = redirectFetch; 65 | 66 | setTimeout(() => { 67 | console.dir("dispatch load"); 68 | window.dispatchEvent(new Event("load")); 69 | }, 1000); 70 | }, 71 | status: (status) => { 72 | console.dir("status: " + status); 73 | return res; }, 74 | } 75 | 76 | await abap.Classes["CL_EXPRESS_ICF_SHIM"].run({req: {body: "", method: "GET", path: "", url: ""}, res, class: "ZCL_HTTP_HANDLER"}); 77 | } 78 | 79 | run(); 80 | -------------------------------------------------------------------------------- /test/zcl_http_handler.clas.abap: -------------------------------------------------------------------------------- 1 | CLASS zcl_http_handler DEFINITION PUBLIC. 2 | PUBLIC SECTION. 3 | INTERFACES if_http_extension. 4 | ENDCLASS. 5 | 6 | CLASS zcl_http_handler IMPLEMENTATION. 7 | 8 | METHOD if_http_extension~handle_request. 9 | 10 | DATA(result) = NEW zcl_otm_table_maintenance( 'ZOPENTEST' )->serve( VALUE #( 11 | method = server->request->get_method( ) 12 | path = server->request->get_header_field( '~path' ) 13 | body = server->request->get_data( ) ) ). 14 | 15 | server->response->set_data( result-body ). 16 | server->response->set_content_type( result-content_type ). 17 | server->response->set_status( 18 | code = result-status 19 | reason = CONV #( result-status ) ). 20 | 21 | ENDMETHOD. 22 | 23 | ENDCLASS. 24 | -------------------------------------------------------------------------------- /test/zcl_otm_table_maintenance.clas.testclasses.abap: -------------------------------------------------------------------------------- 1 | CLASS ltcl_test DEFINITION FOR TESTING RISK LEVEL HARMLESS DURATION SHORT FINAL. 2 | 3 | PRIVATE SECTION. 4 | METHODS list_key_fields FOR TESTING RAISING cx_static_check. 5 | METHODS build_metadata FOR TESTING RAISING cx_static_check. 6 | 7 | ENDCLASS. 8 | 9 | CLASS ltcl_test IMPLEMENTATION. 10 | 11 | METHOD list_key_fields. 12 | 13 | DATA(fields) = NEW zcl_otm_table_maintenance( 'ZOPENTEST' )->list_key_fields( ). 14 | 15 | cl_abap_unit_assert=>assert_equals( 16 | act = lines( fields ) 17 | exp = 1 ). 18 | 19 | ENDMETHOD. 20 | 21 | METHOD build_metadata. 22 | 23 | DATA(fields) = NEW zcl_otm_table_maintenance( 'ZOPENTEST' )->build_metadata( ). 24 | 25 | cl_abap_unit_assert=>assert_equals( 26 | act = lines( fields ) 27 | exp = 3 ). 28 | 29 | ENDMETHOD. 30 | 31 | ENDCLASS. 32 | -------------------------------------------------------------------------------- /test/zopentest.tabl.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | ZOPENTEST 7 | E 8 | TRANSP 9 | ZOPENTEST 10 | X 11 | A 12 | 1 13 | 14 | 15 | ZOPENTEST 16 | A 17 | 0 18 | APPL1 19 | N 20 | 21 | 22 | 23 | KEYFIELD 24 | X 25 | 0 26 | C 27 | 000008 28 | X 29 | CHAR 30 | 000004 31 | CHAR 32 | 33 | 34 | VALUEFIELD 35 | 0 36 | C 37 | 000020 38 | CHAR 39 | 000010 40 | CHAR 41 | 42 | 43 | BOOLEAN 44 | ABAP_BOOLEAN 45 | 0 46 | X 47 | F 48 | E 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-require-imports */ 2 | /* eslint-disable @typescript-eslint/no-var-requires */ 3 | const path = require("path"); 4 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 5 | const CopyPlugin = require("copy-webpack-plugin"); 6 | const webpack = require("webpack"); 7 | 8 | module.exports = ({mode} = {mode: "development"}) => ({ 9 | entry: { 10 | "app": "./test/web.mjs", 11 | }, 12 | mode, 13 | devtool: "nosources-source-map", 14 | experiments: { 15 | topLevelAwait: true 16 | }, 17 | output: { 18 | path: path.join(__dirname, "build"), 19 | filename: "[name].bundle.js", 20 | globalObject: "self", 21 | clean: true, 22 | }, 23 | devServer: { 24 | open: true, 25 | hot: true, 26 | }, 27 | resolve: { 28 | fallback: { 29 | "./%23ui2%23cl_json.clas.mjs": false, 30 | "crypto": require.resolve("crypto-browserify"), 31 | "path": require.resolve("path-browserify"), 32 | "buffer": require.resolve("buffer/"), 33 | "util/types": false, 34 | "util": require.resolve("web-encoding"), 35 | "zlib": false, 36 | "stream": false, 37 | "process": false, 38 | "http": false, 39 | "url": false, 40 | "fs": false, 41 | "tls": false, 42 | "https": false, 43 | "net": false, 44 | }, 45 | extensions: [".mjs", ".js"], 46 | }, 47 | module: { 48 | rules: [ 49 | ] 50 | }, 51 | plugins: [ 52 | new HtmlWebpackPlugin({ 53 | template: "test/index.html", 54 | }), 55 | new CopyPlugin({ 56 | patterns: [ 57 | { from: './node_modules/sql.js/dist/sql-wasm.wasm', to: "./" }, 58 | { from: './node_modules/sql.js/dist/sql-wasm-debug.wasm', to: "./" }, 59 | { from: './node_modules/sql.js/dist/sql-wasm-debug.js', to: "./" }, 60 | ], 61 | }), 62 | new webpack.ProvidePlugin({ 63 | process: 'process/browser', 64 | Buffer: ['buffer', 'Buffer'], 65 | }), 66 | ], 67 | }); 68 | --------------------------------------------------------------------------------