mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2026-01-06 04:26:57 +00:00
Feat : Integration with OpenMetadata Airflow APIs to make ingestion jobs scheduled through UI. (#1153)
* Feat : Integration with OpenMetadata Airflow APIs to make ingestion jobs scheduled through UI. * minor changed in mocked_data * added first wizard * added fields for details and config. * Feat :Added Cron Editor * Style: added styling for completed step label * Integrated ingestion APIs for list, trigger and delete And fixed Add Ingestion modal states * minor css changes * Removed enable ingestion from add service modal. * wrote effect to get updated cron value. * Added validation for required field. * handled undefined case for status * show actual data on preview * Added ingestionType to listing page * Added validation for each step. * fixed popover issue for status * added validation for already exists ingestion pipeline. * Minor UI tweaks for ingestion workflow list * Integrated add ingestion API * Added suppport for deploy and run ingestion. * Added support for Edit ingestion through PATCH API * Added search for ingestions * Style : added deploy icon for button. * Added pagination for ingestion list. * minor fix * Minor changes * Added no data placeholder and fixed non-parsing css Co-authored-by: darth-coder00 <aashit@getcollate.io>
This commit is contained in:
parent
496fa143d4
commit
01f9e54874
@ -4,6 +4,43 @@
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"@ant-design/colors": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-6.0.0.tgz",
|
||||
"integrity": "sha512-qAZRvPzfdWHtfameEGP2Qvuf838NhergR35o+EuVyB5XvSA98xod5r4utvi4TJ3ywmevm290g9nsCG5MryrdWQ==",
|
||||
"requires": {
|
||||
"@ctrl/tinycolor": "^3.4.0"
|
||||
}
|
||||
},
|
||||
"@ant-design/icons": {
|
||||
"version": "4.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-4.7.0.tgz",
|
||||
"integrity": "sha512-aoB4Z7JA431rt6d4u+8xcNPPCrdufSRMUOpxa1ab6mz1JCQZOEVolj2WVs/tDFmN62zzK30mNelEsprLYsSF3g==",
|
||||
"requires": {
|
||||
"@ant-design/colors": "^6.0.0",
|
||||
"@ant-design/icons-svg": "^4.2.1",
|
||||
"@babel/runtime": "^7.11.2",
|
||||
"classnames": "^2.2.6",
|
||||
"rc-util": "^5.9.4"
|
||||
}
|
||||
},
|
||||
"@ant-design/icons-svg": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@ant-design/icons-svg/-/icons-svg-4.2.1.tgz",
|
||||
"integrity": "sha512-EB0iwlKDGpG93hW8f85CTJTs4SvMX7tt5ceupvhALp1IF44SeUFOMhKUOYqpsoYWQKAOuTRDMqn75rEaKDp0Xw=="
|
||||
},
|
||||
"@ant-design/react-slick": {
|
||||
"version": "0.28.4",
|
||||
"resolved": "https://registry.npmjs.org/@ant-design/react-slick/-/react-slick-0.28.4.tgz",
|
||||
"integrity": "sha512-j9eAHTn7GxbXUFNknJoHS2ceAsqrQi2j8XykjZE1IXCD8kJF+t28EvhBLniDpbOsBk/3kjalnhriTfZcjBHNqg==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.10.4",
|
||||
"classnames": "^2.2.5",
|
||||
"json2mq": "^0.2.0",
|
||||
"lodash": "^4.17.21",
|
||||
"resize-observer-polyfill": "^1.5.0"
|
||||
}
|
||||
},
|
||||
"@babel/code-frame": {
|
||||
"version": "7.14.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz",
|
||||
@ -1304,6 +1341,11 @@
|
||||
"webpack-merge": "^4.2.2"
|
||||
}
|
||||
},
|
||||
"@ctrl/tinycolor": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.4.0.tgz",
|
||||
"integrity": "sha512-JZButFdZ1+/xAfpguQHoabIXkcqRRKpMrWKBkpEZZyxfY9C1DpADFB8PEqGSTeFr135SaTRfKqGKx5xSCLI7ZQ=="
|
||||
},
|
||||
"@deuex-solutions/redoc": {
|
||||
"version": "2.0.0-rc.27",
|
||||
"resolved": "https://registry.npmjs.org/@deuex-solutions/redoc/-/redoc-2.0.0-rc.27.tgz",
|
||||
@ -4659,6 +4701,54 @@
|
||||
"color-convert": "^1.9.0"
|
||||
}
|
||||
},
|
||||
"antd": {
|
||||
"version": "4.16.13",
|
||||
"resolved": "https://registry.npmjs.org/antd/-/antd-4.16.13.tgz",
|
||||
"integrity": "sha512-EMPD3fzKe7oayx9keD/GA1oKatcx7j5CGlkJj5eLS0/eEDDEkxVj3DFmKOPuHYt4BK7ltTzMFS+quSTmqUXPiw==",
|
||||
"requires": {
|
||||
"@ant-design/colors": "^6.0.0",
|
||||
"@ant-design/icons": "^4.6.3",
|
||||
"@ant-design/react-slick": "~0.28.1",
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"array-tree-filter": "^2.1.0",
|
||||
"classnames": "^2.2.6",
|
||||
"copy-to-clipboard": "^3.2.0",
|
||||
"lodash": "^4.17.21",
|
||||
"moment": "^2.25.3",
|
||||
"rc-cascader": "~1.4.0",
|
||||
"rc-checkbox": "~2.3.0",
|
||||
"rc-collapse": "~3.1.0",
|
||||
"rc-dialog": "~8.6.0",
|
||||
"rc-drawer": "~4.3.0",
|
||||
"rc-dropdown": "~3.2.0",
|
||||
"rc-field-form": "~1.20.0",
|
||||
"rc-image": "~5.2.5",
|
||||
"rc-input-number": "~7.1.0",
|
||||
"rc-mentions": "~1.6.1",
|
||||
"rc-menu": "~9.0.12",
|
||||
"rc-motion": "^2.4.0",
|
||||
"rc-notification": "~4.5.7",
|
||||
"rc-pagination": "~3.1.9",
|
||||
"rc-picker": "~2.5.10",
|
||||
"rc-progress": "~3.1.0",
|
||||
"rc-rate": "~2.9.0",
|
||||
"rc-resize-observer": "^1.0.0",
|
||||
"rc-select": "~12.1.6",
|
||||
"rc-slider": "~9.7.1",
|
||||
"rc-steps": "~4.1.0",
|
||||
"rc-switch": "~3.2.0",
|
||||
"rc-table": "~7.15.1",
|
||||
"rc-tabs": "~11.10.0",
|
||||
"rc-textarea": "~0.3.0",
|
||||
"rc-tooltip": "~5.1.1",
|
||||
"rc-tree": "~4.2.1",
|
||||
"rc-tree-select": "~4.3.0",
|
||||
"rc-trigger": "^5.2.10",
|
||||
"rc-upload": "~4.3.0",
|
||||
"rc-util": "^5.13.1",
|
||||
"scroll-into-view-if-needed": "^2.2.25"
|
||||
}
|
||||
},
|
||||
"anymatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
|
||||
@ -4743,6 +4833,11 @@
|
||||
"is-string": "^1.0.5"
|
||||
}
|
||||
},
|
||||
"array-tree-filter": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/array-tree-filter/-/array-tree-filter-2.1.0.tgz",
|
||||
"integrity": "sha512-4ROwICNlNw/Hqa9v+rk5h22KjmzB1JGTMVKP2AKJBOCgb0yL0ASf0+YvCcLNNwquOHNX48jkeZIJ3a+oOQqKcw=="
|
||||
},
|
||||
"array-union": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
|
||||
@ -4841,6 +4936,11 @@
|
||||
"integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==",
|
||||
"dev": true
|
||||
},
|
||||
"async-validator": {
|
||||
"version": "3.5.2",
|
||||
"resolved": "https://registry.npmjs.org/async-validator/-/async-validator-3.5.2.tgz",
|
||||
"integrity": "sha512-8eLCg00W9pIRZSB781UUX/H6Oskmm8xloZfr09lz5bikRpBVDlJ3hRVuxxP1SxcwsEYfJ4IU8Q19Y8/893r3rQ=="
|
||||
},
|
||||
"asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
@ -6281,6 +6381,14 @@
|
||||
"integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=",
|
||||
"dev": true
|
||||
},
|
||||
"copy-to-clipboard": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.1.tgz",
|
||||
"integrity": "sha512-i13qo6kIHTTpCm8/Wup+0b1mVWETvu2kIMzKoK8FpkLkFxlt0znUAHcMzox+T8sPlqtZXq3CulEjQHsYiGFJUw==",
|
||||
"requires": {
|
||||
"toggle-selection": "^1.0.6"
|
||||
}
|
||||
},
|
||||
"copy-webpack-plugin": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-7.0.0.tgz",
|
||||
@ -6412,6 +6520,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"cronstrue": {
|
||||
"version": "1.122.0",
|
||||
"resolved": "https://registry.npmjs.org/cronstrue/-/cronstrue-1.122.0.tgz",
|
||||
"integrity": "sha512-PFuhZd+iPQQ0AWTXIEYX+t3nFGzBrWxmTWUKJOrsGRewaBSLKZ4I1f8s2kryU75nNxgyugZgiGh2OJsCTA/XlA=="
|
||||
},
|
||||
"cross-fetch": {
|
||||
"version": "3.1.4",
|
||||
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.4.tgz",
|
||||
@ -6826,6 +6939,16 @@
|
||||
"whatwg-url": "^8.0.0"
|
||||
}
|
||||
},
|
||||
"date-fns": {
|
||||
"version": "2.25.0",
|
||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.25.0.tgz",
|
||||
"integrity": "sha512-ovYRFnTrbGPD4nqaEqescPEv1mNwvt+UTqI3Ay9SzNtey9NZnYu6E2qCcBBgJ6/2VF1zGGygpyTDITqpQQ5e+w=="
|
||||
},
|
||||
"dayjs": {
|
||||
"version": "1.10.7",
|
||||
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.10.7.tgz",
|
||||
"integrity": "sha512-P6twpd70BcPK34K26uJ1KT3wlhpuOAPoMwJzpsIWUxHZ7wpmbdZL/hQqBDfz7hGurYSa5PhzdhDHtt319hL3ig=="
|
||||
},
|
||||
"debug": {
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz",
|
||||
@ -7137,6 +7260,11 @@
|
||||
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.3.0.tgz",
|
||||
"integrity": "sha512-PzwHEmsRP3IGY4gv/Ug+rMeaTIyTJvadCb+ujYXYeIylbHJezIyNToe8KfEgHTCEYyC+/bUghYOGg8yMGlZ6vA=="
|
||||
},
|
||||
"dom-align": {
|
||||
"version": "1.12.2",
|
||||
"resolved": "https://registry.npmjs.org/dom-align/-/dom-align-1.12.2.tgz",
|
||||
"integrity": "sha512-pHuazgqrsTFrGU2WLDdXxCFabkdQDx72ddkraZNih1KsMcN5qsRSTR9O4VJRlwTPCPb5COYg3LOfiMHHcPInHg=="
|
||||
},
|
||||
"dom-converter": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz",
|
||||
@ -15977,6 +16105,11 @@
|
||||
"resolved": "https://registry.npmjs.org/modern-normalize/-/modern-normalize-1.1.0.tgz",
|
||||
"integrity": "sha512-2lMlY1Yc1+CUy0gw4H95uNN7vjbpoED7NNRSBHE25nWfLBdmMzFCsPshlzbxHz+gYMcBEUN8V4pU16prcdPSgA=="
|
||||
},
|
||||
"moment": {
|
||||
"version": "2.29.1",
|
||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz",
|
||||
"integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ=="
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
@ -17558,6 +17691,384 @@
|
||||
"unpipe": "1.0.0"
|
||||
}
|
||||
},
|
||||
"rc-align": {
|
||||
"version": "4.0.11",
|
||||
"resolved": "https://registry.npmjs.org/rc-align/-/rc-align-4.0.11.tgz",
|
||||
"integrity": "sha512-n9mQfIYQbbNTbefyQnRHZPWuTEwG1rY4a9yKlIWHSTbgwI+XUMGRYd0uJ5pE2UbrNX0WvnMBA1zJ3Lrecpra/A==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.10.1",
|
||||
"classnames": "2.x",
|
||||
"dom-align": "^1.7.0",
|
||||
"lodash": "^4.17.21",
|
||||
"rc-util": "^5.3.0",
|
||||
"resize-observer-polyfill": "^1.5.1"
|
||||
}
|
||||
},
|
||||
"rc-cascader": {
|
||||
"version": "1.4.3",
|
||||
"resolved": "https://registry.npmjs.org/rc-cascader/-/rc-cascader-1.4.3.tgz",
|
||||
"integrity": "sha512-Q4l9Mv8aaISJ+giVnM9IaXxDeMqHUGLvi4F+LksS6pHlaKlN4awop/L+IMjIXpL+ug/ojaCyv/ixcVopJYYCVA==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"array-tree-filter": "^2.1.0",
|
||||
"rc-trigger": "^5.0.4",
|
||||
"rc-util": "^5.0.1",
|
||||
"warning": "^4.0.1"
|
||||
}
|
||||
},
|
||||
"rc-checkbox": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/rc-checkbox/-/rc-checkbox-2.3.2.tgz",
|
||||
"integrity": "sha512-afVi1FYiGv1U0JlpNH/UaEXdh6WUJjcWokj/nUN2TgG80bfG+MDdbfHKlLcNNba94mbjy2/SXJ1HDgrOkXGAjg==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.10.1",
|
||||
"classnames": "^2.2.1"
|
||||
}
|
||||
},
|
||||
"rc-collapse": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/rc-collapse/-/rc-collapse-3.1.2.tgz",
|
||||
"integrity": "sha512-HujcKq7mghk/gVKeI6EjzTbb8e19XUZpakrYazu1MblEZ3Hu3WBMSN4A3QmvbF6n1g7x6lUlZvsHZ5shABWYOQ==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.10.1",
|
||||
"classnames": "2.x",
|
||||
"rc-motion": "^2.3.4",
|
||||
"rc-util": "^5.2.1",
|
||||
"shallowequal": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"rc-dialog": {
|
||||
"version": "8.6.0",
|
||||
"resolved": "https://registry.npmjs.org/rc-dialog/-/rc-dialog-8.6.0.tgz",
|
||||
"integrity": "sha512-GSbkfqjqxpZC5/zc+8H332+q5l/DKUhpQr0vdX2uDsxo5K0PhvaMEVjyoJUTkZ3+JstEADQji1PVLVb/2bJeOQ==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.10.1",
|
||||
"classnames": "^2.2.6",
|
||||
"rc-motion": "^2.3.0",
|
||||
"rc-util": "^5.6.1"
|
||||
}
|
||||
},
|
||||
"rc-drawer": {
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/rc-drawer/-/rc-drawer-4.3.1.tgz",
|
||||
"integrity": "sha512-GMfFy4maqxS9faYXEhQ+0cA1xtkddEQzraf6SAdzWbn444DrrLogwYPk1NXSpdXjLCLxgxOj9MYtyYG42JsfXg==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.10.1",
|
||||
"classnames": "^2.2.6",
|
||||
"rc-util": "^5.7.0"
|
||||
}
|
||||
},
|
||||
"rc-dropdown": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/rc-dropdown/-/rc-dropdown-3.2.0.tgz",
|
||||
"integrity": "sha512-j1HSw+/QqlhxyTEF6BArVZnTmezw2LnSmRk6I9W7BCqNCKaRwleRmMMs1PHbuaG8dKHVqP6e21RQ7vPBLVnnNw==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.10.1",
|
||||
"classnames": "^2.2.6",
|
||||
"rc-trigger": "^5.0.4"
|
||||
}
|
||||
},
|
||||
"rc-field-form": {
|
||||
"version": "1.20.1",
|
||||
"resolved": "https://registry.npmjs.org/rc-field-form/-/rc-field-form-1.20.1.tgz",
|
||||
"integrity": "sha512-f64KEZop7zSlrG4ef/PLlH12SLn6iHDQ3sTG+RfKBM45hikwV1i8qMf53xoX12NvXXWg1VwchggX/FSso4bWaA==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.8.4",
|
||||
"async-validator": "^3.0.3",
|
||||
"rc-util": "^5.8.0"
|
||||
}
|
||||
},
|
||||
"rc-image": {
|
||||
"version": "5.2.5",
|
||||
"resolved": "https://registry.npmjs.org/rc-image/-/rc-image-5.2.5.tgz",
|
||||
"integrity": "sha512-qUfZjYIODxO0c8a8P5GeuclYXZjzW4hV/5hyo27XqSFo1DmTCs2HkVeQObkcIk5kNsJtgsj1KoPThVsSc/PXOw==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.11.2",
|
||||
"classnames": "^2.2.6",
|
||||
"rc-dialog": "~8.6.0",
|
||||
"rc-util": "^5.0.6"
|
||||
}
|
||||
},
|
||||
"rc-input-number": {
|
||||
"version": "7.1.4",
|
||||
"resolved": "https://registry.npmjs.org/rc-input-number/-/rc-input-number-7.1.4.tgz",
|
||||
"integrity": "sha512-EG4iqkqyqzLRu/Dq+fw2od7nlgvXLEatE+J6uhi3HXE1qlM3C7L6a7o/hL9Ly9nimkES2IeQoj3Qda3I0izj3Q==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.10.1",
|
||||
"classnames": "^2.2.5",
|
||||
"rc-util": "^5.9.8"
|
||||
}
|
||||
},
|
||||
"rc-mentions": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/rc-mentions/-/rc-mentions-1.6.1.tgz",
|
||||
"integrity": "sha512-LDzGI8jJVGnkhpTZxZuYBhMz3avcZZqPGejikchh97xPni/g4ht714Flh7DVvuzHQ+BoKHhIjobHnw1rcP8erg==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.10.1",
|
||||
"classnames": "^2.2.6",
|
||||
"rc-menu": "^9.0.0",
|
||||
"rc-textarea": "^0.3.0",
|
||||
"rc-trigger": "^5.0.4",
|
||||
"rc-util": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"rc-menu": {
|
||||
"version": "9.0.14",
|
||||
"resolved": "https://registry.npmjs.org/rc-menu/-/rc-menu-9.0.14.tgz",
|
||||
"integrity": "sha512-CIox5mZeLDAi32SlHrV7UeSjv7tmJJhwRyxQtZCKt351w3q59XlL4WMFOmtT9gwIfP9h0XoxdBZUMe/xzkp78A==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.10.1",
|
||||
"classnames": "2.x",
|
||||
"rc-motion": "^2.4.3",
|
||||
"rc-overflow": "^1.2.0",
|
||||
"rc-trigger": "^5.1.2",
|
||||
"rc-util": "^5.12.0",
|
||||
"shallowequal": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"rc-motion": {
|
||||
"version": "2.4.4",
|
||||
"resolved": "https://registry.npmjs.org/rc-motion/-/rc-motion-2.4.4.tgz",
|
||||
"integrity": "sha512-ms7n1+/TZQBS0Ydd2Q5P4+wJTSOrhIrwNxLXCZpR7Fa3/oac7Yi803HDALc2hLAKaCTQtw9LmQeB58zcwOsqlQ==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.11.1",
|
||||
"classnames": "^2.2.1",
|
||||
"rc-util": "^5.2.1"
|
||||
}
|
||||
},
|
||||
"rc-notification": {
|
||||
"version": "4.5.7",
|
||||
"resolved": "https://registry.npmjs.org/rc-notification/-/rc-notification-4.5.7.tgz",
|
||||
"integrity": "sha512-zhTGUjBIItbx96SiRu3KVURcLOydLUHZCPpYEn1zvh+re//Tnq/wSxN4FKgp38n4HOgHSVxcLEeSxBMTeBBDdw==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.10.1",
|
||||
"classnames": "2.x",
|
||||
"rc-motion": "^2.2.0",
|
||||
"rc-util": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"rc-overflow": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/rc-overflow/-/rc-overflow-1.2.2.tgz",
|
||||
"integrity": "sha512-X5kj9LDU1ue5wHkqvCprJWLKC+ZLs3p4He/oxjZ1Q4NKaqKBaYf5OdSzRSgh3WH8kSdrfU8LjvlbWnHgJOEkNQ==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.11.1",
|
||||
"classnames": "^2.2.1",
|
||||
"rc-resize-observer": "^1.0.0",
|
||||
"rc-util": "^5.5.1"
|
||||
}
|
||||
},
|
||||
"rc-pagination": {
|
||||
"version": "3.1.9",
|
||||
"resolved": "https://registry.npmjs.org/rc-pagination/-/rc-pagination-3.1.9.tgz",
|
||||
"integrity": "sha512-IKBKaJ4icVPeEk9qRHrFBJmHxBUrCp3+nENBYob4Ofqsu3RXjBOy4N36zONO7oubgLyiG3PxVmyAuVlTkoc7Jg==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.10.1",
|
||||
"classnames": "^2.2.1"
|
||||
}
|
||||
},
|
||||
"rc-picker": {
|
||||
"version": "2.5.19",
|
||||
"resolved": "https://registry.npmjs.org/rc-picker/-/rc-picker-2.5.19.tgz",
|
||||
"integrity": "sha512-u6myoCu/qiQ0vLbNzSzNrzTQhs7mldArCpPHrEI6OUiifs+IPXmbesqSm0zilJjfzrZJLgYeyyOMSznSlh0GKA==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.10.1",
|
||||
"classnames": "^2.2.1",
|
||||
"date-fns": "2.x",
|
||||
"dayjs": "1.x",
|
||||
"moment": "^2.24.0",
|
||||
"rc-trigger": "^5.0.4",
|
||||
"rc-util": "^5.4.0",
|
||||
"shallowequal": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"rc-progress": {
|
||||
"version": "3.1.4",
|
||||
"resolved": "https://registry.npmjs.org/rc-progress/-/rc-progress-3.1.4.tgz",
|
||||
"integrity": "sha512-XBAif08eunHssGeIdxMXOmRQRULdHaDdIFENQ578CMb4dyewahmmfJRyab+hw4KH4XssEzzYOkAInTLS7JJG+Q==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.10.1",
|
||||
"classnames": "^2.2.6"
|
||||
}
|
||||
},
|
||||
"rc-rate": {
|
||||
"version": "2.9.1",
|
||||
"resolved": "https://registry.npmjs.org/rc-rate/-/rc-rate-2.9.1.tgz",
|
||||
"integrity": "sha512-MmIU7FT8W4LYRRHJD1sgG366qKtSaKb67D0/vVvJYR0lrCuRrCiVQ5qhfT5ghVO4wuVIORGpZs7ZKaYu+KMUzA==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.10.1",
|
||||
"classnames": "^2.2.5",
|
||||
"rc-util": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"rc-resize-observer": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/rc-resize-observer/-/rc-resize-observer-1.0.1.tgz",
|
||||
"integrity": "sha512-OxO2mJI9e8610CAWBFfm52SPvWib0eNKjaSsRbbKHmLaJIxw944P+D61DlLJ/w2vuOjGNcalJu8VdqyNm/XCRg==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.10.1",
|
||||
"classnames": "^2.2.1",
|
||||
"rc-util": "^5.0.0",
|
||||
"resize-observer-polyfill": "^1.5.1"
|
||||
}
|
||||
},
|
||||
"rc-select": {
|
||||
"version": "12.1.13",
|
||||
"resolved": "https://registry.npmjs.org/rc-select/-/rc-select-12.1.13.tgz",
|
||||
"integrity": "sha512-cPI+aesP6dgCAaey4t4upDbEukJe+XN0DK6oO/6flcCX5o28o7KNZD7JAiVtC/6fCwqwI/kSs7S/43dvHmBl+A==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.10.1",
|
||||
"classnames": "2.x",
|
||||
"rc-motion": "^2.0.1",
|
||||
"rc-overflow": "^1.0.0",
|
||||
"rc-trigger": "^5.0.4",
|
||||
"rc-util": "^5.9.8",
|
||||
"rc-virtual-list": "^3.2.0"
|
||||
}
|
||||
},
|
||||
"rc-slider": {
|
||||
"version": "9.7.4",
|
||||
"resolved": "https://registry.npmjs.org/rc-slider/-/rc-slider-9.7.4.tgz",
|
||||
"integrity": "sha512-pjLKLiDKiaL7/pNywfIBD+lDo5TtVo05KuIBSWEIoqu6FHh6IMWvthCiaODuYaVs3RLeF2nXOP5AjkD2Lt2Rwg==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.10.1",
|
||||
"classnames": "^2.2.5",
|
||||
"rc-tooltip": "^5.0.1",
|
||||
"rc-util": "^5.0.0",
|
||||
"shallowequal": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"rc-steps": {
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/rc-steps/-/rc-steps-4.1.4.tgz",
|
||||
"integrity": "sha512-qoCqKZWSpkh/b03ASGx1WhpKnuZcRWmvuW+ZUu4mvMdfvFzVxblTwUM+9aBd0mlEUFmt6GW8FXhMpHkK3Uzp3w==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.10.2",
|
||||
"classnames": "^2.2.3",
|
||||
"rc-util": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"rc-switch": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/rc-switch/-/rc-switch-3.2.2.tgz",
|
||||
"integrity": "sha512-+gUJClsZZzvAHGy1vZfnwySxj+MjLlGRyXKXScrtCTcmiYNPzxDFOxdQ/3pK1Kt/0POvwJ/6ALOR8gwdXGhs+A==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.10.1",
|
||||
"classnames": "^2.2.1",
|
||||
"rc-util": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"rc-table": {
|
||||
"version": "7.15.2",
|
||||
"resolved": "https://registry.npmjs.org/rc-table/-/rc-table-7.15.2.tgz",
|
||||
"integrity": "sha512-TAs7kCpIZwc2mtvD8CMrXSM6TqJDUsy0rUEV1YgRru33T8bjtAtc+9xW/KC1VWROJlHSpU0R0kXjFs9h/6+IzQ==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.10.1",
|
||||
"classnames": "^2.2.5",
|
||||
"rc-resize-observer": "^1.0.0",
|
||||
"rc-util": "^5.13.0",
|
||||
"shallowequal": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"rc-tabs": {
|
||||
"version": "11.10.3",
|
||||
"resolved": "https://registry.npmjs.org/rc-tabs/-/rc-tabs-11.10.3.tgz",
|
||||
"integrity": "sha512-rPxsci+76/nnJowNOBO3LTi4eL6trG49cR9yPc4XbuyHXhCHUujN5F4+jFl7trISy+yVN6gCZ/wiTtVnkcR/UA==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.11.2",
|
||||
"classnames": "2.x",
|
||||
"rc-dropdown": "^3.2.0",
|
||||
"rc-menu": "^9.0.0",
|
||||
"rc-resize-observer": "^1.0.0",
|
||||
"rc-util": "^5.5.0"
|
||||
}
|
||||
},
|
||||
"rc-textarea": {
|
||||
"version": "0.3.5",
|
||||
"resolved": "https://registry.npmjs.org/rc-textarea/-/rc-textarea-0.3.5.tgz",
|
||||
"integrity": "sha512-qa+k5vDn9ct65qr+SgD2KwJ9Xz6P84lG2z+TDht/RBr71WnM/K61PqHUAcUyU6YqTJD26IXgjPuuhZR7HMw7eA==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.10.1",
|
||||
"classnames": "^2.2.1",
|
||||
"rc-resize-observer": "^1.0.0",
|
||||
"rc-util": "^5.7.0"
|
||||
}
|
||||
},
|
||||
"rc-tooltip": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/rc-tooltip/-/rc-tooltip-5.1.1.tgz",
|
||||
"integrity": "sha512-alt8eGMJulio6+4/uDm7nvV+rJq9bsfxFDCI0ljPdbuoygUscbsMYb6EQgwib/uqsXQUvzk+S7A59uYHmEgmDA==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.11.2",
|
||||
"rc-trigger": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"rc-tree": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/rc-tree/-/rc-tree-4.2.2.tgz",
|
||||
"integrity": "sha512-V1hkJt092VrOVjNyfj5IYbZKRMHxWihZarvA5hPL/eqm7o2+0SNkeidFYm7LVVBrAKBpOpa0l8xt04uiqOd+6w==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.10.1",
|
||||
"classnames": "2.x",
|
||||
"rc-motion": "^2.0.1",
|
||||
"rc-util": "^5.0.0",
|
||||
"rc-virtual-list": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"rc-tree-select": {
|
||||
"version": "4.3.3",
|
||||
"resolved": "https://registry.npmjs.org/rc-tree-select/-/rc-tree-select-4.3.3.tgz",
|
||||
"integrity": "sha512-0tilOHLJA6p+TNg4kD559XnDX3PTEYuoSF7m7ryzFLAYvdEEPtjn0QZc5z6L0sMKBiBlj8a2kf0auw8XyHU3lA==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.10.1",
|
||||
"classnames": "2.x",
|
||||
"rc-select": "^12.0.0",
|
||||
"rc-tree": "^4.0.0",
|
||||
"rc-util": "^5.0.5"
|
||||
}
|
||||
},
|
||||
"rc-trigger": {
|
||||
"version": "5.2.10",
|
||||
"resolved": "https://registry.npmjs.org/rc-trigger/-/rc-trigger-5.2.10.tgz",
|
||||
"integrity": "sha512-FkUf4H9BOFDaIwu42fvRycXMAvkttph9AlbCZXssZDVzz2L+QZ0ERvfB/4nX3ZFPh1Zd+uVGr1DEDeXxq4J1TA==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.11.2",
|
||||
"classnames": "^2.2.6",
|
||||
"rc-align": "^4.0.0",
|
||||
"rc-motion": "^2.0.0",
|
||||
"rc-util": "^5.5.0"
|
||||
}
|
||||
},
|
||||
"rc-upload": {
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/rc-upload/-/rc-upload-4.3.2.tgz",
|
||||
"integrity": "sha512-v0HdwC/19xKAn1OYZ4hTMUSqSs/IA0n1v4p/cioSSnKubHrdpcCXC45N+TFMSOZtBlf4+xMNCFo3KDih31lAMg==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.10.1",
|
||||
"classnames": "^2.2.5",
|
||||
"rc-util": "^5.2.0"
|
||||
}
|
||||
},
|
||||
"rc-util": {
|
||||
"version": "5.15.0",
|
||||
"resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.15.0.tgz",
|
||||
"integrity": "sha512-8RI8sjOCXD3FhD3dzQNBQetpGol6BBd3sHQ/8jSGk9NPT0CH3JGtBfPODnASyE7AdDpCFQMOmgcp9CBs3S/1hg==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"react-is": "^16.12.0",
|
||||
"shallowequal": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"rc-virtual-list": {
|
||||
"version": "3.4.2",
|
||||
"resolved": "https://registry.npmjs.org/rc-virtual-list/-/rc-virtual-list-3.4.2.tgz",
|
||||
"integrity": "sha512-OyVrrPvvFcHvV0ssz5EDZ+7Rf5qLat/+mmujjchNw5FfbJWNDwkpQ99EcVE6+FtNRmX9wFa1LGNpZLUTvp/4GQ==",
|
||||
"requires": {
|
||||
"classnames": "^2.2.6",
|
||||
"rc-resize-observer": "^1.0.0",
|
||||
"rc-util": "^5.0.7"
|
||||
}
|
||||
},
|
||||
"react": {
|
||||
"version": "16.14.0",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz",
|
||||
@ -17699,6 +18210,11 @@
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||
},
|
||||
"react-js-cron": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/react-js-cron/-/react-js-cron-1.2.1.tgz",
|
||||
"integrity": "sha512-3iesosu5l/JsmbSZj8kb3OPcGFA+yM6WvetbsDk8SC8mgaxyAWU+1VS27rrd1OPCnCAHjx42trBxAteQRbtYqg=="
|
||||
},
|
||||
"react-js-pagination": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/react-js-pagination/-/react-js-pagination-3.0.3.tgz",
|
||||
@ -20681,6 +21197,11 @@
|
||||
"is-number": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"toggle-selection": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz",
|
||||
"integrity": "sha1-bkWxJj8gF/oKzH2J14sVuL932jI="
|
||||
},
|
||||
"toidentifier": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
"@types/react-dom": "^17.0.5",
|
||||
"@typescript-eslint/eslint-plugin": "^2.10.0",
|
||||
"@typescript-eslint/parser": "^2.10.0",
|
||||
"antd": "^4.16.13",
|
||||
"autoprefixer": "^9.8.6",
|
||||
"axios": "^0.21.1",
|
||||
"babel-plugin-named-asset-import": "^0.3.6",
|
||||
@ -26,6 +27,7 @@
|
||||
"codemirror": "^5.62.3",
|
||||
"cookie-storage": "^6.1.0",
|
||||
"core-js": "^3.10.1",
|
||||
"cronstrue": "^1.122.0",
|
||||
"diff": "^5.0.0",
|
||||
"draft-js": "^0.11.7",
|
||||
"eslint": "^6.6.0",
|
||||
@ -59,6 +61,7 @@
|
||||
"react-dom": "^16.14.0",
|
||||
"react-draft-wysiwyg": "^1.14.7",
|
||||
"react-flow-renderer": "^9.6.8",
|
||||
"react-js-cron": "^1.2.1",
|
||||
"react-js-pagination": "^3.0.3",
|
||||
"react-markdown": "^6.0.3",
|
||||
"react-oidc": "^1.0.3",
|
||||
|
||||
@ -0,0 +1,3 @@
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.1538 0.075705L0.292674 6.34163C-0.131459 6.58534 -0.0775638 7.17584 0.344226 7.35393L2.83513 8.39903L9.56737 2.46585C9.69625 2.35103 9.87903 2.52678 9.7689 2.66034L4.12394 9.53787V11.4242C4.12394 11.9772 4.79177 12.1951 5.11983 11.7944L6.60781 9.98309L9.52754 11.2063C9.86028 11.3469 10.2399 11.1383 10.3008 10.7798L11.988 0.656838C12.0677 0.183496 11.5592 -0.158623 11.1538 0.075705Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 517 B |
@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { Operation } from 'fast-json-patch';
|
||||
import { IngestionData } from '../components/Ingestion/ingestion.interface';
|
||||
import { getURLWithQueryFields } from '../utils/APIUtils';
|
||||
import APIClient from './index';
|
||||
|
||||
const operationsBaseUrl = '/api/operations/v1';
|
||||
|
||||
export const addIngestionWorkflow = (
|
||||
data: IngestionData
|
||||
): Promise<AxiosResponse> => {
|
||||
const url = '/ingestion';
|
||||
|
||||
return APIClient({
|
||||
method: 'post',
|
||||
url,
|
||||
baseURL: operationsBaseUrl,
|
||||
data: data,
|
||||
});
|
||||
};
|
||||
|
||||
export const getIngestionWorkflows = (
|
||||
arrQueryFields: Array<string>,
|
||||
paging?: string
|
||||
): Promise<AxiosResponse> => {
|
||||
const url = `${getURLWithQueryFields('/ingestion', arrQueryFields)}${
|
||||
paging ? paging : ''
|
||||
}`;
|
||||
|
||||
return APIClient({ method: 'get', url, baseURL: operationsBaseUrl });
|
||||
};
|
||||
|
||||
export const triggerIngestionWorkflowsById = (
|
||||
id: string,
|
||||
arrQueryFields = ''
|
||||
): Promise<AxiosResponse> => {
|
||||
const url = getURLWithQueryFields(`/ingestion/trigger/${id}`, arrQueryFields);
|
||||
|
||||
return APIClient({ method: 'post', url, baseURL: operationsBaseUrl });
|
||||
};
|
||||
|
||||
export const deleteIngestionWorkflowsById = (
|
||||
id: string,
|
||||
arrQueryFields = ''
|
||||
): Promise<AxiosResponse> => {
|
||||
const url = getURLWithQueryFields(`/ingestion/${id}`, arrQueryFields);
|
||||
|
||||
return APIClient({ method: 'delete', url, baseURL: operationsBaseUrl });
|
||||
};
|
||||
|
||||
export const patchIngestionWorkflowBtId = (
|
||||
id: string,
|
||||
data: Array<Operation>
|
||||
): Promise<AxiosResponse> => {
|
||||
const url = `/ingestion/${id}`;
|
||||
|
||||
return APIClient({
|
||||
method: 'patch',
|
||||
url,
|
||||
baseURL: operationsBaseUrl,
|
||||
data: data,
|
||||
headers: { 'Content-type': 'application/json-patch+json' },
|
||||
});
|
||||
};
|
||||
@ -0,0 +1,460 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import classNames from 'classnames';
|
||||
import cronstrue from 'cronstrue';
|
||||
import { compare } from 'fast-json-patch';
|
||||
import { capitalize, isNil, lowerCase } from 'lodash';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import {
|
||||
getServiceDetailsPath,
|
||||
TITLE_FOR_NON_ADMIN_ACTION,
|
||||
} from '../../constants/constants';
|
||||
import { NoDataFoundPlaceHolder } from '../../constants/services.const';
|
||||
import { useAuth } from '../../hooks/authHooks';
|
||||
import { isEven } from '../../utils/CommonUtils';
|
||||
import { Button } from '../buttons/Button/Button';
|
||||
import NextPrevious from '../common/next-previous/NextPrevious';
|
||||
import NonAdminAction from '../common/non-admin-action/NonAdminAction';
|
||||
import PopOver from '../common/popover/PopOver';
|
||||
import Searchbar from '../common/searchbar/Searchbar';
|
||||
import PageContainer from '../containers/PageContainer';
|
||||
import IngestionModal from '../IngestionModal/IngestionModal.component';
|
||||
import Loader from '../Loader/Loader';
|
||||
import ConfirmationModal from '../Modals/ConfirmationModal/ConfirmationModal';
|
||||
import { IngestionData, Props } from './ingestion.interface';
|
||||
|
||||
const Ingestion: React.FC<Props> = ({
|
||||
ingestionList,
|
||||
serviceList,
|
||||
deleteIngestion,
|
||||
triggerIngestion,
|
||||
addIngestion,
|
||||
updateIngestion,
|
||||
paging,
|
||||
pagingHandler,
|
||||
}: Props) => {
|
||||
const { isAdminUser, isAuthDisabled } = useAuth();
|
||||
const [searchText, setSearchText] = useState('');
|
||||
const [currTriggerId, setCurrTriggerId] = useState({ id: '', state: '' });
|
||||
const [isAdding, setIsAdding] = useState<boolean>(false);
|
||||
const [isUpdating, setIsUpdating] = useState<boolean>(false);
|
||||
const [isConfirmationModalOpen, setIsConfirmationModalOpen] = useState(false);
|
||||
const [deleteSelection, setDeleteSelection] = useState({
|
||||
id: '',
|
||||
name: '',
|
||||
state: '',
|
||||
});
|
||||
const [updateSelection, setUpdateSelection] = useState({
|
||||
id: '',
|
||||
name: '',
|
||||
state: '',
|
||||
ingestion: {} as IngestionData,
|
||||
});
|
||||
|
||||
const handleSearchAction = (searchValue: string) => {
|
||||
setSearchText(searchValue);
|
||||
};
|
||||
|
||||
const handleTriggerIngestion = (id: string, displayName: string) => {
|
||||
setCurrTriggerId({ id, state: 'waiting' });
|
||||
triggerIngestion(id, displayName)
|
||||
.then(() => {
|
||||
setCurrTriggerId({ id, state: 'success' });
|
||||
setTimeout(() => setCurrTriggerId({ id: '', state: '' }), 1500);
|
||||
})
|
||||
.catch(() => setCurrTriggerId({ id: '', state: '' }));
|
||||
};
|
||||
|
||||
const handleCancelConfirmationModal = () => {
|
||||
setIsConfirmationModalOpen(false);
|
||||
setDeleteSelection({
|
||||
id: '',
|
||||
name: '',
|
||||
state: '',
|
||||
});
|
||||
};
|
||||
|
||||
const handleUpdate = (ingestion: IngestionData) => {
|
||||
setUpdateSelection({
|
||||
id: ingestion.id as string,
|
||||
name: ingestion.displayName,
|
||||
state: '',
|
||||
ingestion: ingestion,
|
||||
});
|
||||
setIsUpdating(true);
|
||||
};
|
||||
|
||||
const handleCancelUpdate = () => {
|
||||
setUpdateSelection({
|
||||
id: '',
|
||||
name: '',
|
||||
state: '',
|
||||
ingestion: {} as IngestionData,
|
||||
});
|
||||
setIsUpdating(false);
|
||||
};
|
||||
const handleUpdateIngestion = (
|
||||
data: IngestionData,
|
||||
triggerIngestion?: boolean
|
||||
) => {
|
||||
const { service, owner } = updateSelection.ingestion;
|
||||
const updatedData = {
|
||||
...updateSelection.ingestion,
|
||||
...data,
|
||||
service,
|
||||
owner,
|
||||
};
|
||||
const patch = compare(updateSelection.ingestion, updatedData);
|
||||
setUpdateSelection((prev) => ({ ...prev, state: 'waiting' }));
|
||||
updateIngestion(
|
||||
updateSelection.id,
|
||||
updateSelection.name,
|
||||
patch,
|
||||
triggerIngestion
|
||||
)
|
||||
.then(() => {
|
||||
setTimeout(() => {
|
||||
setUpdateSelection((prev) => ({ ...prev, state: 'success' }));
|
||||
handleCancelUpdate();
|
||||
}, 500);
|
||||
})
|
||||
.catch(() => {
|
||||
handleCancelUpdate();
|
||||
});
|
||||
};
|
||||
|
||||
const handleDelete = (id: string, displayName: string) => {
|
||||
setDeleteSelection({ id, name: displayName, state: 'waiting' });
|
||||
deleteIngestion(id, displayName)
|
||||
.then(() => {
|
||||
setTimeout(() => {
|
||||
setDeleteSelection({ id, name: displayName, state: 'success' });
|
||||
handleCancelConfirmationModal();
|
||||
}, 500);
|
||||
})
|
||||
.catch(() => {
|
||||
handleCancelConfirmationModal();
|
||||
});
|
||||
};
|
||||
|
||||
const ConfirmDelete = (id: string, name: string) => {
|
||||
setDeleteSelection({
|
||||
id,
|
||||
name,
|
||||
state: '',
|
||||
});
|
||||
setIsConfirmationModalOpen(true);
|
||||
};
|
||||
|
||||
const getServiceTypeFromName = (serviceName = ''): string => {
|
||||
return (
|
||||
serviceList.find((service) => service.name === serviceName)
|
||||
?.serviceType || ''
|
||||
);
|
||||
};
|
||||
|
||||
const getSearchedIngestions = useCallback(() => {
|
||||
const sText = lowerCase(searchText);
|
||||
|
||||
return sText
|
||||
? ingestionList.filter(
|
||||
(ing) =>
|
||||
lowerCase(ing.displayName).includes(sText) ||
|
||||
lowerCase(ing.name).includes(sText)
|
||||
)
|
||||
: ingestionList;
|
||||
}, [searchText, ingestionList]);
|
||||
|
||||
const getStatuses = (ingestion: IngestionData) => {
|
||||
const lastFiveIngestions = ingestion.ingestionStatuses
|
||||
?.sort((a, b) => {
|
||||
// Turn your strings into millis, and then subtract them
|
||||
// to get a value that is either negative, positive, or zero.
|
||||
const date1 = new Date(a.startDate);
|
||||
const date2 = new Date(b.startDate);
|
||||
|
||||
return date1.getTime() - date2.getTime();
|
||||
})
|
||||
.slice(Math.max(ingestion.ingestionStatuses.length - 5, 0));
|
||||
|
||||
return lastFiveIngestions?.map((r, i) => {
|
||||
return (
|
||||
<PopOver
|
||||
html={
|
||||
<div className="tw-text-left">
|
||||
{r.startDate ? (
|
||||
<p>Start Date: {new Date(r.startDate).toUTCString()}</p>
|
||||
) : null}
|
||||
{r.endDate ? (
|
||||
<p>End Date: {new Date(r.endDate).toUTCString()}</p>
|
||||
) : null}
|
||||
</div>
|
||||
}
|
||||
key={i}
|
||||
position="bottom"
|
||||
theme="light"
|
||||
trigger="mouseenter">
|
||||
{i === lastFiveIngestions.length - 1 ? (
|
||||
<p
|
||||
className={`tw-h-5 tw-w-16 tw-rounded-sm tw-bg-status-${r.state} tw-mr-1 tw-px-1 tw-text-white tw-text-center`}>
|
||||
{capitalize(r.state)}
|
||||
</p>
|
||||
) : (
|
||||
<p
|
||||
className={`tw-w-4 tw-h-5 tw-rounded-sm tw-bg-status-${r.state} tw-mr-1`}
|
||||
/>
|
||||
)}
|
||||
</PopOver>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<PageContainer className="tw-bg-white">
|
||||
<div className="tw-px-4">
|
||||
<div className="tw-flex">
|
||||
<div className="tw-w-4/12">
|
||||
{searchText || getSearchedIngestions().length > 0 ? (
|
||||
<Searchbar
|
||||
placeholder="Search for ingestion..."
|
||||
searchValue={searchText}
|
||||
typingInterval={500}
|
||||
onSearch={handleSearchAction}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="tw-w-8/12 tw-flex tw-justify-end">
|
||||
<NonAdminAction
|
||||
position="bottom"
|
||||
title={TITLE_FOR_NON_ADMIN_ACTION}>
|
||||
<Button
|
||||
className={classNames('tw-h-8 tw-rounded tw-mb-2', {
|
||||
'tw-opacity-40': !isAdminUser && !isAuthDisabled,
|
||||
})}
|
||||
data-testid="add-new-user-button"
|
||||
size="small"
|
||||
theme="primary"
|
||||
variant="contained"
|
||||
onClick={() => setIsAdding(true)}>
|
||||
Add Ingestion
|
||||
</Button>
|
||||
</NonAdminAction>
|
||||
</div>
|
||||
</div>
|
||||
{getSearchedIngestions().length ? (
|
||||
<div className="tw-table-responsive tw-my-6">
|
||||
<table className="tw-w-full" data-testid="ingestion-table">
|
||||
<thead>
|
||||
<tr className="tableHead-row">
|
||||
<th className="tableHead-cell">Name</th>
|
||||
<th className="tableHead-cell">Type</th>
|
||||
<th className="tableHead-cell">Service</th>
|
||||
<th className="tableHead-cell">Schedule</th>
|
||||
<th className="tableHead-cell">Recent Runs</th>
|
||||
{/* <th className="tableHead-cell">Next Run</th> */}
|
||||
<th className="tableHead-cell">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="tableBody">
|
||||
{getSearchedIngestions().map((ingestion, index) => (
|
||||
<tr
|
||||
className={classNames(
|
||||
'tableBody-row',
|
||||
!isEven(index + 1) ? 'odd-row' : null
|
||||
)}
|
||||
key={index}>
|
||||
<td className="tableBody-cell">{ingestion.displayName}</td>
|
||||
<td className="tableBody-cell">
|
||||
{ingestion.ingestionType}
|
||||
</td>
|
||||
<td className="tableBody-cell">
|
||||
<Link
|
||||
to={getServiceDetailsPath(
|
||||
ingestion.service.name as string,
|
||||
getServiceTypeFromName(ingestion.service.name)
|
||||
)}>
|
||||
{ingestion.service.name}
|
||||
</Link>
|
||||
</td>
|
||||
<td className="tableBody-cell">
|
||||
<PopOver
|
||||
html={
|
||||
<div>
|
||||
{cronstrue.toString(
|
||||
ingestion.scheduleInterval || '',
|
||||
{
|
||||
use24HourTimeFormat: true,
|
||||
verbose: true,
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
position="bottom"
|
||||
theme="light"
|
||||
trigger="mouseenter">
|
||||
<span>{ingestion.scheduleInterval}</span>
|
||||
</PopOver>
|
||||
</td>
|
||||
<td className="tableBody-cell">
|
||||
<div className="tw-flex">{getStatuses(ingestion)}</div>
|
||||
</td>
|
||||
{/* <td className="tableBody-cell">
|
||||
{ingestion.nextExecutionDate || '--'}
|
||||
</td> */}
|
||||
<td className="tableBody-cell">
|
||||
<NonAdminAction
|
||||
position="bottom"
|
||||
title={TITLE_FOR_NON_ADMIN_ACTION}>
|
||||
<div className="tw-flex">
|
||||
<div
|
||||
className="link-text tw-mr-2"
|
||||
onClick={() =>
|
||||
handleTriggerIngestion(
|
||||
ingestion.id as string,
|
||||
ingestion.displayName
|
||||
)
|
||||
}>
|
||||
{currTriggerId.id === ingestion.id ? (
|
||||
currTriggerId.state === 'success' ? (
|
||||
<i aria-hidden="true" className="fa fa-check" />
|
||||
) : (
|
||||
<Loader size="small" type="default" />
|
||||
)
|
||||
) : (
|
||||
'Run'
|
||||
)}
|
||||
</div>
|
||||
<p
|
||||
className="link-text tw-mr-2"
|
||||
onClick={() => handleUpdate(ingestion)}>
|
||||
{updateSelection.id === ingestion.id ? (
|
||||
updateSelection.state === 'success' ? (
|
||||
<i aria-hidden="true" className="fa fa-check" />
|
||||
) : (
|
||||
<Loader size="small" type="default" />
|
||||
)
|
||||
) : (
|
||||
'Edit'
|
||||
)}
|
||||
</p>
|
||||
<div
|
||||
className="link-text tw-mr-2"
|
||||
onClick={() =>
|
||||
ConfirmDelete(
|
||||
ingestion.id as string,
|
||||
ingestion.displayName
|
||||
)
|
||||
}>
|
||||
{deleteSelection.id === ingestion.id ? (
|
||||
deleteSelection.state === 'success' ? (
|
||||
<i aria-hidden="true" className="fa fa-check" />
|
||||
) : (
|
||||
<Loader size="small" type="default" />
|
||||
)
|
||||
) : (
|
||||
'Delete'
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</NonAdminAction>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
{Boolean(!isNil(paging.after) || !isNil(paging.before)) && (
|
||||
<NextPrevious paging={paging} pagingHandler={pagingHandler} />
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="tw-flex tw-items-center tw-flex-col">
|
||||
<div className="tw-mt-24">
|
||||
<img alt="No Service" src={NoDataFoundPlaceHolder} width={250} />
|
||||
</div>
|
||||
<div className="tw-mt-11">
|
||||
<p className="tw-text-lg tw-text-center">
|
||||
{`No ingestion workflows found ${
|
||||
searchText ? `for "${searchText}"` : ''
|
||||
}`}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{isAdding ? (
|
||||
<IngestionModal
|
||||
addIngestion={(data, triggerIngestion) => {
|
||||
setIsAdding(false);
|
||||
addIngestion(data, triggerIngestion);
|
||||
}}
|
||||
header="Add Ingestion"
|
||||
ingestionList={ingestionList}
|
||||
name=""
|
||||
service=""
|
||||
serviceList={serviceList.map((s) => ({
|
||||
name: s.name,
|
||||
serviceType: s.serviceType,
|
||||
}))}
|
||||
type=""
|
||||
onCancel={() => setIsAdding(false)}
|
||||
/>
|
||||
) : null}
|
||||
{isUpdating ? (
|
||||
<IngestionModal
|
||||
isUpdating
|
||||
header={<p>{`Edit ${updateSelection.name}`}</p>}
|
||||
ingestionList={ingestionList}
|
||||
selectedIngestion={updateSelection.ingestion}
|
||||
serviceList={serviceList.map((s) => ({
|
||||
name: s.name,
|
||||
serviceType: s.serviceType,
|
||||
}))}
|
||||
updateIngestion={(data, triggerIngestion) => {
|
||||
setIsUpdating(false);
|
||||
handleUpdateIngestion(data, triggerIngestion);
|
||||
}}
|
||||
onCancel={() => handleCancelUpdate()}
|
||||
/>
|
||||
) : null}
|
||||
{isConfirmationModalOpen && (
|
||||
<ConfirmationModal
|
||||
bodyText={`You want to delete ingestion ${deleteSelection.name} permanently? This action cannot be reverted.`}
|
||||
cancelText="Discard"
|
||||
confirmButtonCss="tw-bg-error hover:tw-bg-error focus:tw-bg-error"
|
||||
confirmText={
|
||||
deleteSelection.state === 'waiting' ? (
|
||||
<Loader size="small" type="white" />
|
||||
) : deleteSelection.state === 'success' ? (
|
||||
<i aria-hidden="true" className="fa fa-check" />
|
||||
) : (
|
||||
'Delete'
|
||||
)
|
||||
}
|
||||
header="Are you sure?"
|
||||
onCancel={handleCancelConfirmationModal}
|
||||
onConfirm={() =>
|
||||
handleDelete(deleteSelection.id, deleteSelection.name)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</PageContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default Ingestion;
|
||||
@ -0,0 +1,52 @@
|
||||
import { Operation } from 'fast-json-patch';
|
||||
import { Paging } from 'Models';
|
||||
import { IngestionType } from '../../enums/service.enum';
|
||||
import { DatabaseService } from '../../generated/entity/services/databaseService';
|
||||
import { EntityReference } from '../../generated/type/entityReference';
|
||||
|
||||
export interface ConnectorConfig {
|
||||
username: string;
|
||||
password: string;
|
||||
host: string;
|
||||
database: string;
|
||||
includeFilterPattern: Array<string>;
|
||||
excludeFilterPattern: Array<string>;
|
||||
includeViews: boolean;
|
||||
excludeDataProfiler?: boolean;
|
||||
enableDataProfiler?: boolean;
|
||||
}
|
||||
export interface IngestionData {
|
||||
id?: string;
|
||||
name: string;
|
||||
displayName: string;
|
||||
ingestionType: IngestionType;
|
||||
service: EntityReference;
|
||||
scheduleInterval: string;
|
||||
ingestionStatuses?: Array<{
|
||||
state: string;
|
||||
startDate: string;
|
||||
endDate: string;
|
||||
}>;
|
||||
nextExecutionDate?: string;
|
||||
connectorConfig?: ConnectorConfig;
|
||||
forceDeploy?: boolean;
|
||||
owner?: { id: string; name?: string; type: string };
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
}
|
||||
|
||||
export interface Props {
|
||||
paging: Paging;
|
||||
ingestionList: Array<IngestionData>;
|
||||
serviceList: Array<DatabaseService>;
|
||||
pagingHandler: (value: string) => void;
|
||||
deleteIngestion: (id: string, displayName: string) => Promise<void>;
|
||||
triggerIngestion: (id: string, displayName: string) => Promise<void>;
|
||||
addIngestion: (data: IngestionData, triggerIngestion?: boolean) => void;
|
||||
updateIngestion: (
|
||||
id: string,
|
||||
displayName: string,
|
||||
patch: Array<Operation>,
|
||||
triggerIngestion?: boolean
|
||||
) => Promise<void>;
|
||||
}
|
||||
@ -0,0 +1,715 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import classNames from 'classnames';
|
||||
import cronstrue from 'cronstrue';
|
||||
import React, { Fragment, useEffect, useState } from 'react';
|
||||
import { CronError } from 'react-js-cron';
|
||||
import { IngestionType } from '../../enums/service.enum';
|
||||
import { getIngestionTypeList } from '../../utils/ServiceUtils';
|
||||
import SVGIcons from '../../utils/SvgUtils';
|
||||
import { Button } from '../buttons/Button/Button';
|
||||
import CronEditor from '../common/CronEditor/CronEditor.component';
|
||||
import IngestionStepper from '../IngestionStepper/IngestionStepper.component';
|
||||
import { Steps } from '../IngestionStepper/IngestionStepper.interface';
|
||||
import './IngestionModal.css';
|
||||
import {
|
||||
IngestionModalProps,
|
||||
ServiceData,
|
||||
ValidationErrorMsg,
|
||||
} from './IngestionModal.interface';
|
||||
|
||||
const errorMsg = (value: string) => {
|
||||
return (
|
||||
<div className="tw-mt-1">
|
||||
<strong className="tw-text-red-500 tw-text-xs tw-italic">{value}</strong>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const STEPS: Array<Steps> = [
|
||||
{ name: 'Ingestion details', step: 1 },
|
||||
{ name: 'Connector config', step: 2 },
|
||||
{ name: 'Scheduling', step: 3 },
|
||||
{ name: 'Review and Deploy', step: 4 },
|
||||
];
|
||||
|
||||
const requiredField = (label: string) => (
|
||||
<>
|
||||
{label} <span className="tw-text-red-500"> *</span>
|
||||
</>
|
||||
);
|
||||
|
||||
const Field = ({ children }: { children: React.ReactNode }) => {
|
||||
return <div className="tw-mt-6">{children}</div>;
|
||||
};
|
||||
|
||||
const PreviewSection = ({
|
||||
header,
|
||||
data,
|
||||
className,
|
||||
}: {
|
||||
header: string;
|
||||
data: Array<{ key: string; value: string }>;
|
||||
className: string;
|
||||
}) => {
|
||||
return (
|
||||
<div className={className}>
|
||||
{/* <hr className="tw-border-separator" /> */}
|
||||
<p className="preview-header tw-px-1">{header}</p>
|
||||
<div className="tw-grid tw-gap-4 tw-grid-cols-3 tw-place-content-center tw-pl-6">
|
||||
{data.map((d, i) => (
|
||||
<div key={i}>
|
||||
<p className="tw-text-xs tw-font-normal tw-text-grey-muted">
|
||||
{d.key}
|
||||
</p>
|
||||
<p>{d.value}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const getServiceName = (service: string) => {
|
||||
return service.split('$$').splice(1).join('$$');
|
||||
};
|
||||
|
||||
const getIngestionName = (name: string) => {
|
||||
const nameString = name.trim().replace(/\s+/g, '_');
|
||||
|
||||
return nameString.toLowerCase();
|
||||
};
|
||||
|
||||
const getCurrentDate = () => {
|
||||
return `${new Date().toLocaleDateString('en-CA')}`;
|
||||
};
|
||||
|
||||
const setService = (
|
||||
serviceList: Array<ServiceData>,
|
||||
currentservice: string
|
||||
) => {
|
||||
const service = serviceList.find((s) => s.name === currentservice);
|
||||
|
||||
return service ? `${service?.serviceType}$$${service?.name}` : '';
|
||||
};
|
||||
|
||||
const IngestionModal: React.FC<IngestionModalProps> = ({
|
||||
isUpdating,
|
||||
header,
|
||||
serviceList = [], // TODO: remove default assignment after resolving prop validation warning
|
||||
ingestionList,
|
||||
onCancel,
|
||||
addIngestion,
|
||||
updateIngestion,
|
||||
selectedIngestion,
|
||||
}: IngestionModalProps) => {
|
||||
const [activeStep, setActiveStep] = useState<number>(1);
|
||||
|
||||
const [startDate, setStartDate] = useState<string>(
|
||||
selectedIngestion?.startDate || getCurrentDate()
|
||||
);
|
||||
const [endDate, setEndDate] = useState<string>(
|
||||
selectedIngestion?.endDate || ''
|
||||
);
|
||||
|
||||
const [ingestionName, setIngestionName] = useState<string>(
|
||||
selectedIngestion?.displayName || ''
|
||||
);
|
||||
const [ingestionType, setIngestionType] = useState<string>(
|
||||
selectedIngestion?.ingestionType || ''
|
||||
);
|
||||
const [ingestionService, setIngestionService] = useState<string>(
|
||||
setService(serviceList, selectedIngestion?.service?.name as string) || ''
|
||||
);
|
||||
|
||||
const [username, setUsername] = useState<string>(
|
||||
selectedIngestion?.connectorConfig?.username || ''
|
||||
);
|
||||
const [password, setPassword] = useState<string>(
|
||||
selectedIngestion?.connectorConfig?.password || ''
|
||||
);
|
||||
const [host, setHost] = useState<string>(
|
||||
selectedIngestion?.connectorConfig?.host || ''
|
||||
);
|
||||
const [database, setDatabase] = useState<string>(
|
||||
selectedIngestion?.connectorConfig?.database || ''
|
||||
);
|
||||
const [includeFilterPattern, setIncludeFilterPattern] = useState<
|
||||
Array<string>
|
||||
>(selectedIngestion?.connectorConfig?.includeFilterPattern || []);
|
||||
const [excludeFilterPattern, setExcludeFilterPattern] = useState<
|
||||
Array<string>
|
||||
>(selectedIngestion?.connectorConfig?.excludeFilterPattern || []);
|
||||
const [includeViews, setIncludeViews] = useState<boolean>(
|
||||
selectedIngestion?.connectorConfig?.includeViews || true
|
||||
);
|
||||
const [excludeDataProfiler, setExcludeDataProfiler] = useState<boolean>(
|
||||
selectedIngestion?.connectorConfig?.enableDataProfiler || false
|
||||
);
|
||||
|
||||
const [ingestionSchedule, setIngestionSchedule] = useState<string>(
|
||||
selectedIngestion?.scheduleInterval || '*/5 * * * *'
|
||||
);
|
||||
const [cronError, setCronError] = useState<CronError>();
|
||||
|
||||
const [showErrorMsg, setShowErrorMsg] = useState<ValidationErrorMsg>({
|
||||
selectService: false,
|
||||
name: false,
|
||||
username: false,
|
||||
password: false,
|
||||
ingestionType: false,
|
||||
host: false,
|
||||
database: false,
|
||||
ingestionSchedule: false,
|
||||
isPipelineExists: false,
|
||||
isPipelineNameExists: false,
|
||||
});
|
||||
|
||||
const isPipelineExists = () => {
|
||||
return ingestionList.some(
|
||||
(i) =>
|
||||
i.service.name === getServiceName(ingestionService) &&
|
||||
i.ingestionType === ingestionType &&
|
||||
i.service.name !== selectedIngestion?.name &&
|
||||
i.service.displayName === selectedIngestion?.ingestionType
|
||||
);
|
||||
};
|
||||
|
||||
const isPipeLineNameExists = () => {
|
||||
return ingestionList.some(
|
||||
(i) =>
|
||||
i.name === getIngestionName(ingestionName) &&
|
||||
i.name !== selectedIngestion?.name
|
||||
);
|
||||
};
|
||||
|
||||
const handleValidation = (
|
||||
event: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>
|
||||
) => {
|
||||
const value = event.target.value;
|
||||
const name = event.target.name;
|
||||
|
||||
switch (name) {
|
||||
case 'name':
|
||||
setIngestionName(value);
|
||||
|
||||
break;
|
||||
case 'selectService':
|
||||
setIngestionService(value);
|
||||
setIngestionType('');
|
||||
|
||||
break;
|
||||
case 'ingestionType':
|
||||
setIngestionType(value);
|
||||
|
||||
break;
|
||||
case 'username':
|
||||
setUsername(value);
|
||||
|
||||
break;
|
||||
case 'password':
|
||||
setPassword(value);
|
||||
|
||||
break;
|
||||
case 'host':
|
||||
setHost(value);
|
||||
|
||||
break;
|
||||
case 'database':
|
||||
setDatabase(value);
|
||||
|
||||
break;
|
||||
case 'ingestionSchedule':
|
||||
setIngestionSchedule(value);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
setShowErrorMsg({
|
||||
...showErrorMsg,
|
||||
[name]: !value,
|
||||
});
|
||||
};
|
||||
|
||||
const forwardStepHandler = (activeStep: number) => {
|
||||
let isValid = false;
|
||||
switch (activeStep) {
|
||||
case 1:
|
||||
isValid = Boolean(
|
||||
ingestionName &&
|
||||
ingestionService &&
|
||||
ingestionType &&
|
||||
!isPipelineExists()
|
||||
);
|
||||
setShowErrorMsg({
|
||||
...showErrorMsg,
|
||||
name: !ingestionName,
|
||||
ingestionType: !ingestionType,
|
||||
selectService: !ingestionService,
|
||||
});
|
||||
|
||||
break;
|
||||
case 2:
|
||||
isValid = Boolean(username && password && host && database);
|
||||
setShowErrorMsg({
|
||||
...showErrorMsg,
|
||||
username: !username,
|
||||
password: !password,
|
||||
host: !host,
|
||||
database: !database,
|
||||
});
|
||||
|
||||
break;
|
||||
case 3:
|
||||
isValid = Boolean(ingestionSchedule && !cronError);
|
||||
setShowErrorMsg({
|
||||
...showErrorMsg,
|
||||
ingestionSchedule: !ingestionSchedule,
|
||||
});
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
setActiveStep((pre) => (pre < STEPS.length && isValid ? pre + 1 : pre));
|
||||
};
|
||||
|
||||
const getActiveStepFields = (activeStep: number) => {
|
||||
switch (activeStep) {
|
||||
case 1:
|
||||
return (
|
||||
<Fragment>
|
||||
<Field>
|
||||
<label className="tw-block" htmlFor="name">
|
||||
{requiredField('Name:')}
|
||||
</label>
|
||||
<input
|
||||
className={classNames('tw-form-inputs tw-px-3 tw-py-1', {
|
||||
'tw-cursor-not-allowed': isUpdating,
|
||||
})}
|
||||
id="name"
|
||||
name="name"
|
||||
placeholder="Ingestion name"
|
||||
readOnly={isUpdating}
|
||||
type="text"
|
||||
value={ingestionName}
|
||||
onChange={handleValidation}
|
||||
/>
|
||||
{showErrorMsg.name && errorMsg('Ingestion Name is required')}
|
||||
{showErrorMsg.isPipelineNameExists &&
|
||||
errorMsg(`Ingestion with similar name already exists.`)}
|
||||
</Field>
|
||||
|
||||
<Field>
|
||||
<label className="tw-block" htmlFor="selectService">
|
||||
{requiredField('Select Service:')}
|
||||
</label>
|
||||
<select
|
||||
className={classNames('tw-form-inputs tw-px-3 tw-py-1', {
|
||||
'tw-cursor-not-allowed': isUpdating,
|
||||
})}
|
||||
data-testid="selectService"
|
||||
disabled={isUpdating}
|
||||
id="selectService"
|
||||
name="selectService"
|
||||
value={ingestionService}
|
||||
onChange={handleValidation}>
|
||||
<option value="">Select Service</option>
|
||||
{serviceList.map((service, index) => (
|
||||
<option
|
||||
key={index}
|
||||
value={`${service.serviceType}$$${service.name}`}>
|
||||
{service.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
{showErrorMsg.selectService && errorMsg('Service is required')}
|
||||
</Field>
|
||||
<Field>
|
||||
<label className="tw-block " htmlFor="ingestionType">
|
||||
{requiredField('Type of ingestion:')}
|
||||
</label>
|
||||
<select
|
||||
className={classNames('tw-form-inputs tw-px-3 tw-py-1', {
|
||||
'tw-cursor-not-allowed': !ingestionService,
|
||||
})}
|
||||
data-testid="selectService"
|
||||
disabled={!ingestionService || isUpdating}
|
||||
id="ingestionType"
|
||||
name="ingestionType"
|
||||
value={ingestionType}
|
||||
onChange={handleValidation}>
|
||||
<option value="">Select ingestion type</option>
|
||||
{(
|
||||
getIngestionTypeList(ingestionService?.split('$$')?.[0]) || []
|
||||
).map((service, index) => (
|
||||
<option key={index} value={service}>
|
||||
{service}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
{showErrorMsg.ingestionType &&
|
||||
errorMsg('Ingestion Type is required')}
|
||||
{showErrorMsg.isPipelineExists &&
|
||||
errorMsg(
|
||||
`Ingestion with service ${getServiceName(
|
||||
ingestionService
|
||||
)} and ingestion-type ${ingestionType} already exists `
|
||||
)}
|
||||
</Field>
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
case 2:
|
||||
return (
|
||||
<Fragment>
|
||||
<Field>
|
||||
<label className="tw-block" htmlFor="username">
|
||||
{requiredField('Username:')}
|
||||
</label>
|
||||
<input
|
||||
className="tw-form-inputs tw-px-3 tw-py-1"
|
||||
id="username"
|
||||
name="username"
|
||||
placeholder="User name"
|
||||
type="text"
|
||||
value={username}
|
||||
onChange={handleValidation}
|
||||
/>
|
||||
{showErrorMsg.username && errorMsg('Username is required')}
|
||||
</Field>
|
||||
<Field>
|
||||
<label className="tw-block" htmlFor="password">
|
||||
{requiredField('Password:')}
|
||||
</label>
|
||||
<input
|
||||
className="tw-form-inputs tw-px-3 tw-py-1"
|
||||
id="password"
|
||||
name="password"
|
||||
placeholder="Password"
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={handleValidation}
|
||||
/>
|
||||
{showErrorMsg.password && errorMsg('Password is required')}
|
||||
</Field>
|
||||
<Field>
|
||||
<label className="tw-block" htmlFor="host">
|
||||
{requiredField('Host:')}
|
||||
</label>
|
||||
<input
|
||||
className="tw-form-inputs tw-px-3 tw-py-1"
|
||||
id="host"
|
||||
name="host"
|
||||
placeholder="Host"
|
||||
type="text"
|
||||
value={host}
|
||||
onChange={handleValidation}
|
||||
/>
|
||||
{showErrorMsg.host && errorMsg('Host is required')}
|
||||
</Field>
|
||||
<Field>
|
||||
<label className="tw-block" htmlFor="database">
|
||||
{requiredField('Database:')}
|
||||
</label>
|
||||
<input
|
||||
className="tw-form-inputs tw-px-3 tw-py-1"
|
||||
id="database"
|
||||
name="database"
|
||||
placeholder="Database"
|
||||
type="text"
|
||||
value={database}
|
||||
onChange={handleValidation}
|
||||
/>
|
||||
{showErrorMsg.database && errorMsg('Database is required')}
|
||||
</Field>
|
||||
<Field>
|
||||
<label className="tw-block" htmlFor="includeFilterPattern">
|
||||
Include Filter Patterns:
|
||||
</label>
|
||||
<input
|
||||
className="tw-form-inputs tw-px-3 tw-py-1"
|
||||
id="includeFilterPattern"
|
||||
name="includeFilterPattern"
|
||||
placeholder="Include filter patterns comma seperated"
|
||||
type="text"
|
||||
value={includeFilterPattern}
|
||||
onChange={(e) => setIncludeFilterPattern([e.target.value])}
|
||||
/>
|
||||
</Field>
|
||||
<Field>
|
||||
<label className="tw-block" htmlFor="excludeFilterPattern">
|
||||
Exclude Filter Patterns:
|
||||
</label>
|
||||
<input
|
||||
className="tw-form-inputs tw-px-3 tw-py-1"
|
||||
id="excludeFilterPattern"
|
||||
name="excludeFilterPattern"
|
||||
placeholder="Exclude filter patterns comma seperated"
|
||||
type="text"
|
||||
value={excludeFilterPattern}
|
||||
onChange={(e) => setExcludeFilterPattern([e.target.value])}
|
||||
/>
|
||||
</Field>
|
||||
<Field>
|
||||
<div className="tw-flex tw-justify-between">
|
||||
<Fragment>
|
||||
<label>Include views:</label>
|
||||
<div
|
||||
className={classNames(
|
||||
'toggle-switch',
|
||||
includeViews ? 'open' : null
|
||||
)}
|
||||
onClick={() => setIncludeViews(!includeViews)}>
|
||||
<div className="switch" />
|
||||
</div>
|
||||
</Fragment>
|
||||
<Fragment>
|
||||
<label>Enable data profiler:</label>
|
||||
<div
|
||||
className={classNames(
|
||||
'toggle-switch',
|
||||
excludeDataProfiler ? 'open' : null
|
||||
)}
|
||||
onClick={() =>
|
||||
setExcludeDataProfiler(!excludeDataProfiler)
|
||||
}>
|
||||
<div className="switch" />
|
||||
</div>
|
||||
</Fragment>
|
||||
</div>
|
||||
</Field>
|
||||
</Fragment>
|
||||
);
|
||||
case 3:
|
||||
return (
|
||||
<Fragment>
|
||||
<div className="tw-mt-4">
|
||||
<label htmlFor="">{requiredField('Schedule interval:')}</label>
|
||||
<div className="tw-flex tw-justify-items-start tw-mt-2">
|
||||
<CronEditor
|
||||
defaultValue={ingestionSchedule}
|
||||
onChangeHandler={(v) => setIngestionSchedule(v)}
|
||||
onError={setCronError}>
|
||||
<p className="tw-text-grey-muted tw-text-xs tw-mt-1">
|
||||
<span>( </span>
|
||||
<span className="tw-font-normal">{ingestionSchedule}</span>
|
||||
<span> in UTC Timezone ) </span>
|
||||
</p>
|
||||
{showErrorMsg.ingestionSchedule
|
||||
? errorMsg('Ingestion schedule is required')
|
||||
: cronError && errorMsg(cronError.description)}
|
||||
</CronEditor>
|
||||
</div>
|
||||
</div>
|
||||
<Field>
|
||||
<label htmlFor="startDate">Start date:</label>
|
||||
<input
|
||||
className="tw-form-inputs tw-px-3 tw-py-1"
|
||||
pattern="YY-MM-DD"
|
||||
type="date"
|
||||
value={startDate}
|
||||
onChange={(e) => setStartDate(e.target.value)}
|
||||
/>
|
||||
</Field>
|
||||
<Field>
|
||||
<label htmlFor="endDate">End date:</label>
|
||||
<input
|
||||
className="tw-form-inputs tw-px-3 tw-py-1"
|
||||
min={startDate}
|
||||
pattern="YY-MM-DD"
|
||||
type="date"
|
||||
value={endDate}
|
||||
onChange={(e) => setEndDate(e.target.value)}
|
||||
/>
|
||||
</Field>
|
||||
</Fragment>
|
||||
);
|
||||
case 4:
|
||||
return (
|
||||
<Fragment>
|
||||
<div className="tw-flex tw-flex-col tw-mt-6">
|
||||
<PreviewSection
|
||||
className="tw-mb-4 tw-mt-4"
|
||||
data={[
|
||||
{ key: 'Name', value: ingestionName },
|
||||
{
|
||||
key: 'Service Type',
|
||||
value: getServiceName(ingestionService),
|
||||
},
|
||||
{ key: 'Ingestion Type', value: ingestionType },
|
||||
]}
|
||||
header="Ingestion Details"
|
||||
/>
|
||||
<PreviewSection
|
||||
className="tw-mb-4 tw-mt-6"
|
||||
data={[
|
||||
{ key: 'Username', value: username },
|
||||
{ key: 'Password', value: password },
|
||||
{ key: 'Host', value: host },
|
||||
{ key: 'Database', value: database },
|
||||
{ key: 'Include views', value: includeViews ? 'Yes' : 'No' },
|
||||
{
|
||||
key: 'Enable Data Profiler',
|
||||
value: excludeDataProfiler ? 'Yes' : 'No',
|
||||
},
|
||||
]}
|
||||
header="Connector Config"
|
||||
/>
|
||||
<PreviewSection
|
||||
className="tw-mt-6"
|
||||
data={[]}
|
||||
header="Scheduling"
|
||||
/>
|
||||
<p className="tw-pl-6">
|
||||
{cronstrue.toString(ingestionSchedule || '', {
|
||||
use24HourTimeFormat: true,
|
||||
verbose: true,
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const onSaveHandler = (triggerIngestion = false) => {
|
||||
const ingestionData = {
|
||||
ingestionType: ingestionType as IngestionType,
|
||||
displayName: ingestionName,
|
||||
name: getIngestionName(ingestionName),
|
||||
service: { name: getServiceName(ingestionService), id: '', type: '' },
|
||||
startDate: startDate || getCurrentDate(),
|
||||
endDate: endDate || '',
|
||||
scheduleInterval: ingestionSchedule,
|
||||
forceDeploy: true,
|
||||
connectorConfig: {
|
||||
database: database,
|
||||
enableDataProfiler: excludeDataProfiler,
|
||||
excludeFilterPattern: excludeFilterPattern,
|
||||
host: host,
|
||||
includeFilterPattern: includeFilterPattern,
|
||||
includeViews: includeViews,
|
||||
password: password,
|
||||
username: username,
|
||||
},
|
||||
};
|
||||
addIngestion?.(ingestionData, triggerIngestion);
|
||||
updateIngestion?.(ingestionData, triggerIngestion);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setShowErrorMsg({
|
||||
...showErrorMsg,
|
||||
isPipelineExists: isPipelineExists(),
|
||||
isPipelineNameExists: isPipeLineNameExists(),
|
||||
});
|
||||
}, [ingestionType, ingestionService, ingestionName]);
|
||||
|
||||
useEffect(() => {
|
||||
if (endDate) {
|
||||
const startDt = new Date(startDate);
|
||||
const endDt = new Date(endDate);
|
||||
if (endDt.getTime() < startDt.getTime()) {
|
||||
setEndDate('');
|
||||
}
|
||||
}
|
||||
}, [startDate]);
|
||||
|
||||
return (
|
||||
<dialog className="tw-modal" data-testid="service-modal">
|
||||
<div className="tw-modal-backdrop" />
|
||||
<div className="tw-modal-container tw-max-w-2xl">
|
||||
<div className="tw-modal-header">
|
||||
<p className="tw-modal-title">{header}</p>
|
||||
<div className="tw-flex">
|
||||
<svg
|
||||
className="tw-w-6 tw-h-6 tw-ml-1 tw-cursor-pointer"
|
||||
data-testid="closeWhatsNew"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
onClick={onCancel}>
|
||||
<path
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div className="tw-modal-body">
|
||||
<IngestionStepper activeStep={activeStep} steps={STEPS} />
|
||||
|
||||
<form className="tw-min-w-full" data-testid="form">
|
||||
<div className="tw-px-4">{getActiveStepFields(activeStep)}</div>
|
||||
</form>
|
||||
</div>
|
||||
<div className="tw-modal-footer tw-justify-between">
|
||||
<Button
|
||||
className={classNames('tw-mr-2', {
|
||||
'tw-invisible': activeStep === 1,
|
||||
})}
|
||||
data-testid="cancel"
|
||||
size="regular"
|
||||
theme="primary"
|
||||
variant="text"
|
||||
onClick={() => setActiveStep((pre) => (pre > 1 ? pre - 1 : pre))}>
|
||||
<i className="fas fa-arrow-left tw-text-sm tw-align-middle tw-pr-1.5" />{' '}
|
||||
<span>Previous</span>
|
||||
</Button>
|
||||
|
||||
{activeStep === 4 ? (
|
||||
<div className="tw-flex">
|
||||
<Button
|
||||
data-testid="save-button"
|
||||
size="regular"
|
||||
theme="primary"
|
||||
type="submit"
|
||||
variant="contained"
|
||||
onClick={() => onSaveHandler()}>
|
||||
<span className="tw-mr-2">Deploy</span>
|
||||
<SVGIcons alt="Deploy" icon="icon-deploy" />
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<Button
|
||||
data-testid="next-button"
|
||||
size="regular"
|
||||
theme="primary"
|
||||
variant="contained"
|
||||
onClick={() => forwardStepHandler(activeStep)}>
|
||||
<span>Next</span>
|
||||
<i className="fas fa-arrow-right tw-text-sm tw-align-middle tw-pl-1.5" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default IngestionModal;
|
||||
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
.preview-header {
|
||||
transform: translateY(-13px);
|
||||
font-size: 14px;
|
||||
display: inline-block;
|
||||
background: white;
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
ConnectorConfig,
|
||||
IngestionData,
|
||||
} from '../Ingestion/ingestion.interface';
|
||||
|
||||
export interface ServiceData {
|
||||
serviceType: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface IngestionModalProps {
|
||||
isUpdating?: boolean;
|
||||
ingestionList: Array<IngestionData>;
|
||||
header: string | React.ReactNode;
|
||||
name?: string;
|
||||
service?: string;
|
||||
serviceList: Array<ServiceData>;
|
||||
type?: string;
|
||||
schedule?: string;
|
||||
connectorConfig?: ConnectorConfig;
|
||||
selectedIngestion?: IngestionData;
|
||||
addIngestion?: (data: IngestionData, triggerIngestion?: boolean) => void;
|
||||
updateIngestion?: (data: IngestionData, triggerIngestion?: boolean) => void;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
export interface ValidationErrorMsg {
|
||||
selectService: boolean;
|
||||
name: boolean;
|
||||
username: boolean;
|
||||
password: boolean;
|
||||
ingestionType: boolean;
|
||||
host: boolean;
|
||||
database: boolean;
|
||||
ingestionSchedule: boolean;
|
||||
isPipelineExists: boolean;
|
||||
isPipelineNameExists: boolean;
|
||||
}
|
||||
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import classNames from 'classnames';
|
||||
import React, { Fragment } from 'react';
|
||||
import './IngestionStepper.css';
|
||||
type Props = {
|
||||
steps: Array<{ name: string; step: number }>;
|
||||
activeStep: number;
|
||||
};
|
||||
const IngestionStepper = ({ steps, activeStep }: Props) => {
|
||||
return (
|
||||
<div className="ingestion-content tw-relative">
|
||||
{steps.map((step, index) => (
|
||||
<Fragment key={index}>
|
||||
{index > 0 && index < steps.length && (
|
||||
<span className="ingestion-line" />
|
||||
)}
|
||||
<div className="ingestion-wrapper" key={index}>
|
||||
<span className="tw-flex tw-flex-col">
|
||||
<span
|
||||
className={classNames(
|
||||
'ingestion-rounder tw-self-center',
|
||||
{
|
||||
active: step.step === activeStep,
|
||||
},
|
||||
{ completed: step.step < activeStep }
|
||||
)}
|
||||
/>
|
||||
<span
|
||||
className={classNames('tw-mt-2 tw-text-xs', {
|
||||
'tw-text-primary': step.step <= activeStep,
|
||||
})}>
|
||||
{step.name}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default IngestionStepper;
|
||||
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
.ingestion-content {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
}
|
||||
.ingestion-wrapper {
|
||||
display: flex;
|
||||
}
|
||||
.ingestion-rounder {
|
||||
display: block;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border: 3px solid #d9ceee;
|
||||
background: white;
|
||||
border-radius: 50%;
|
||||
margin-top: 0.15rem;
|
||||
z-index: 100;
|
||||
}
|
||||
.ingestion-rounder.active {
|
||||
border-color: #7147e8;
|
||||
}
|
||||
.ingestion-rounder.completed {
|
||||
background-color: #d9ceee;
|
||||
}
|
||||
|
||||
.ingestion-rounder.completed::after {
|
||||
content: '\2713';
|
||||
display: block;
|
||||
margin-top: -5px;
|
||||
font-weight: 900;
|
||||
color: #7147e8;
|
||||
}
|
||||
|
||||
.ingestion-line::before {
|
||||
top: 10px;
|
||||
bottom: 0;
|
||||
position: absolute;
|
||||
content: ' ';
|
||||
width: 78%;
|
||||
height: 2px;
|
||||
left: 64px;
|
||||
background-color: #d9ceee;
|
||||
}
|
||||
.ingestion-line-se {
|
||||
display: block;
|
||||
width: 2px;
|
||||
height: 15px;
|
||||
background-color: #d9ceee;
|
||||
transform: translate(8px, 0);
|
||||
}
|
||||
@ -0,0 +1,4 @@
|
||||
export type Steps = {
|
||||
name: string;
|
||||
step: number;
|
||||
};
|
||||
@ -15,7 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import classNames from 'classnames';
|
||||
// import classNames from 'classnames';
|
||||
import { ServiceTypes } from 'Models';
|
||||
import React, { FunctionComponent, useEffect, useRef, useState } from 'react';
|
||||
import { serviceTypes } from '../../../constants/services.const';
|
||||
@ -28,14 +28,14 @@ import {
|
||||
import { DatabaseService } from '../../../generated/entity/services/databaseService';
|
||||
import { MessagingService } from '../../../generated/entity/services/messagingService';
|
||||
import { PipelineService } from '../../../generated/entity/services/pipelineService';
|
||||
import { fromISOString } from '../../../utils/ServiceUtils';
|
||||
// import { fromISOString } from '../../../utils/ServiceUtils';
|
||||
import { Button } from '../../buttons/Button/Button';
|
||||
import MarkdownWithPreview from '../../common/editor/MarkdownWithPreview';
|
||||
// import { serviceType } from '../../../constants/services.const';
|
||||
|
||||
export type DataObj = {
|
||||
description: string | undefined;
|
||||
ingestionSchedule:
|
||||
ingestionSchedule?:
|
||||
| {
|
||||
repeatFrequency: string;
|
||||
startDate: string;
|
||||
@ -128,15 +128,15 @@ const requiredField = (label: string) => (
|
||||
</>
|
||||
);
|
||||
|
||||
const generateOptions = (count: number, initialValue = 0) => {
|
||||
return Array(count)
|
||||
.fill(null)
|
||||
.map((_, i) => (
|
||||
<option key={i + initialValue} value={i + initialValue}>
|
||||
{i + initialValue}
|
||||
</option>
|
||||
));
|
||||
};
|
||||
// const generateOptions = (count: number, initialValue = 0) => {
|
||||
// return Array(count)
|
||||
// .fill(null)
|
||||
// .map((_, i) => (
|
||||
// <option key={i + initialValue} value={i + initialValue}>
|
||||
// {i + initialValue}
|
||||
// </option>
|
||||
// ));
|
||||
// };
|
||||
|
||||
const generateName = (data: Array<ServiceDataObj>) => {
|
||||
const newArr: string[] = [];
|
||||
@ -187,7 +187,7 @@ export const AddServiceModal: FunctionComponent<Props> = ({
|
||||
);
|
||||
const [parseUrl] = useState(seprateUrl(data?.jdbc?.connectionUrl) || {});
|
||||
const [existingNames] = useState(generateName(serviceList));
|
||||
const [ingestion, setIngestion] = useState(!!data?.ingestionSchedule);
|
||||
// const [ingestion, setIngestion] = useState(!!data?.ingestionSchedule);
|
||||
const [selectService, setSelectService] = useState(data?.serviceType || '');
|
||||
const [name, setName] = useState(data?.name || '');
|
||||
// const [userName, setUserName] = useState(parseUrl?.userName || '');
|
||||
@ -214,9 +214,9 @@ export const AddServiceModal: FunctionComponent<Props> = ({
|
||||
const [server, setServer] = useState(data?.server || '');
|
||||
const [env, setEnv] = useState(data?.env || '');
|
||||
const [pipelineUrl, setPipelineUrl] = useState(data?.pipelineUrl || '');
|
||||
const [frequency, setFrequency] = useState(
|
||||
fromISOString(data?.ingestionSchedule?.repeatFrequency)
|
||||
);
|
||||
// const [frequency, setFrequency] = useState(
|
||||
// fromISOString(data?.ingestionSchedule?.repeatFrequency)
|
||||
// );
|
||||
const [showErrorMsg, setShowErrorMsg] = useState<ErrorMsg>({
|
||||
selectService: false,
|
||||
name: false,
|
||||
@ -242,13 +242,13 @@ export const AddServiceModal: FunctionComponent<Props> = ({
|
||||
: 'hostname1:port1, hostname2:port2';
|
||||
};
|
||||
|
||||
const handleChangeFrequency = (
|
||||
event: React.ChangeEvent<HTMLSelectElement>
|
||||
) => {
|
||||
const name = event.target.name,
|
||||
value = +event.target.value;
|
||||
setFrequency({ ...frequency, [name]: value });
|
||||
};
|
||||
// const handleChangeFrequency = (
|
||||
// event: React.ChangeEvent<HTMLSelectElement>
|
||||
// ) => {
|
||||
// const name = event.target.name,
|
||||
// value = +event.target.value;
|
||||
// setFrequency({ ...frequency, [name]: value });
|
||||
// };
|
||||
|
||||
const handleValidation = (
|
||||
event: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>
|
||||
@ -420,16 +420,16 @@ export const AddServiceModal: FunctionComponent<Props> = ({
|
||||
}
|
||||
setShowErrorMsg(setMsg);
|
||||
if (onSaveHelper(setMsg)) {
|
||||
const { day, hour, minute } = frequency;
|
||||
const date = new Date();
|
||||
// const { day, hour, minute } = frequency;
|
||||
// const date = new Date();
|
||||
let dataObj: DataObj = {
|
||||
description: markdownRef.current?.getEditorContent(),
|
||||
ingestionSchedule: ingestion
|
||||
? {
|
||||
repeatFrequency: `P${day}DT${hour}H${minute}M`,
|
||||
startDate: date.toISOString(),
|
||||
}
|
||||
: undefined,
|
||||
// ingestionSchedule: ingestion
|
||||
// ? {
|
||||
// repeatFrequency: `P${day}DT${hour}H${minute}M`,
|
||||
// startDate: date.toISOString(),
|
||||
// }
|
||||
// : undefined,
|
||||
name: name,
|
||||
serviceType: selectService,
|
||||
};
|
||||
@ -956,7 +956,7 @@ export const AddServiceModal: FunctionComponent<Props> = ({
|
||||
value={data?.description || ''}
|
||||
/>
|
||||
</div>
|
||||
<div className="tw-mt-4 tw-flex tw-items-center">
|
||||
{/* <div className="tw-mt-4 tw-flex tw-items-center">
|
||||
<label className="tw-form-label tw-mb-0">Enable Ingestion</label>
|
||||
<div
|
||||
className={classNames(
|
||||
@ -967,8 +967,8 @@ export const AddServiceModal: FunctionComponent<Props> = ({
|
||||
onClick={() => setIngestion(!ingestion)}>
|
||||
<div className="switch" />
|
||||
</div>
|
||||
</div>
|
||||
{ingestion && (
|
||||
</div> */}
|
||||
{/* {ingestion && (
|
||||
<div className="tw-grid tw-grid-cols-3 tw-gap-2 tw-gap-y-0 tw-mt-4">
|
||||
<div className="tw-col-span-3">
|
||||
<label className="tw-block tw-form-label" htmlFor="frequency">
|
||||
@ -1024,7 +1024,7 @@ export const AddServiceModal: FunctionComponent<Props> = ({
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
)} */}
|
||||
</form>
|
||||
</div>
|
||||
<div className="tw-modal-footer tw-justify-end">
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
import classNames from 'classnames';
|
||||
import React from 'react';
|
||||
import { ReactNode } from 'react-markdown';
|
||||
import { Button } from '../../buttons/Button/Button';
|
||||
type Props = {
|
||||
cancelText: string;
|
||||
confirmText: string;
|
||||
confirmText: string | ReactNode;
|
||||
bodyText: string;
|
||||
header: string;
|
||||
headerClassName?: string;
|
||||
|
||||
@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import 'antd/dist/antd.css';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import Cron, { CronError } from 'react-js-cron';
|
||||
|
||||
type Props = {
|
||||
defaultValue: string;
|
||||
onChangeHandler?: (v: string) => void;
|
||||
children?: React.ReactNode;
|
||||
isReadOnly?: boolean;
|
||||
className?: string;
|
||||
onError?: (v: CronError) => void;
|
||||
};
|
||||
|
||||
const CronEditor = ({
|
||||
defaultValue,
|
||||
onChangeHandler,
|
||||
children,
|
||||
isReadOnly = false,
|
||||
className,
|
||||
onError,
|
||||
}: Props) => {
|
||||
const [value, setValue] = useState(defaultValue);
|
||||
const customSetValue = useCallback((newValue) => {
|
||||
setValue(newValue);
|
||||
onChangeHandler?.(newValue);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setValue(defaultValue);
|
||||
}, [defaultValue]);
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<Cron
|
||||
className="tw-z-9999 my-project-cron"
|
||||
readOnly={isReadOnly}
|
||||
setValue={customSetValue}
|
||||
value={value}
|
||||
onError={onError}
|
||||
/>
|
||||
{children ? <>{children}</> : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CronEditor;
|
||||
@ -133,6 +133,7 @@ export const ROUTES = {
|
||||
PIPELINE_DETAILS: `/pipeline/${PLACEHOLDER_ROUTE_PIPELINE_FQN}`,
|
||||
PIPELINE_DETAILS_WITH_TAB: `/pipeline/${PLACEHOLDER_ROUTE_PIPELINE_FQN}/${PLACEHOLDER_ROUTE_TAB}`,
|
||||
ONBOARDING: '/onboarding',
|
||||
INGESTION: '/ingestion',
|
||||
};
|
||||
|
||||
export const IN_PAGE_SEARCH_ROUTES: Record<string, Array<string>> = {
|
||||
@ -253,6 +254,7 @@ export const navLinkSettings = [
|
||||
{ name: 'Tags', to: '/tags', disabled: false },
|
||||
// { name: 'Store', to: '/store', disabled: false },
|
||||
{ name: 'Services', to: '/services', disabled: false },
|
||||
{ name: 'Ingestions', to: '/ingestion', disabled: false },
|
||||
// { name: 'Marketplace', to: '/marketplace', disabled: true },
|
||||
// { name: 'Preferences', to: '/preference', disabled: true },
|
||||
];
|
||||
|
||||
@ -52,3 +52,18 @@ export enum PipelineServiceType {
|
||||
AIRFLOW = 'Airflow',
|
||||
PREFECT = 'Prefect',
|
||||
}
|
||||
|
||||
export enum IngestionType {
|
||||
BIGQUERY = 'bigquery',
|
||||
BIGQUERY_USAGE = 'bigquery-usage',
|
||||
REDSHIFT = 'redshift',
|
||||
REDSHIFT_USAGE = 'redshift-usage',
|
||||
SNOWFLAKE = 'snowflake',
|
||||
SNOWFLAKE_USAGE = 'snowflake-usage',
|
||||
HIVE = 'hive',
|
||||
MSSQL = 'mssql',
|
||||
MYSQL = 'mysql',
|
||||
POSTGRES = 'postgres',
|
||||
TRINO = 'trino',
|
||||
VERTICA = 'vertica',
|
||||
}
|
||||
|
||||
@ -0,0 +1,223 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { AxiosError, AxiosResponse } from 'axios';
|
||||
import { Operation } from 'fast-json-patch';
|
||||
import { Paging } from 'Models';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import {
|
||||
addIngestionWorkflow,
|
||||
deleteIngestionWorkflowsById,
|
||||
getIngestionWorkflows,
|
||||
patchIngestionWorkflowBtId,
|
||||
triggerIngestionWorkflowsById,
|
||||
} from '../../axiosAPIs/ingestionWorkflowAPI';
|
||||
import { getServices } from '../../axiosAPIs/serviceAPI';
|
||||
import Ingestion from '../../components/Ingestion/Ingestion.component';
|
||||
import { IngestionData } from '../../components/Ingestion/ingestion.interface';
|
||||
import Loader from '../../components/Loader/Loader';
|
||||
import { DatabaseService } from '../../generated/entity/services/databaseService';
|
||||
import { EntityReference } from '../../generated/type/entityReference';
|
||||
import useToastContext from '../../hooks/useToastContext';
|
||||
import { getCurrentUserId } from '../../utils/CommonUtils';
|
||||
import { getOwnerFromId } from '../../utils/TableUtils';
|
||||
|
||||
const IngestionPage = () => {
|
||||
const showToast = useToastContext();
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [ingestions, setIngestions] = useState([]);
|
||||
const [serviceList, setServiceList] = useState<Array<DatabaseService>>([]);
|
||||
const [paging, setPaging] = useState<Paging>({} as Paging);
|
||||
const getDatabaseServices = () => {
|
||||
getServices('databaseServices')
|
||||
.then((res: AxiosResponse) => {
|
||||
setServiceList(res.data.data);
|
||||
})
|
||||
.catch((err: AxiosError) => {
|
||||
// eslint-disable-next-line
|
||||
console.log(err);
|
||||
});
|
||||
};
|
||||
|
||||
const getAllIngestionWorkflows = (paging?: string) => {
|
||||
getIngestionWorkflows(['owner, service, tags, status'], paging)
|
||||
.then((res) => {
|
||||
if (res.data.data) {
|
||||
setIngestions(res.data.data);
|
||||
setPaging(res.data.paging);
|
||||
setIsLoading(false);
|
||||
} else {
|
||||
setPaging({} as Paging);
|
||||
}
|
||||
})
|
||||
.catch((err: AxiosError) => {
|
||||
const msg = err.message;
|
||||
showToast({
|
||||
variant: 'error',
|
||||
body: msg ?? `Error while getting ingestion workflow`,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const triggerIngestionById = (
|
||||
id: string,
|
||||
displayName: string
|
||||
): Promise<void> => {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
triggerIngestionWorkflowsById(id)
|
||||
.then((res) => {
|
||||
if (res.data) {
|
||||
resolve();
|
||||
getAllIngestionWorkflows();
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
})
|
||||
.catch((err: AxiosError) => {
|
||||
const msg = err.message;
|
||||
showToast({
|
||||
variant: 'error',
|
||||
body:
|
||||
msg ?? `Error while triggering ingestion workflow ${displayName}`,
|
||||
});
|
||||
reject();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const deleteIngestionById = (
|
||||
id: string,
|
||||
displayName: string
|
||||
): Promise<void> => {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
deleteIngestionWorkflowsById(id)
|
||||
.then(() => {
|
||||
resolve();
|
||||
getAllIngestionWorkflows();
|
||||
})
|
||||
.catch((err: AxiosError) => {
|
||||
const msg = err.message;
|
||||
showToast({
|
||||
variant: 'error',
|
||||
body:
|
||||
msg ?? `Error while deleting ingestion workflow ${displayName}`,
|
||||
});
|
||||
reject();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const updateIngestionById = (
|
||||
id: string,
|
||||
displayName: string,
|
||||
patch: Array<Operation>,
|
||||
triggerIngestion?: boolean
|
||||
): Promise<void> => {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
patchIngestionWorkflowBtId(id, patch)
|
||||
.then(() => {
|
||||
resolve();
|
||||
getAllIngestionWorkflows();
|
||||
if (triggerIngestion) {
|
||||
triggerIngestionById(id, displayName).then();
|
||||
}
|
||||
})
|
||||
.catch((err: AxiosError) => {
|
||||
const msg = err.message;
|
||||
showToast({
|
||||
variant: 'error',
|
||||
body:
|
||||
msg ?? `Error while updating ingestion workflow ${displayName}`,
|
||||
});
|
||||
reject();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const addIngestionWorkflowHandler = (
|
||||
data: IngestionData,
|
||||
triggerIngestion?: boolean
|
||||
) => {
|
||||
setIsLoading(true);
|
||||
const service = serviceList.find((s) => s.name === data.service.name);
|
||||
const owner = getOwnerFromId(getCurrentUserId());
|
||||
const ingestionData = {
|
||||
...data,
|
||||
service: {
|
||||
id: service?.id,
|
||||
type: 'databaseService',
|
||||
name: data.service.name,
|
||||
} as EntityReference,
|
||||
owner: {
|
||||
id: owner?.id as string,
|
||||
name: owner?.name,
|
||||
type: 'user',
|
||||
},
|
||||
};
|
||||
|
||||
addIngestionWorkflow(ingestionData)
|
||||
.then((res: AxiosResponse) => {
|
||||
const { id, displayName } = res.data;
|
||||
setIsLoading(false);
|
||||
getAllIngestionWorkflows();
|
||||
if (triggerIngestion) {
|
||||
triggerIngestionById(id, displayName).then();
|
||||
}
|
||||
})
|
||||
.catch((err: AxiosError) => {
|
||||
const msg = err.message;
|
||||
showToast({
|
||||
variant: 'error',
|
||||
body: msg ?? `Something went wrong`,
|
||||
});
|
||||
setIsLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
const pagingHandler = (cursorType: string) => {
|
||||
const pagingString = `&${cursorType}=${
|
||||
paging[cursorType as keyof typeof paging]
|
||||
}`;
|
||||
getAllIngestionWorkflows(pagingString);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getDatabaseServices();
|
||||
getAllIngestionWorkflows();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
{isLoading ? (
|
||||
<Loader />
|
||||
) : (
|
||||
<Ingestion
|
||||
addIngestion={addIngestionWorkflowHandler}
|
||||
deleteIngestion={deleteIngestionById}
|
||||
ingestionList={ingestions}
|
||||
paging={paging}
|
||||
pagingHandler={pagingHandler}
|
||||
serviceList={serviceList}
|
||||
triggerIngestion={triggerIngestionById}
|
||||
updateIngestion={updateIngestionById}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default IngestionPage;
|
||||
@ -26,6 +26,7 @@ import DatabaseDetails from '../pages/database-details/index';
|
||||
import DatasetDetailsPage from '../pages/DatasetDetailsPage/DatasetDetailsPage.component';
|
||||
import EntityVersionPage from '../pages/EntityVersionPage/EntityVersionPage.component';
|
||||
import ExplorePage from '../pages/explore/ExplorePage.component';
|
||||
import IngestionPage from '../pages/IngestionPage/IngestionPage.component';
|
||||
import MyDataPage from '../pages/MyDataPage/MyDataPage.component';
|
||||
import PipelineDetailsPage from '../pages/PipelineDetails/PipelineDetailsPage.component';
|
||||
import ReportsPage from '../pages/reports';
|
||||
@ -110,6 +111,7 @@ const AuthenticatedAppRouter: FunctionComponent = () => {
|
||||
component={EntityVersionPage}
|
||||
path={ROUTES.DATASET_VERSION}
|
||||
/>
|
||||
<Route exact component={IngestionPage} path={ROUTES.INGESTION} />
|
||||
|
||||
<Redirect to={ROUTES.NOT_FOUND} />
|
||||
</Switch>
|
||||
|
||||
@ -317,6 +317,22 @@
|
||||
box-shadow: 0 0 0 0.5px #7147e8;
|
||||
}
|
||||
|
||||
/* Cron editor css */
|
||||
.my-project-cron {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.my-project-cron-select {
|
||||
@apply tw-rounded tw-border tw-border-main
|
||||
focus:tw-outline-none focus:tw-border-focus hover:tw-border-hover focus:tw-shadow-none hover:tw-shadow-none;
|
||||
}
|
||||
.my-project-cron-clear-button {
|
||||
background-color: #7147e8 !important;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* react-slick */
|
||||
|
||||
.slick-dots {
|
||||
|
||||
@ -703,3 +703,17 @@ body .list-option.rdw-option-active {
|
||||
text-decoration: line-through;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
/* status style */
|
||||
.tw-bg-status-success {
|
||||
background-color: #07a35a;
|
||||
}
|
||||
.tw-bg-status-failed {
|
||||
background-color: #e54937;
|
||||
}
|
||||
.tw-bg-status-running {
|
||||
background-color: #276ef1;
|
||||
}
|
||||
.tw-bg-status-queued {
|
||||
background-color: #777777;
|
||||
}
|
||||
|
||||
@ -28,6 +28,7 @@ import {
|
||||
import {
|
||||
DashboardServiceType,
|
||||
DatabaseServiceType,
|
||||
IngestionType,
|
||||
MessagingServiceType,
|
||||
PipelineServiceType,
|
||||
} from '../enums/service.enum';
|
||||
@ -249,3 +250,54 @@ export const getTotalEntityCountByService = (buckets: Array<Bucket> = []) => {
|
||||
|
||||
return entityCounts;
|
||||
};
|
||||
|
||||
export const getIngestionTypeList = (
|
||||
serviceType: string
|
||||
): Array<string> | undefined => {
|
||||
let ingestionType: Array<string> | undefined;
|
||||
switch (serviceType) {
|
||||
case DatabaseServiceType.BIGQUERY:
|
||||
ingestionType = [IngestionType.BIGQUERY, IngestionType.BIGQUERY_USAGE];
|
||||
|
||||
break;
|
||||
case DatabaseServiceType.HIVE:
|
||||
ingestionType = [IngestionType.HIVE];
|
||||
|
||||
break;
|
||||
|
||||
case DatabaseServiceType.MSSQL:
|
||||
ingestionType = [IngestionType.MSSQL];
|
||||
|
||||
break;
|
||||
|
||||
case DatabaseServiceType.MYSQL:
|
||||
ingestionType = [IngestionType.MYSQL];
|
||||
|
||||
break;
|
||||
|
||||
case DatabaseServiceType.POSTGRES:
|
||||
ingestionType = [IngestionType.POSTGRES];
|
||||
|
||||
break;
|
||||
|
||||
case DatabaseServiceType.REDSHIFT:
|
||||
ingestionType = [IngestionType.REDSHIFT, IngestionType.REDSHIFT_USAGE];
|
||||
|
||||
break;
|
||||
|
||||
case DatabaseServiceType.TRINO:
|
||||
ingestionType = [IngestionType.TRINO];
|
||||
|
||||
break;
|
||||
|
||||
case DatabaseServiceType.SNOWFLAKE:
|
||||
ingestionType = [IngestionType.SNOWFLAKE, IngestionType.SNOWFLAKE_USAGE];
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return ingestionType;
|
||||
};
|
||||
|
||||
@ -10,6 +10,7 @@ import IconConfig from '../assets/svg/config.svg';
|
||||
import IconDashboardGrey from '../assets/svg/dashboard-grey.svg';
|
||||
import IconDashboard from '../assets/svg/dashboard.svg';
|
||||
import IconAsstest from '../assets/svg/data-assets.svg';
|
||||
import IconDeploy from '../assets/svg/deploy-icon.svg';
|
||||
import IconDoc from '../assets/svg/doc.svg';
|
||||
import IconError from '../assets/svg/error.svg';
|
||||
import IconExternalLink from '../assets/svg/external-link.svg';
|
||||
@ -146,6 +147,7 @@ export const Icons = {
|
||||
PIPELINE_GREY: 'pipeline-grey',
|
||||
VERSION: 'icon-version',
|
||||
VERSION_WHITE: 'icon-version-white',
|
||||
ICON_DEPLOY: 'icon-deploy',
|
||||
};
|
||||
|
||||
const SVGIcons: FunctionComponent<Props> = ({
|
||||
@ -435,6 +437,10 @@ const SVGIcons: FunctionComponent<Props> = ({
|
||||
case Icons.VERSION_WHITE:
|
||||
IconComponent = IconVersionWhite;
|
||||
|
||||
break;
|
||||
case Icons.ICON_DEPLOY:
|
||||
IconComponent = IconDeploy;
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
|
||||
@ -33,6 +33,13 @@ const info = '#1890FF';
|
||||
const warning = '#FFC34E';
|
||||
const warningBG = '#FFC34E40';
|
||||
|
||||
// status colors
|
||||
|
||||
const statusSuccess = '#07A35A';
|
||||
const statusFailed = '#E54937';
|
||||
const statusRunning = '#276EF1';
|
||||
const statusQueued = '#777777';
|
||||
|
||||
// Background colors
|
||||
const bodyBG = '#FCFBFE';
|
||||
const bodyHoverBG = '#F9F8FD';
|
||||
@ -94,6 +101,10 @@ module.exports = {
|
||||
error: error,
|
||||
warning: warning,
|
||||
'warning-lite': warningBG,
|
||||
'status-success': statusSuccess,
|
||||
'status-failed': statusFailed,
|
||||
'status-running': statusRunning,
|
||||
'status-queued': statusQueued,
|
||||
info: info,
|
||||
separator: mainSeparator,
|
||||
},
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user