mirror of https://github.com/apache/superset.git
[dashboard] New, list view (react) (#8845)
* adds dashboard listview component * use new api * use json over rison * lint * adds seperate dashboard list view * edit and delete actions * fix lint ignore * fix common_bootstrap_payload is now a function * fix license * fix pylint * isort * fix tests * lint * lint ts * fix js tests * fix double import from bad rebase * fix indent error * lookup permissions * generic permission lookup * get tslint to pass * adds js specs * lint * fix rebase * lint * lint again * fix type errors preventing build * adds more specs * fix tslint error * fix null check * remove unecessary code * use translations provided by api * more translations * linting * fix spec * i18n * fix register order
This commit is contained in:
parent
7e6719050b
commit
7b97764dbc
|
@ -16,16 +16,19 @@
|
||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
{
|
{
|
||||||
"sourceMaps": true,
|
"sourceMaps": true,
|
||||||
"retainLines": true,
|
"retainLines": true,
|
||||||
"presets" : ["airbnb", "@babel/preset-react", "@babel/preset-env"],
|
"presets": ["airbnb", "@babel/preset-react", "@babel/preset-env"],
|
||||||
"plugins": ["lodash", "@babel/plugin-syntax-dynamic-import", "react-hot-loader/babel"],
|
"plugins": [
|
||||||
|
"lodash",
|
||||||
|
"@babel/plugin-syntax-dynamic-import",
|
||||||
|
"@babel/plugin-proposal-class-properties",
|
||||||
|
"react-hot-loader/babel"
|
||||||
|
],
|
||||||
"env": {
|
"env": {
|
||||||
"test": {
|
"test": {
|
||||||
"plugins": [
|
"plugins": ["babel-plugin-dynamic-import-node"]
|
||||||
"babel-plugin-dynamic-import-node"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,4 +32,11 @@ module.exports = {
|
||||||
'^.+\\.tsx?$': 'ts-jest',
|
'^.+\\.tsx?$': 'ts-jest',
|
||||||
},
|
},
|
||||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'],
|
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'],
|
||||||
|
globals: {
|
||||||
|
'ts-jest': {
|
||||||
|
diagnostics: {
|
||||||
|
warnOnly: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -435,6 +435,164 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@babel/helper-create-class-features-plugin": {
|
||||||
|
"version": "7.7.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.7.4.tgz",
|
||||||
|
"integrity": "sha512-l+OnKACG4uiDHQ/aJT8dwpR+LhCJALxL0mJ6nzjB25e5IPwqV1VOsY7ah6UB1DG+VOXAIMtuC54rFJGiHkxjgA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/helper-function-name": "^7.7.4",
|
||||||
|
"@babel/helper-member-expression-to-functions": "^7.7.4",
|
||||||
|
"@babel/helper-optimise-call-expression": "^7.7.4",
|
||||||
|
"@babel/helper-plugin-utils": "^7.0.0",
|
||||||
|
"@babel/helper-replace-supers": "^7.7.4",
|
||||||
|
"@babel/helper-split-export-declaration": "^7.7.4"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/generator": {
|
||||||
|
"version": "7.7.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.7.4.tgz",
|
||||||
|
"integrity": "sha512-m5qo2WgdOJeyYngKImbkyQrnUN1mPceaG5BV+G0E3gWsa4l/jCSryWJdM2x8OuGAOyh+3d5pVYfZWCiNFtynxg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/types": "^7.7.4",
|
||||||
|
"jsesc": "^2.5.1",
|
||||||
|
"lodash": "^4.17.13",
|
||||||
|
"source-map": "^0.5.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@babel/helper-function-name": {
|
||||||
|
"version": "7.7.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.7.4.tgz",
|
||||||
|
"integrity": "sha512-AnkGIdiBhEuiwdoMnKm7jfPfqItZhgRaZfMg1XX3bS25INOnLPjPG1Ppnajh8eqgt5kPJnfqrRHqFqmjKDZLzQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/helper-get-function-arity": "^7.7.4",
|
||||||
|
"@babel/template": "^7.7.4",
|
||||||
|
"@babel/types": "^7.7.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@babel/helper-get-function-arity": {
|
||||||
|
"version": "7.7.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.7.4.tgz",
|
||||||
|
"integrity": "sha512-QTGKEdCkjgzgfJ3bAyRwF4yyT3pg+vDgan8DSivq1eS0gwi+KGKE5x8kRcbeFTb/673mkO5SN1IZfmCfA5o+EA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/types": "^7.7.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@babel/helper-member-expression-to-functions": {
|
||||||
|
"version": "7.7.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.7.4.tgz",
|
||||||
|
"integrity": "sha512-9KcA1X2E3OjXl/ykfMMInBK+uVdfIVakVe7W7Lg3wfXUNyS3Q1HWLFRwZIjhqiCGbslummPDnmb7vIekS0C1vw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/types": "^7.7.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@babel/helper-optimise-call-expression": {
|
||||||
|
"version": "7.7.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.7.4.tgz",
|
||||||
|
"integrity": "sha512-VB7gWZ2fDkSuqW6b1AKXkJWO5NyNI3bFL/kK79/30moK57blr6NbH8xcl2XcKCwOmJosftWunZqfO84IGq3ZZg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/types": "^7.7.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@babel/helper-replace-supers": {
|
||||||
|
"version": "7.7.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.7.4.tgz",
|
||||||
|
"integrity": "sha512-pP0tfgg9hsZWo5ZboYGuBn/bbYT/hdLPVSS4NMmiRJdwWhP0IznPwN9AE1JwyGsjSPLC364I0Qh5p+EPkGPNpg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/helper-member-expression-to-functions": "^7.7.4",
|
||||||
|
"@babel/helper-optimise-call-expression": "^7.7.4",
|
||||||
|
"@babel/traverse": "^7.7.4",
|
||||||
|
"@babel/types": "^7.7.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@babel/helper-split-export-declaration": {
|
||||||
|
"version": "7.7.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.7.4.tgz",
|
||||||
|
"integrity": "sha512-guAg1SXFcVr04Guk9eq0S4/rWS++sbmyqosJzVs8+1fH5NI+ZcmkaSkc7dmtAFbHFva6yRJnjW3yAcGxjueDug==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/types": "^7.7.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@babel/parser": {
|
||||||
|
"version": "7.7.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.7.4.tgz",
|
||||||
|
"integrity": "sha512-jIwvLO0zCL+O/LmEJQjWA75MQTWwx3c3u2JOTDK5D3/9egrWRRA0/0hk9XXywYnXZVVpzrBYeIQTmhwUaePI9g==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"@babel/template": {
|
||||||
|
"version": "7.7.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.7.4.tgz",
|
||||||
|
"integrity": "sha512-qUzihgVPguAzXCK7WXw8pqs6cEwi54s3E+HrejlkuWO6ivMKx9hZl3Y2fSXp9i5HgyWmj7RKP+ulaYnKM4yYxw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/code-frame": "^7.0.0",
|
||||||
|
"@babel/parser": "^7.7.4",
|
||||||
|
"@babel/types": "^7.7.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@babel/traverse": {
|
||||||
|
"version": "7.7.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.7.4.tgz",
|
||||||
|
"integrity": "sha512-P1L58hQyupn8+ezVA2z5KBm4/Zr4lCC8dwKCMYzsa5jFMDMQAzaBNy9W5VjB+KAmBjb40U7a/H6ao+Xo+9saIw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/code-frame": "^7.5.5",
|
||||||
|
"@babel/generator": "^7.7.4",
|
||||||
|
"@babel/helper-function-name": "^7.7.4",
|
||||||
|
"@babel/helper-split-export-declaration": "^7.7.4",
|
||||||
|
"@babel/parser": "^7.7.4",
|
||||||
|
"@babel/types": "^7.7.4",
|
||||||
|
"debug": "^4.1.0",
|
||||||
|
"globals": "^11.1.0",
|
||||||
|
"lodash": "^4.17.13"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/code-frame": {
|
||||||
|
"version": "7.5.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz",
|
||||||
|
"integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/highlight": "^7.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@babel/types": {
|
||||||
|
"version": "7.7.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.7.4.tgz",
|
||||||
|
"integrity": "sha512-cz5Ji23KCi4T+YIE/BolWosrJuSmoZeN1EFnRtBwF+KKLi8GG/Z2c2hOJJeCXPk4mwk4QFvTmwIodJowXgttRA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"esutils": "^2.0.2",
|
||||||
|
"lodash": "^4.17.13",
|
||||||
|
"to-fast-properties": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"debug": {
|
||||||
|
"version": "4.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
|
||||||
|
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"ms": "^2.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ms": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"@babel/helper-define-map": {
|
"@babel/helper-define-map": {
|
||||||
"version": "7.5.5",
|
"version": "7.5.5",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.5.5.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.5.5.tgz",
|
||||||
|
@ -918,6 +1076,16 @@
|
||||||
"@babel/plugin-syntax-async-generators": "^7.2.0"
|
"@babel/plugin-syntax-async-generators": "^7.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@babel/plugin-proposal-class-properties": {
|
||||||
|
"version": "7.7.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.7.4.tgz",
|
||||||
|
"integrity": "sha512-EcuXeV4Hv1X3+Q1TsuOmyyxeTRiSqurGJ26+I/FW1WbymmRRapVORm6x1Zl3iDIHyRxEs+VXWp6qnlcfcJSbbw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/helper-create-class-features-plugin": "^7.7.4",
|
||||||
|
"@babel/helper-plugin-utils": "^7.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@babel/plugin-proposal-dynamic-import": {
|
"@babel/plugin-proposal-dynamic-import": {
|
||||||
"version": "7.5.0",
|
"version": "7.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.5.0.tgz",
|
||||||
|
@ -9237,8 +9405,7 @@
|
||||||
"decode-uri-component": {
|
"decode-uri-component": {
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
|
||||||
"integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=",
|
"integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"deep-equal": {
|
"deep-equal": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
|
@ -20880,6 +21047,11 @@
|
||||||
"refractor": "^2.4.1"
|
"refractor": "^2.4.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"react-table": {
|
||||||
|
"version": "7.0.0-beta.26",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-table/-/react-table-7.0.0-beta.26.tgz",
|
||||||
|
"integrity": "sha512-Pw/1T9kiAjV1cIf6K6bQV6yNQc3O7XUGin1RcSR1xFKw0RNGC5vl1VDPZrNep1BXDsbR6o8O63X45HFNVg6HzA=="
|
||||||
|
},
|
||||||
"react-test-renderer": {
|
"react-test-renderer": {
|
||||||
"version": "16.9.0",
|
"version": "16.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.9.0.tgz",
|
||||||
|
@ -22019,6 +22191,26 @@
|
||||||
"integrity": "sha512-Ga8c8NjAAp46Br4+0oZ2WxJCwIzwP60Gq1YPgU+39PiTVxyed/iKE/zyZI6+UlVYH5Q4PaQdHhcegIFPZTUfoQ==",
|
"integrity": "sha512-Ga8c8NjAAp46Br4+0oZ2WxJCwIzwP60Gq1YPgU+39PiTVxyed/iKE/zyZI6+UlVYH5Q4PaQdHhcegIFPZTUfoQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"serialize-query-params": {
|
||||||
|
"version": "0.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/serialize-query-params/-/serialize-query-params-0.1.4.tgz",
|
||||||
|
"integrity": "sha512-d3GHKPAOBULhCMg+jM687vRIMnTXMo8M0lHUOVeFxSGYvfmNlksiOpLyb0orhXPhhFCvZvt+SwC2iPRVIhKS/g==",
|
||||||
|
"requires": {
|
||||||
|
"query-string": "^5.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"query-string": {
|
||||||
|
"version": "5.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz",
|
||||||
|
"integrity": "sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==",
|
||||||
|
"requires": {
|
||||||
|
"decode-uri-component": "^0.2.0",
|
||||||
|
"object-assign": "^4.1.0",
|
||||||
|
"strict-uri-encode": "^1.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"serve-index": {
|
"serve-index": {
|
||||||
"version": "1.9.1",
|
"version": "1.9.1",
|
||||||
"resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz",
|
"resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz",
|
||||||
|
@ -23761,9 +23953,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ts-loader": {
|
"ts-loader": {
|
||||||
"version": "5.3.1",
|
"version": "5.4.5",
|
||||||
"resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-5.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-5.4.5.tgz",
|
||||||
"integrity": "sha512-fDDgpBH3SR8xlt2MasLdz3Yy611PQ/UY/KGyo7TgXhTRU/6sS8uGG0nJYnU1OdFBNKcoYbId1UTNaAOUn+i41g==",
|
"integrity": "sha512-XYsjfnRQCBum9AMRZpk2rTYSVpdZBpZK+kDh0TeT3kxmQNBDVIeUjdPjY5RZry4eIAb8XHc4gYSUiUWPYvzSRw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"chalk": "^2.3.0",
|
"chalk": "^2.3.0",
|
||||||
|
@ -23782,51 +23974,10 @@
|
||||||
"color-convert": "^1.9.0"
|
"color-convert": "^1.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"arr-diff": {
|
|
||||||
"version": "4.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz",
|
|
||||||
"integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"array-unique": {
|
|
||||||
"version": "0.3.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz",
|
|
||||||
"integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"braces": {
|
|
||||||
"version": "2.3.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz",
|
|
||||||
"integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"arr-flatten": "^1.1.0",
|
|
||||||
"array-unique": "^0.3.2",
|
|
||||||
"extend-shallow": "^2.0.1",
|
|
||||||
"fill-range": "^4.0.0",
|
|
||||||
"isobject": "^3.0.1",
|
|
||||||
"repeat-element": "^1.1.2",
|
|
||||||
"snapdragon": "^0.8.1",
|
|
||||||
"snapdragon-node": "^2.0.1",
|
|
||||||
"split-string": "^3.0.2",
|
|
||||||
"to-regex": "^3.0.1"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"extend-shallow": {
|
|
||||||
"version": "2.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
|
|
||||||
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"is-extendable": "^0.1.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"chalk": {
|
"chalk": {
|
||||||
"version": "2.4.1",
|
"version": "2.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
||||||
"integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==",
|
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"ansi-styles": "^3.2.1",
|
"ansi-styles": "^3.2.1",
|
||||||
|
@ -23835,274 +23986,26 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"enhanced-resolve": {
|
"enhanced-resolve": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.1.1.tgz",
|
||||||
"integrity": "sha512-F/7vkyTtyc/llOIn8oWclcB25KdRaiPBpZYDgJHgh/UHtpgT2p2eldQgtQnLtUvfMKPKxbRaQM/hHkvLHt1Vng==",
|
"integrity": "sha512-98p2zE+rL7/g/DzMHMTF4zZlCgeVdJ7yr6xzEpJRYwFYrGi9ANdn5DnJURg6RpBkyk60XYDnWIv51VfIhfNGuA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"graceful-fs": "^4.1.2",
|
"graceful-fs": "^4.1.2",
|
||||||
"memory-fs": "^0.4.0",
|
"memory-fs": "^0.5.0",
|
||||||
"tapable": "^1.0.0"
|
"tapable": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"expand-brackets": {
|
|
||||||
"version": "2.1.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz",
|
|
||||||
"integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"debug": "^2.3.3",
|
|
||||||
"define-property": "^0.2.5",
|
|
||||||
"extend-shallow": "^2.0.1",
|
|
||||||
"posix-character-classes": "^0.1.0",
|
|
||||||
"regex-not": "^1.0.0",
|
|
||||||
"snapdragon": "^0.8.1",
|
|
||||||
"to-regex": "^3.0.1"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"define-property": {
|
|
||||||
"version": "0.2.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
|
|
||||||
"integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"is-descriptor": "^0.1.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"extend-shallow": {
|
|
||||||
"version": "2.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
|
|
||||||
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"is-extendable": "^0.1.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"is-accessor-descriptor": {
|
|
||||||
"version": "0.1.6",
|
|
||||||
"resolved": "http://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
|
|
||||||
"integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"kind-of": "^3.0.2"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"kind-of": {
|
|
||||||
"version": "3.2.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
|
|
||||||
"integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"is-buffer": "^1.1.5"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"is-data-descriptor": {
|
|
||||||
"version": "0.1.4",
|
|
||||||
"resolved": "http://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
|
|
||||||
"integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"kind-of": "^3.0.2"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"kind-of": {
|
|
||||||
"version": "3.2.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
|
|
||||||
"integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"is-buffer": "^1.1.5"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"is-descriptor": {
|
|
||||||
"version": "0.1.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz",
|
|
||||||
"integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"is-accessor-descriptor": "^0.1.6",
|
|
||||||
"is-data-descriptor": "^0.1.4",
|
|
||||||
"kind-of": "^5.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"kind-of": {
|
|
||||||
"version": "5.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz",
|
|
||||||
"integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==",
|
|
||||||
"dev": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"extend-shallow": {
|
|
||||||
"version": "3.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
|
|
||||||
"integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"assign-symbols": "^1.0.0",
|
|
||||||
"is-extendable": "^1.0.1"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"is-extendable": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
|
|
||||||
"integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"is-plain-object": "^2.0.4"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"extglob": {
|
|
||||||
"version": "2.0.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz",
|
|
||||||
"integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"array-unique": "^0.3.2",
|
|
||||||
"define-property": "^1.0.0",
|
|
||||||
"expand-brackets": "^2.1.4",
|
|
||||||
"extend-shallow": "^2.0.1",
|
|
||||||
"fragment-cache": "^0.2.1",
|
|
||||||
"regex-not": "^1.0.0",
|
|
||||||
"snapdragon": "^0.8.1",
|
|
||||||
"to-regex": "^3.0.1"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"define-property": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
|
|
||||||
"integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"is-descriptor": "^1.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"extend-shallow": {
|
|
||||||
"version": "2.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
|
|
||||||
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"is-extendable": "^0.1.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"fill-range": {
|
|
||||||
"version": "4.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
|
|
||||||
"integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"extend-shallow": "^2.0.1",
|
|
||||||
"is-number": "^3.0.0",
|
|
||||||
"repeat-string": "^1.6.1",
|
|
||||||
"to-regex-range": "^2.1.0"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"extend-shallow": {
|
|
||||||
"version": "2.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
|
|
||||||
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"is-extendable": "^0.1.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"is-accessor-descriptor": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
|
|
||||||
"integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"kind-of": "^6.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"is-data-descriptor": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
|
|
||||||
"integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"kind-of": "^6.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"is-descriptor": {
|
|
||||||
"version": "1.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
|
|
||||||
"integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"is-accessor-descriptor": "^1.0.0",
|
|
||||||
"is-data-descriptor": "^1.0.0",
|
|
||||||
"kind-of": "^6.0.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"is-number": {
|
|
||||||
"version": "3.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
|
|
||||||
"integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"kind-of": "^3.0.2"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"kind-of": {
|
|
||||||
"version": "3.2.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
|
|
||||||
"integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"is-buffer": "^1.1.5"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"kind-of": {
|
|
||||||
"version": "6.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
|
|
||||||
"integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"memory-fs": {
|
"memory-fs": {
|
||||||
"version": "0.4.1",
|
"version": "0.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz",
|
||||||
"integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=",
|
"integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"errno": "^0.1.3",
|
"errno": "^0.1.3",
|
||||||
"readable-stream": "^2.0.1"
|
"readable-stream": "^2.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"micromatch": {
|
|
||||||
"version": "3.1.10",
|
|
||||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
|
|
||||||
"integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"arr-diff": "^4.0.0",
|
|
||||||
"array-unique": "^0.3.2",
|
|
||||||
"braces": "^2.3.1",
|
|
||||||
"define-property": "^2.0.2",
|
|
||||||
"extend-shallow": "^3.0.2",
|
|
||||||
"extglob": "^2.0.4",
|
|
||||||
"fragment-cache": "^0.2.1",
|
|
||||||
"kind-of": "^6.0.2",
|
|
||||||
"nanomatch": "^1.2.9",
|
|
||||||
"object.pick": "^1.3.0",
|
|
||||||
"regex-not": "^1.0.0",
|
|
||||||
"snapdragon": "^0.8.1",
|
|
||||||
"to-regex": "^3.0.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"supports-color": {
|
"supports-color": {
|
||||||
"version": "5.5.0",
|
"version": "5.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||||
|
@ -24113,37 +24016,38 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tapable": {
|
"tapable": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz",
|
||||||
"integrity": "sha512-9I2ydhj8Z9veORCw5PRm4u9uebCn0mcCa6scWoNcbZ6dAtoo2618u9UUzxgmsCOreJpqDDuv61LvwofW7hLcBA==",
|
"integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tslib": {
|
"tslib": {
|
||||||
"version": "1.9.3",
|
"version": "1.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz",
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz",
|
||||||
"integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==",
|
"integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"tslint": {
|
"tslint": {
|
||||||
"version": "5.11.0",
|
"version": "5.20.1",
|
||||||
"resolved": "https://registry.npmjs.org/tslint/-/tslint-5.11.0.tgz",
|
"resolved": "https://registry.npmjs.org/tslint/-/tslint-5.20.1.tgz",
|
||||||
"integrity": "sha1-mPMMAurjzecAYgHkwzywi0hYHu0=",
|
"integrity": "sha512-EcMxhzCFt8k+/UP5r8waCf/lzmeSyVlqxqMEDQE7rWYiQky8KpIBz1JAoYXfROHrPZ1XXd43q8yQnULOLiBRQg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"babel-code-frame": "^6.22.0",
|
"@babel/code-frame": "^7.0.0",
|
||||||
"builtin-modules": "^1.1.1",
|
"builtin-modules": "^1.1.1",
|
||||||
"chalk": "^2.3.0",
|
"chalk": "^2.3.0",
|
||||||
"commander": "^2.12.1",
|
"commander": "^2.12.1",
|
||||||
"diff": "^3.2.0",
|
"diff": "^4.0.1",
|
||||||
"glob": "^7.1.1",
|
"glob": "^7.1.1",
|
||||||
"js-yaml": "^3.7.0",
|
"js-yaml": "^3.13.1",
|
||||||
"minimatch": "^3.0.4",
|
"minimatch": "^3.0.4",
|
||||||
|
"mkdirp": "^0.5.1",
|
||||||
"resolve": "^1.3.2",
|
"resolve": "^1.3.2",
|
||||||
"semver": "^5.3.0",
|
"semver": "^5.3.0",
|
||||||
"tslib": "^1.8.0",
|
"tslib": "^1.8.0",
|
||||||
"tsutils": "^2.27.2"
|
"tsutils": "^2.29.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ansi-styles": {
|
"ansi-styles": {
|
||||||
|
@ -24156,9 +24060,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"chalk": {
|
"chalk": {
|
||||||
"version": "2.4.1",
|
"version": "2.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
||||||
"integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==",
|
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"ansi-styles": "^3.2.1",
|
"ansi-styles": "^3.2.1",
|
||||||
|
@ -24166,6 +24070,12 @@
|
||||||
"supports-color": "^5.3.0"
|
"supports-color": "^5.3.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"diff": {
|
||||||
|
"version": "4.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.1.tgz",
|
||||||
|
"integrity": "sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"supports-color": {
|
"supports-color": {
|
||||||
"version": "5.5.0",
|
"version": "5.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||||
|
@ -24178,12 +24088,23 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tslint-react": {
|
"tslint-react": {
|
||||||
"version": "3.6.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/tslint-react/-/tslint-react-3.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/tslint-react/-/tslint-react-4.1.0.tgz",
|
||||||
"integrity": "sha512-AIv1QcsSnj7e9pFir6cJ6vIncTqxfqeFF3Lzh8SuuBljueYzEAtByuB6zMaD27BL0xhMEqsZ9s5eHuCONydjBw==",
|
"integrity": "sha512-Y7CbFn09X7Mpg6rc7t/WPbmjx9xPI8p1RsQyiGCLWgDR6sh3+IBSlT+bEkc0PSZcWwClOkqq2wPsID8Vep6szQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"tsutils": "^2.13.1"
|
"tsutils": "^3.9.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"tsutils": {
|
||||||
|
"version": "3.17.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.17.1.tgz",
|
||||||
|
"integrity": "sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"tslib": "^1.8.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tsutils": {
|
"tsutils": {
|
||||||
|
@ -24586,6 +24507,14 @@
|
||||||
"integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==",
|
"integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"use-query-params": {
|
||||||
|
"version": "0.4.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/use-query-params/-/use-query-params-0.4.5.tgz",
|
||||||
|
"integrity": "sha512-HeSgLvEj26pkNRGeAIq+uTo6Z22iaAqDMosq+Be5lab4v57gwVIUKsS3iZ1BBgsUbLEKKpoqcVvqd9MUg+lkIw==",
|
||||||
|
"requires": {
|
||||||
|
"serialize-query-params": "^0.1.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"util": {
|
"util": {
|
||||||
"version": "0.10.4",
|
"version": "0.10.4",
|
||||||
"resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz",
|
"resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz",
|
||||||
|
|
|
@ -136,6 +136,7 @@
|
||||||
"react-split": "^2.0.4",
|
"react-split": "^2.0.4",
|
||||||
"react-sticky": "^6.0.2",
|
"react-sticky": "^6.0.2",
|
||||||
"react-syntax-highlighter": "^7.0.4",
|
"react-syntax-highlighter": "^7.0.4",
|
||||||
|
"react-table": "^7.0.0-beta.26",
|
||||||
"react-transition-group": "^2.5.3",
|
"react-transition-group": "^2.5.3",
|
||||||
"react-virtualized": "9.19.1",
|
"react-virtualized": "9.19.1",
|
||||||
"react-virtualized-select": "^3.1.3",
|
"react-virtualized-select": "^3.1.3",
|
||||||
|
@ -146,12 +147,14 @@
|
||||||
"redux-undo": "^1.0.0-beta9-9-7",
|
"redux-undo": "^1.0.0-beta9-9-7",
|
||||||
"regenerator-runtime": "^0.13.3",
|
"regenerator-runtime": "^0.13.3",
|
||||||
"shortid": "^2.2.6",
|
"shortid": "^2.2.6",
|
||||||
"urijs": "^1.18.10"
|
"urijs": "^1.18.10",
|
||||||
|
"use-query-params": "^0.4.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/cli": "^7.5.5",
|
"@babel/cli": "^7.5.5",
|
||||||
"@babel/core": "^7.5.5",
|
"@babel/core": "^7.5.5",
|
||||||
"@babel/node": "^7.5.5",
|
"@babel/node": "^7.5.5",
|
||||||
|
"@babel/plugin-proposal-class-properties": "^7.7.4",
|
||||||
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
|
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
|
||||||
"@babel/preset-env": "^7.5.5",
|
"@babel/preset-env": "^7.5.5",
|
||||||
"@babel/preset-react": "^7.0.0",
|
"@babel/preset-react": "^7.0.0",
|
||||||
|
@ -208,9 +211,10 @@
|
||||||
"thread-loader": "^1.2.0",
|
"thread-loader": "^1.2.0",
|
||||||
"transform-loader": "^0.2.3",
|
"transform-loader": "^0.2.3",
|
||||||
"ts-jest": "^24.0.2",
|
"ts-jest": "^24.0.2",
|
||||||
"ts-loader": "^5.2.0",
|
"ts-loader": "^5.4.5",
|
||||||
"tslint": "^5.11.0",
|
"tslib": "^1.10.0",
|
||||||
"tslint-react": "^3.6.0",
|
"tslint": "^5.20.1",
|
||||||
|
"tslint-react": "^4.1.0",
|
||||||
"typescript": "^3.5.3",
|
"typescript": "^3.5.3",
|
||||||
"url-loader": "^1.0.1",
|
"url-loader": "^1.0.1",
|
||||||
"webpack": "^4.19.0",
|
"webpack": "^4.19.0",
|
||||||
|
|
|
@ -0,0 +1,175 @@
|
||||||
|
/**
|
||||||
|
* 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 React from 'react';
|
||||||
|
import { mount } from 'enzyme';
|
||||||
|
import { act } from 'react-dom/test-utils';
|
||||||
|
import { MenuItem, Pagination } from 'react-bootstrap';
|
||||||
|
|
||||||
|
import ListView from 'src/components/ListView/ListView';
|
||||||
|
|
||||||
|
describe('ListView', () => {
|
||||||
|
const mockedProps = {
|
||||||
|
title: 'Data Table',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
accessor: 'id',
|
||||||
|
Header: 'ID',
|
||||||
|
sortable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessor: 'name',
|
||||||
|
Header: 'Name',
|
||||||
|
filterable: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
data: [
|
||||||
|
{ id: 1, name: 'data 1' },
|
||||||
|
{ id: 2, name: 'data 2' },
|
||||||
|
],
|
||||||
|
count: 2,
|
||||||
|
pageSize: 1,
|
||||||
|
fetchData: jest.fn(() => []),
|
||||||
|
loading: false,
|
||||||
|
filterTypes: {
|
||||||
|
id: [],
|
||||||
|
name: [{ name: 'sw', label: 'Starts With' }],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const wrapper = mount(<ListView {...mockedProps} />);
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
mockedProps.fetchData.mockClear();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls fetchData on mount', () => {
|
||||||
|
expect(wrapper.find(ListView)).toHaveLength(1);
|
||||||
|
expect(mockedProps.fetchData.mock.calls[0]).toMatchInlineSnapshot(`
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"filters": Object {},
|
||||||
|
"pageIndex": 0,
|
||||||
|
"pageSize": 1,
|
||||||
|
"sortBy": Array [],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls fetchData on sort', () => {
|
||||||
|
wrapper
|
||||||
|
.find('[data-test="sort-header"]')
|
||||||
|
.first()
|
||||||
|
.simulate('click');
|
||||||
|
expect(mockedProps.fetchData.mock.calls[0]).toMatchInlineSnapshot(`
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"filters": Object {},
|
||||||
|
"pageIndex": 0,
|
||||||
|
"pageSize": 1,
|
||||||
|
"sortBy": Array [
|
||||||
|
Object {
|
||||||
|
"desc": false,
|
||||||
|
"id": "id",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls fetchData on filter', () => {
|
||||||
|
act(() => {
|
||||||
|
wrapper
|
||||||
|
.find('.dropdown-toggle')
|
||||||
|
.children('button')
|
||||||
|
.props()
|
||||||
|
.onClick();
|
||||||
|
|
||||||
|
wrapper
|
||||||
|
.find(MenuItem)
|
||||||
|
.props()
|
||||||
|
.onSelect({ id: 'name', Header: 'name' });
|
||||||
|
});
|
||||||
|
wrapper.update();
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
wrapper.find('.filter-inputs input[type="text"]').prop('onChange')({
|
||||||
|
currentTarget: { value: 'foo' },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
wrapper.update();
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
wrapper
|
||||||
|
.find('[data-test="apply-filters"]')
|
||||||
|
.last()
|
||||||
|
.prop('onClick')();
|
||||||
|
});
|
||||||
|
wrapper.update();
|
||||||
|
|
||||||
|
expect(mockedProps.fetchData.mock.calls[0]).toMatchInlineSnapshot(`
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"filters": Object {
|
||||||
|
"name": Object {
|
||||||
|
"filterId": "sw",
|
||||||
|
"filterValue": "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"pageIndex": 0,
|
||||||
|
"pageSize": 1,
|
||||||
|
"sortBy": Array [
|
||||||
|
Object {
|
||||||
|
"desc": false,
|
||||||
|
"id": "id",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls fetchData on page change', () => {
|
||||||
|
act(() => {
|
||||||
|
wrapper.find(Pagination).prop('onSelect')(2);
|
||||||
|
});
|
||||||
|
wrapper.update();
|
||||||
|
|
||||||
|
expect(mockedProps.fetchData.mock.calls[0]).toMatchInlineSnapshot(`
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"filters": Object {
|
||||||
|
"name": Object {
|
||||||
|
"filterId": "sw",
|
||||||
|
"filterValue": "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"pageIndex": 1,
|
||||||
|
"pageSize": 1,
|
||||||
|
"sortBy": Array [
|
||||||
|
Object {
|
||||||
|
"desc": false,
|
||||||
|
"id": "id",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
|
@ -68,7 +68,7 @@ describe('ExploreChartHeader', () => {
|
||||||
it('should updateChartTitleOrSaveSlice for existed slice', () => {
|
it('should updateChartTitleOrSaveSlice for existed slice', () => {
|
||||||
const newTitle = 'New Chart Title';
|
const newTitle = 'New Chart Title';
|
||||||
wrapper.instance().updateChartTitleOrSaveSlice(newTitle);
|
wrapper.instance().updateChartTitleOrSaveSlice(newTitle);
|
||||||
expect(stub.call.length).toEqual(1);
|
expect(stub.call).toHaveLength(1);
|
||||||
expect(stub).toHaveBeenCalledWith(mockProps.slice.form_data, {
|
expect(stub).toHaveBeenCalledWith(mockProps.slice.form_data, {
|
||||||
action: 'overwrite',
|
action: 'overwrite',
|
||||||
slice_name: newTitle,
|
slice_name: newTitle,
|
||||||
|
@ -79,7 +79,7 @@ describe('ExploreChartHeader', () => {
|
||||||
const newTitle = 'New Chart Title';
|
const newTitle = 'New Chart Title';
|
||||||
wrapper.setProps({ slice: undefined });
|
wrapper.setProps({ slice: undefined });
|
||||||
wrapper.instance().updateChartTitleOrSaveSlice(newTitle);
|
wrapper.instance().updateChartTitleOrSaveSlice(newTitle);
|
||||||
expect(stub.call.length).toEqual(1);
|
expect(stub.call).toHaveLength(1);
|
||||||
expect(stub).toHaveBeenCalledWith(mockProps.form_data, {
|
expect(stub).toHaveBeenCalledWith(mockProps.form_data, {
|
||||||
action: 'saveas',
|
action: 'saveas',
|
||||||
slice_name: newTitle,
|
slice_name: newTitle,
|
||||||
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
/**
|
||||||
|
* 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 React from 'react';
|
||||||
|
import { mount } from 'enzyme';
|
||||||
|
import thunk from 'redux-thunk';
|
||||||
|
import configureStore from 'redux-mock-store';
|
||||||
|
import fetchMock from 'fetch-mock';
|
||||||
|
|
||||||
|
import DashboardList from 'src/views/dashboardList/DashboardList';
|
||||||
|
import ListView from 'src/components/ListView/ListView';
|
||||||
|
|
||||||
|
// store needed for withToasts(DashboardTable)
|
||||||
|
const mockStore = configureStore([thunk]);
|
||||||
|
const store = mockStore({});
|
||||||
|
|
||||||
|
const dashboardsInfoEndpoint = 'glob:*/api/v1/dashboard/_info*';
|
||||||
|
const dashboardsEndpoint = 'glob:*/api/v1/dashboard/?*';
|
||||||
|
|
||||||
|
const mockDashboards = [...new Array(3)].map((_, i) => ({
|
||||||
|
id: i,
|
||||||
|
url: 'url',
|
||||||
|
dashboard_title: `title ${i}`,
|
||||||
|
changed_by_name: 'user',
|
||||||
|
changed_by_url: 'changed_by_url',
|
||||||
|
changed_by_fk: 1,
|
||||||
|
published: true,
|
||||||
|
changed_on: new Date().toISOString(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
fetchMock.get(dashboardsInfoEndpoint, {
|
||||||
|
permissions: ['can_list', 'can_edit'],
|
||||||
|
filters: [],
|
||||||
|
});
|
||||||
|
fetchMock.get(dashboardsEndpoint, {
|
||||||
|
result: mockDashboards,
|
||||||
|
dashboard_count: 3,
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('DashboardList', () => {
|
||||||
|
const mockedProps = {};
|
||||||
|
const wrapper = mount(<DashboardList {...mockedProps} />, {
|
||||||
|
context: { store },
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders', () => {
|
||||||
|
expect(wrapper.find(DashboardList)).toHaveLength(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders a ListView', () => {
|
||||||
|
expect(wrapper.find(ListView)).toHaveLength(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fetches info', () => {
|
||||||
|
const callsI = fetchMock.calls(/dashboard\/_info/);
|
||||||
|
expect(callsI).toHaveLength(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fetches data', () => {
|
||||||
|
wrapper.update();
|
||||||
|
const callsD = fetchMock.calls(/dashboard\/\?q/);
|
||||||
|
expect(callsD).toHaveLength(1);
|
||||||
|
expect(callsD[0][0]).toMatchInlineSnapshot(
|
||||||
|
`"/http//localhost/api/v1/dashboard/?q={%22order_column%22:%22changed_on%22,%22order_direction%22:%22desc%22,%22page%22:0,%22page_size%22:25}"`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
|
@ -21,16 +21,16 @@ import { mount } from 'enzyme';
|
||||||
import thunk from 'redux-thunk';
|
import thunk from 'redux-thunk';
|
||||||
import configureStore from 'redux-mock-store';
|
import configureStore from 'redux-mock-store';
|
||||||
import fetchMock from 'fetch-mock';
|
import fetchMock from 'fetch-mock';
|
||||||
import { Table } from 'reactable-arc';
|
|
||||||
|
|
||||||
|
import ListView from 'src/components/ListView/ListView';
|
||||||
import DashboardTable from '../../../src/welcome/DashboardTable';
|
import DashboardTable from '../../../src/welcome/DashboardTable';
|
||||||
import Loading from '../../../src/components/Loading';
|
import Loading from '../../../src/components/Loading';
|
||||||
|
|
||||||
// store needed for withToasts(TableLoader)
|
// store needed for withToasts(DashboardTable)
|
||||||
const mockStore = configureStore([thunk]);
|
const mockStore = configureStore([thunk]);
|
||||||
const store = mockStore({});
|
const store = mockStore({});
|
||||||
|
|
||||||
const dashboardsEndpoint = 'glob:*/dashboardasync/api/read*';
|
const dashboardsEndpoint = 'glob:*/api/v1/dashboard/*';
|
||||||
const mockDashboards = [{ id: 1, url: 'url', dashboard_title: 'title' }];
|
const mockDashboards = [{ id: 1, url: 'url', dashboard_title: 'title' }];
|
||||||
|
|
||||||
fetchMock.get(dashboardsEndpoint, { result: mockDashboards });
|
fetchMock.get(dashboardsEndpoint, { result: mockDashboards });
|
||||||
|
@ -48,7 +48,7 @@ describe('DashboardTable', () => {
|
||||||
expect(wrapper.find(Loading)).toHaveLength(1);
|
expect(wrapper.find(Loading)).toHaveLength(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('fetches dashboards and renders a Table', done => {
|
it('fetches dashboards and renders a ListView', done => {
|
||||||
const wrapper = setup();
|
const wrapper = setup();
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
@ -56,7 +56,7 @@ describe('DashboardTable', () => {
|
||||||
// there's a delay between response and updating state, so manually set it
|
// there's a delay between response and updating state, so manually set it
|
||||||
// rather than adding a timeout which could introduce flakiness
|
// rather than adding a timeout which could introduce flakiness
|
||||||
wrapper.setState({ dashaboards: mockDashboards });
|
wrapper.setState({ dashaboards: mockDashboards });
|
||||||
expect(wrapper.find(Table)).toHaveLength(1);
|
expect(wrapper.find(ListView)).toHaveLength(1);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,254 @@
|
||||||
|
/**
|
||||||
|
* 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 { t } from '@superset-ui/translation';
|
||||||
|
import React, { FunctionComponent, useMemo } from 'react';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Col,
|
||||||
|
DropdownButton,
|
||||||
|
FormControl,
|
||||||
|
MenuItem,
|
||||||
|
Pagination,
|
||||||
|
Row,
|
||||||
|
// @ts-ignore
|
||||||
|
} from 'react-bootstrap';
|
||||||
|
import Loading from '../Loading';
|
||||||
|
import './ListViewStyles.less';
|
||||||
|
import TableCollection from './TableCollection';
|
||||||
|
import { FetchDataConfig, FilterToggle, FilterType, FilterTypeMap, SortColumn } from './types';
|
||||||
|
import { convertFilters, removeFromList, useListViewState } from './utils';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
columns: any[];
|
||||||
|
data: any[];
|
||||||
|
count: number;
|
||||||
|
pageSize: number;
|
||||||
|
fetchData: (conf: FetchDataConfig) => any;
|
||||||
|
loading: boolean;
|
||||||
|
className?: string;
|
||||||
|
title?: string;
|
||||||
|
initialSort?: SortColumn[];
|
||||||
|
filterTypes?: FilterTypeMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ListView: FunctionComponent<Props> = ({
|
||||||
|
columns,
|
||||||
|
data,
|
||||||
|
count,
|
||||||
|
pageSize: initialPageSize,
|
||||||
|
fetchData,
|
||||||
|
loading,
|
||||||
|
initialSort = [],
|
||||||
|
className = '',
|
||||||
|
title = '',
|
||||||
|
filterTypes = {},
|
||||||
|
}) => {
|
||||||
|
const {
|
||||||
|
getTableProps,
|
||||||
|
getTableBodyProps,
|
||||||
|
headerGroups,
|
||||||
|
rows,
|
||||||
|
prepareRow,
|
||||||
|
canPreviousPage,
|
||||||
|
canNextPage,
|
||||||
|
pageCount = 1,
|
||||||
|
gotoPage,
|
||||||
|
setAllFilters,
|
||||||
|
setFilterToggles,
|
||||||
|
updateFilterToggle,
|
||||||
|
applyFilters,
|
||||||
|
filtersApplied,
|
||||||
|
state: { pageIndex, pageSize, filterToggles },
|
||||||
|
} = useListViewState({
|
||||||
|
columns,
|
||||||
|
count,
|
||||||
|
data,
|
||||||
|
fetchData,
|
||||||
|
initialPageSize,
|
||||||
|
initialSort,
|
||||||
|
});
|
||||||
|
const filterableColumns = useMemo(() => columns.filter((c) => c.filterable), [columns]);
|
||||||
|
const filterable = Boolean(columns.length);
|
||||||
|
|
||||||
|
const removeFilterAndApply = (index: number) => {
|
||||||
|
const updated = removeFromList(filterToggles, index);
|
||||||
|
setFilterToggles(updated);
|
||||||
|
setAllFilters(convertFilters(updated));
|
||||||
|
};
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return <Loading />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`superset-list-view ${className}`}>
|
||||||
|
{title && filterable && (
|
||||||
|
<div className='header'>
|
||||||
|
<Row>
|
||||||
|
<Col md={10}>
|
||||||
|
<h2>{t(title)}</h2>
|
||||||
|
</Col>
|
||||||
|
{filterable && (
|
||||||
|
<Col md={2}>
|
||||||
|
<div className='filter-dropdown'>
|
||||||
|
<DropdownButton
|
||||||
|
bsSize='small'
|
||||||
|
bsStyle={'default'}
|
||||||
|
noCaret={true}
|
||||||
|
title={(
|
||||||
|
<>
|
||||||
|
<i className='fa fa-filter text-primary' />
|
||||||
|
{' '}{t('Filter List')}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
id={'filter-picker'}
|
||||||
|
>
|
||||||
|
{filterableColumns
|
||||||
|
.map(({ id, accessor, Header }) => ({
|
||||||
|
Header,
|
||||||
|
id: id || accessor,
|
||||||
|
}))
|
||||||
|
.map((ft: FilterToggle) => (
|
||||||
|
<MenuItem
|
||||||
|
key={ft.id}
|
||||||
|
eventKey={ft}
|
||||||
|
onSelect={(fltr: FilterToggle) => {
|
||||||
|
setFilterToggles([...filterToggles, fltr]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{ft.Header}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</DropdownButton>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
)}
|
||||||
|
</Row>
|
||||||
|
<hr />
|
||||||
|
{filterToggles.map((ft, i) => (
|
||||||
|
<div key={`${ft.Header}-${i}`} className='filter-inputs'>
|
||||||
|
<Row>
|
||||||
|
<Col className='text-center filter-column' md={2}>
|
||||||
|
<span>{ft.Header}</span>
|
||||||
|
</Col>
|
||||||
|
<Col md={2}>
|
||||||
|
<FormControl
|
||||||
|
componentClass='select'
|
||||||
|
bsSize='small'
|
||||||
|
value={ft.filterId}
|
||||||
|
placeholder={filterTypes[ft.id][0].name}
|
||||||
|
onChange={(e: React.MouseEvent<HTMLInputElement>) =>
|
||||||
|
updateFilterToggle(i, { filterId: e.currentTarget.value })
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{filterTypes[ft.id] && filterTypes[ft.id].map(
|
||||||
|
({ name, operator }: FilterType) => (
|
||||||
|
<option key={name} value={operator}>
|
||||||
|
{name}
|
||||||
|
</option>
|
||||||
|
),
|
||||||
|
)}
|
||||||
|
</FormControl>
|
||||||
|
</Col>
|
||||||
|
<Col md={1} />
|
||||||
|
<Col md={4}>
|
||||||
|
<FormControl
|
||||||
|
type='text'
|
||||||
|
bsSize='small'
|
||||||
|
value={ft.filterValue || ''}
|
||||||
|
onChange={(e: React.KeyboardEvent<HTMLInputElement>) =>
|
||||||
|
updateFilterToggle(i, {
|
||||||
|
filterValue: e.currentTarget.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
<Col md={1}>
|
||||||
|
<div
|
||||||
|
className='filter-close'
|
||||||
|
role='button'
|
||||||
|
onClick={() => removeFilterAndApply(i)}
|
||||||
|
>
|
||||||
|
<i className='fa fa-close text-primary' />
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<br />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
{filterToggles.length > 0 && (
|
||||||
|
<>
|
||||||
|
<Row>
|
||||||
|
<Col md={10} />
|
||||||
|
<Col md={2}>
|
||||||
|
<Button
|
||||||
|
data-test='apply-filters'
|
||||||
|
disabled={filtersApplied ? true : false}
|
||||||
|
bsStyle='primary'
|
||||||
|
onClick={applyFilters}
|
||||||
|
bsSize='small'
|
||||||
|
>
|
||||||
|
{t('Apply')}
|
||||||
|
</Button>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<br />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
<div className='body'>
|
||||||
|
<TableCollection
|
||||||
|
getTableProps={getTableProps}
|
||||||
|
getTableBodyProps={getTableBodyProps}
|
||||||
|
prepareRow={prepareRow}
|
||||||
|
headerGroups={headerGroups}
|
||||||
|
rows={rows}
|
||||||
|
loading={loading}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className='footer'>
|
||||||
|
<Pagination
|
||||||
|
prev={canPreviousPage}
|
||||||
|
first={pageIndex > 1}
|
||||||
|
next={canNextPage}
|
||||||
|
last={pageIndex < pageCount - 2}
|
||||||
|
items={pageCount}
|
||||||
|
activePage={pageIndex + 1}
|
||||||
|
ellipsis={true}
|
||||||
|
boundaryLinks={true}
|
||||||
|
maxButtons={5}
|
||||||
|
onSelect={(p: number) => gotoPage(p - 1)}
|
||||||
|
/>
|
||||||
|
<span className='pull-right'>
|
||||||
|
{t('showing')}{' '}
|
||||||
|
<strong>
|
||||||
|
{pageSize * pageIndex + (rows.length && 1)}-
|
||||||
|
{pageSize * pageIndex + rows.length}
|
||||||
|
</strong>{' '}
|
||||||
|
{t('of')} <strong>{count}</strong>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div >
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ListView;
|
|
@ -0,0 +1,64 @@
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
.superset-list-view {
|
||||||
|
.filter-dropdown {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-column {
|
||||||
|
height: 30px;
|
||||||
|
padding: 5px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-close {
|
||||||
|
height: 30px;
|
||||||
|
padding: 5px;
|
||||||
|
|
||||||
|
i {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-row-loader {
|
||||||
|
animation: shimmer 2s infinite;
|
||||||
|
background: linear-gradient(
|
||||||
|
to right,
|
||||||
|
#f6f7f8 0%,
|
||||||
|
#edeef1 20%,
|
||||||
|
#f6f7f8 40%,
|
||||||
|
#f6f7f8 100%
|
||||||
|
);
|
||||||
|
background-size: 1000px 100%;
|
||||||
|
|
||||||
|
span {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes shimmer {
|
||||||
|
0% {
|
||||||
|
background-position: -1000px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
background-position: 1000px 0;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,90 @@
|
||||||
|
/**
|
||||||
|
* 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 React from 'react';
|
||||||
|
import { Cell, HeaderGroup, Row } from 'react-table';
|
||||||
|
|
||||||
|
interface Props<D> {
|
||||||
|
getTableProps: (userProps?: any) => any;
|
||||||
|
getTableBodyProps: (userProps?: any) => any;
|
||||||
|
prepareRow: (row: Row<D>) => any;
|
||||||
|
headerGroups: Array<HeaderGroup<D>>;
|
||||||
|
rows: Array<Row<D>>;
|
||||||
|
loading: boolean;
|
||||||
|
}
|
||||||
|
/* tslint:disable:jsx-key */
|
||||||
|
export default function TableCollection({
|
||||||
|
getTableProps,
|
||||||
|
getTableBodyProps,
|
||||||
|
prepareRow,
|
||||||
|
headerGroups,
|
||||||
|
rows,
|
||||||
|
loading,
|
||||||
|
}: Props<any>) {
|
||||||
|
return (
|
||||||
|
<table {...getTableProps()} className='table table-hover'>
|
||||||
|
<thead>
|
||||||
|
{headerGroups.map((headerGroup) => (
|
||||||
|
<tr {...headerGroup.getHeaderGroupProps()}>
|
||||||
|
{headerGroup.headers.map((column: any) => (
|
||||||
|
<th {...column.getHeaderProps(column.getSortByToggleProps())} data-test='sort-header'>
|
||||||
|
{column.render('Header')}
|
||||||
|
{' '}
|
||||||
|
{column.sortable && (
|
||||||
|
<i
|
||||||
|
className={`text-primary fa fa-${
|
||||||
|
column.isSorted
|
||||||
|
? column.isSortedDesc
|
||||||
|
? 'sort-down'
|
||||||
|
: 'sort-up'
|
||||||
|
: 'sort'
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</th>
|
||||||
|
))}
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</thead>
|
||||||
|
<tbody {...getTableBodyProps()}>
|
||||||
|
{rows.map((row) => {
|
||||||
|
prepareRow(row);
|
||||||
|
const loadingProps = loading ? { className: 'table-row-loader' } : {};
|
||||||
|
return (
|
||||||
|
<tr
|
||||||
|
{...row.getRowProps()}
|
||||||
|
{...loadingProps}
|
||||||
|
onMouseEnter={() => row.setState && row.setState({ hover: true })}
|
||||||
|
onMouseLeave={() => row.setState && row.setState({ hover: false })}
|
||||||
|
>
|
||||||
|
{row.cells.map((cell: Cell<any>) => {
|
||||||
|
const columnCellProps = cell.column.cellProps || {};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<td {...cell.getCellProps()} {...columnCellProps}>
|
||||||
|
<span>{cell.render('Cell')}</span>
|
||||||
|
</td>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
export interface SortColumn {
|
||||||
|
id: string;
|
||||||
|
desc: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SortColumns = SortColumn[];
|
||||||
|
|
||||||
|
export interface Filter {
|
||||||
|
filterId: number;
|
||||||
|
filterValue: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FilterType {
|
||||||
|
name: string;
|
||||||
|
operator: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FilterTypeMap {
|
||||||
|
[columnId: string]: FilterType[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FilterMap {
|
||||||
|
[columnId: string]: Filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FetchDataConfig {
|
||||||
|
pageIndex: number;
|
||||||
|
pageSize: number;
|
||||||
|
sortBy: SortColumns;
|
||||||
|
filters: FilterMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FilterToggle {
|
||||||
|
id: string;
|
||||||
|
Header: string;
|
||||||
|
filterId?: number;
|
||||||
|
filterValue?: string;
|
||||||
|
}
|
|
@ -0,0 +1,172 @@
|
||||||
|
/**
|
||||||
|
* 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 { useEffect, useState } from 'react';
|
||||||
|
import {
|
||||||
|
useFilters,
|
||||||
|
usePagination,
|
||||||
|
useRowState,
|
||||||
|
useSortBy,
|
||||||
|
useTable,
|
||||||
|
} from 'react-table';
|
||||||
|
|
||||||
|
import {
|
||||||
|
JsonParam,
|
||||||
|
NumberParam,
|
||||||
|
StringParam,
|
||||||
|
useQueryParams,
|
||||||
|
} from 'use-query-params';
|
||||||
|
|
||||||
|
import { FetchDataConfig, FilterToggle, SortColumn } from './types';
|
||||||
|
|
||||||
|
// removes element from a list, returns new list
|
||||||
|
export function removeFromList(list: any[], index: number): any[] {
|
||||||
|
return list.filter((_, i) => index !== i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// apply update to elements of object list, returns new list
|
||||||
|
function updateInList(list: any[], index: number, update: any): any[] {
|
||||||
|
const element = list.find((_, i) => index === i);
|
||||||
|
|
||||||
|
return [
|
||||||
|
...list.slice(0, index),
|
||||||
|
{ ...element, ...update },
|
||||||
|
...list.slice(index + 1),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert filters from UI objects to data objects
|
||||||
|
export function convertFilters(fts: FilterToggle[]) {
|
||||||
|
return fts
|
||||||
|
.filter((ft: FilterToggle) => ft.filterValue)
|
||||||
|
.reduce((acc, ft) => {
|
||||||
|
acc[ft.id] = {
|
||||||
|
filterId: ft.filterId || 'sw',
|
||||||
|
filterValue: ft.filterValue,
|
||||||
|
};
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UseListViewConfig {
|
||||||
|
fetchData: (conf: FetchDataConfig) => any;
|
||||||
|
columns: any[];
|
||||||
|
data: any[];
|
||||||
|
count: number;
|
||||||
|
initialPageSize: number;
|
||||||
|
initialSort?: SortColumn[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useListViewState({
|
||||||
|
fetchData,
|
||||||
|
columns,
|
||||||
|
data,
|
||||||
|
count,
|
||||||
|
initialPageSize,
|
||||||
|
initialSort = [],
|
||||||
|
}: UseListViewConfig) {
|
||||||
|
const [query, setQuery] = useQueryParams({
|
||||||
|
filters: JsonParam,
|
||||||
|
pageIndex: NumberParam,
|
||||||
|
sortColumn: StringParam,
|
||||||
|
sortOrder: StringParam,
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
getTableProps,
|
||||||
|
getTableBodyProps,
|
||||||
|
headerGroups,
|
||||||
|
rows,
|
||||||
|
prepareRow,
|
||||||
|
canPreviousPage,
|
||||||
|
canNextPage,
|
||||||
|
pageCount,
|
||||||
|
gotoPage,
|
||||||
|
setAllFilters,
|
||||||
|
state: { pageIndex, pageSize, sortBy, filters },
|
||||||
|
} = useTable(
|
||||||
|
{
|
||||||
|
columns,
|
||||||
|
count,
|
||||||
|
data,
|
||||||
|
disableSortRemove: true,
|
||||||
|
initialState: {
|
||||||
|
filters: convertFilters(query.filters || []),
|
||||||
|
pageIndex: query.pageIndex || 0,
|
||||||
|
pageSize: initialPageSize,
|
||||||
|
sortBy:
|
||||||
|
query.sortColumn && query.sortOrder
|
||||||
|
? [{ id: query.sortColumn, desc: query.sortOrder === 'desc' }]
|
||||||
|
: initialSort,
|
||||||
|
},
|
||||||
|
manualFilters: true,
|
||||||
|
manualPagination: true,
|
||||||
|
manualSorting: true,
|
||||||
|
pageCount: Math.ceil(count / initialPageSize),
|
||||||
|
},
|
||||||
|
useFilters,
|
||||||
|
useSortBy,
|
||||||
|
usePagination,
|
||||||
|
useRowState,
|
||||||
|
);
|
||||||
|
|
||||||
|
const [filterToggles, setFilterToggles] = useState<FilterToggle[]>(
|
||||||
|
query.filters || [],
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const queryParams: any = {
|
||||||
|
filters: filterToggles,
|
||||||
|
pageIndex,
|
||||||
|
};
|
||||||
|
if (sortBy[0]) {
|
||||||
|
queryParams.sortColumn = sortBy[0].id;
|
||||||
|
queryParams.sortOrder = sortBy[0].desc ? 'desc' : 'asc';
|
||||||
|
}
|
||||||
|
setQuery(queryParams);
|
||||||
|
|
||||||
|
fetchData({ pageIndex, pageSize, sortBy, filters });
|
||||||
|
}, [fetchData, pageIndex, pageSize, sortBy, filters]);
|
||||||
|
|
||||||
|
const filtersApplied = filterToggles.every(
|
||||||
|
({ id, filterValue, filterId = 'sw' }) =>
|
||||||
|
id &&
|
||||||
|
filters[id] &&
|
||||||
|
filters[id].filterValue === filterValue &&
|
||||||
|
filters[id].filterId === filterId,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
applyFilters: () => setAllFilters(convertFilters(filterToggles)),
|
||||||
|
canNextPage,
|
||||||
|
canPreviousPage,
|
||||||
|
filtersApplied,
|
||||||
|
getTableBodyProps,
|
||||||
|
getTableProps,
|
||||||
|
gotoPage,
|
||||||
|
headerGroups,
|
||||||
|
pageCount,
|
||||||
|
prepareRow,
|
||||||
|
rows,
|
||||||
|
setAllFilters,
|
||||||
|
setFilterToggles,
|
||||||
|
state: { pageIndex, pageSize, sortBy, filters, filterToggles },
|
||||||
|
updateFilterToggle: (index: number, update: object) =>
|
||||||
|
setFilterToggles(updateInList(filterToggles, index, update)),
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,243 @@
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Type definitions for react-table 7
|
||||||
|
// Project: https://github.com/tannerlinsley/react-table#readme
|
||||||
|
// Definitions by: Adrien Denat <https://github.com/grsmto>
|
||||||
|
// Artem Berdyshev <https://github.com/berdyshev>
|
||||||
|
// Christian Murphy <https://github.com/ChristianMurphy>
|
||||||
|
// Tai Dupreee <https://github.com/nytai>
|
||||||
|
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
|
||||||
|
// TypeScript Version: 3.0
|
||||||
|
declare module 'react-table' {
|
||||||
|
import { Dispatch, ReactNode, SetStateAction } from 'react';
|
||||||
|
|
||||||
|
export interface Cell<D> {
|
||||||
|
render: (type: string) => any;
|
||||||
|
getCellProps: () => { key: string; [k: string]: any };
|
||||||
|
column: Column<D>;
|
||||||
|
row: Row<D>;
|
||||||
|
state: any;
|
||||||
|
value: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Row<D> {
|
||||||
|
index: number;
|
||||||
|
cells: Array<Cell<D>>;
|
||||||
|
getRowProps: () => { key: string; [k: string]: any };
|
||||||
|
original: any;
|
||||||
|
state?: any;
|
||||||
|
setState?: (state: any) => any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HeaderColumn<D, A extends keyof D = never> {
|
||||||
|
/**
|
||||||
|
* This string/function is used to build the data model for your column.
|
||||||
|
*/
|
||||||
|
accessor: A | ((originalRow: D) => string);
|
||||||
|
Header?: string | ((props: TableInstance<D>) => ReactNode);
|
||||||
|
Filter?: string | ((props: TableInstance<D>) => ReactNode);
|
||||||
|
Cell?: string | ((cell: Cell<D>) => ReactNode);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the unique ID for the column. It is used by reference in things like sorting, grouping, filtering etc.
|
||||||
|
*/
|
||||||
|
id?: string | number;
|
||||||
|
minWidth?: string | number;
|
||||||
|
maxWidth?: string | number;
|
||||||
|
width?: string | number;
|
||||||
|
canSortBy?: boolean;
|
||||||
|
sortByFn?: (a: any, b: any, desc: boolean) => 0 | 1 | -1;
|
||||||
|
defaultSortDesc?: boolean;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Column<D, A extends keyof D = never>
|
||||||
|
extends HeaderColumn<D, A> {
|
||||||
|
id: string | number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Page<D> = Array<Row<D>>;
|
||||||
|
|
||||||
|
export interface EnhancedColumn<D, A extends keyof D = never>
|
||||||
|
extends Column<D, A> {
|
||||||
|
render: (type: string) => any;
|
||||||
|
getHeaderProps: (userProps?: any) => any;
|
||||||
|
getSortByToggleProps: (userProps?: any) => any;
|
||||||
|
sorted: boolean;
|
||||||
|
sortedDesc: boolean;
|
||||||
|
sortedIndex: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HeaderGroup<D, A extends keyof D = never> {
|
||||||
|
headers: Array<EnhancedColumn<D, A>>;
|
||||||
|
getRowProps: (userProps?: any) => any;
|
||||||
|
getHeaderGroupProps: (userProps?: any) => any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Hooks<D> {
|
||||||
|
beforeRender: [];
|
||||||
|
columns: [];
|
||||||
|
headerGroups: [];
|
||||||
|
headers: [];
|
||||||
|
rows: Array<Row<D>>;
|
||||||
|
row: [];
|
||||||
|
renderableRows: [];
|
||||||
|
getTableProps: [];
|
||||||
|
getRowProps: [];
|
||||||
|
getHeaderRowProps: [];
|
||||||
|
getHeaderProps: [];
|
||||||
|
getCellProps: [];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TableInstance<D>
|
||||||
|
extends TableOptions<D>,
|
||||||
|
UseRowsValues<D>,
|
||||||
|
UseFiltersValues,
|
||||||
|
UsePaginationValues<D>,
|
||||||
|
UseColumnsValues<D>,
|
||||||
|
UseRowStateValues<D> {
|
||||||
|
hooks: Hooks<D>;
|
||||||
|
rows: Array<Row<D>>;
|
||||||
|
columns: Array<EnhancedColumn<D>>;
|
||||||
|
getTableProps: (userProps?: any) => any;
|
||||||
|
getTableBodyProps: (userProps?: any) => any;
|
||||||
|
getRowProps: (userProps?: any) => any;
|
||||||
|
prepareRow: (row: Row<D>) => any;
|
||||||
|
getSelectRowToggleProps: (userProps?: any) => any;
|
||||||
|
toggleSelectAll: (forcedState: boolean) => any;
|
||||||
|
state: { [key: string]: any };
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TableOptions<D> {
|
||||||
|
data: D[];
|
||||||
|
columns: Array<HeaderColumn<D>>;
|
||||||
|
state?: { [key: string]: any };
|
||||||
|
debug?: boolean;
|
||||||
|
sortByFn?: (a: any, b: any, desc: boolean) => 0 | 1 | -1;
|
||||||
|
manualSorting?: boolean;
|
||||||
|
manualFilters?: boolean;
|
||||||
|
manualPagination?: boolean;
|
||||||
|
pageCount?: number;
|
||||||
|
disableSorting?: boolean;
|
||||||
|
defaultSortDesc?: boolean;
|
||||||
|
disableMultiSort?: boolean;
|
||||||
|
count?: number;
|
||||||
|
disableSortRemove?: boolean;
|
||||||
|
initialState?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RowsProps {
|
||||||
|
subRowsKey: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FiltersProps {
|
||||||
|
filterFn: () => void;
|
||||||
|
manualFilters: boolean;
|
||||||
|
disableFilters: boolean;
|
||||||
|
setFilter: (columnId: string, filter: string) => any;
|
||||||
|
setAllFilters: (filterObj: any) => any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UsePaginationValues<D> {
|
||||||
|
nextPage: () => any;
|
||||||
|
previousPage: () => any;
|
||||||
|
setPageSize: (size: number) => any;
|
||||||
|
gotoPage: (page: number) => any;
|
||||||
|
canPreviousPage: boolean;
|
||||||
|
canNextPage: boolean;
|
||||||
|
page: Page<D>;
|
||||||
|
pageOptions: [];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UseRowsValues<D> {
|
||||||
|
rows: Array<Row<D>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UseColumnsValues<D> {
|
||||||
|
columns: Array<EnhancedColumn<D>>;
|
||||||
|
headerGroups: Array<HeaderGroup<D>>;
|
||||||
|
headers: Array<EnhancedColumn<D>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UseFiltersValues {
|
||||||
|
setFilter: (columnId: string, filter: string) => any;
|
||||||
|
setAllFilters: (filterObj: any) => any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UseRowStateValues<D> {
|
||||||
|
setRowState: (rowPath: string[], updater: (state: any) => any) => any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useTable<D>(
|
||||||
|
props: TableOptions<D>,
|
||||||
|
...plugins: any[]
|
||||||
|
): TableInstance<D>;
|
||||||
|
|
||||||
|
export function useColumns<D>(
|
||||||
|
props: TableOptions<D>,
|
||||||
|
): TableOptions<D> & UseColumnsValues<D>;
|
||||||
|
|
||||||
|
export function useRows<D>(
|
||||||
|
props: TableOptions<D>,
|
||||||
|
): TableOptions<D> & UseRowsValues<D>;
|
||||||
|
|
||||||
|
export function useFilters<D>(
|
||||||
|
props: TableOptions<D>,
|
||||||
|
): TableOptions<D> & {
|
||||||
|
rows: Array<Row<D>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function useSortBy<D>(
|
||||||
|
props: TableOptions<D>,
|
||||||
|
): TableOptions<D> & {
|
||||||
|
rows: Array<Row<D>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function useGroupBy<D>(
|
||||||
|
props: TableOptions<D>,
|
||||||
|
): TableOptions<D> & { rows: Array<Row<D>> };
|
||||||
|
|
||||||
|
export function usePagination<D>(
|
||||||
|
props: TableOptions<D>,
|
||||||
|
): UsePaginationValues<D>;
|
||||||
|
|
||||||
|
export function useRowState<D>(props: TableOptions<D>): UseRowStateValues<D>;
|
||||||
|
|
||||||
|
export function useFlexLayout<D>(props: TableOptions<D>): TableOptions<D>;
|
||||||
|
|
||||||
|
export function useExpanded<D>(
|
||||||
|
props: TableOptions<D>,
|
||||||
|
): TableOptions<D> & {
|
||||||
|
toggleExpandedByPath: () => any;
|
||||||
|
expandedDepth: [];
|
||||||
|
rows: [];
|
||||||
|
};
|
||||||
|
|
||||||
|
export function useTableState(
|
||||||
|
initialState?: any,
|
||||||
|
overriddenState?: any,
|
||||||
|
options?: {
|
||||||
|
reducer?: (oldState: any, newState: any, type: string) => any;
|
||||||
|
useState?: [any, Dispatch<SetStateAction<any>>];
|
||||||
|
},
|
||||||
|
): any;
|
||||||
|
|
||||||
|
export const actions: any;
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
.dashboard-list-view {
|
||||||
|
.actions {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button {
|
||||||
|
margin: 0 8px;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,294 @@
|
||||||
|
/**
|
||||||
|
* 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 { SupersetClient } from '@superset-ui/connection';
|
||||||
|
import { t } from '@superset-ui/translation';
|
||||||
|
import moment from 'moment';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
// @ts-ignore
|
||||||
|
import { Button, Modal, Panel } from 'react-bootstrap';
|
||||||
|
import ListView from 'src/components/ListView/ListView';
|
||||||
|
import { FilterTypeMap } from 'src/components/ListView/types';
|
||||||
|
import { FetchDataConfig } from 'src/components/ListView/types';
|
||||||
|
import withToasts from 'src/messageToasts/enhancers/withToasts';
|
||||||
|
|
||||||
|
import './DashboardList.less';
|
||||||
|
|
||||||
|
const PAGE_SIZE = 25;
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
addDangerToast: (msg: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
dashboards: any[];
|
||||||
|
dashboardCount: number;
|
||||||
|
loading: boolean;
|
||||||
|
showDeleteModal: boolean;
|
||||||
|
deleteCandidate: any;
|
||||||
|
filterTypes: FilterTypeMap;
|
||||||
|
permissions: string[];
|
||||||
|
labelColumns: { [key: string]: string };
|
||||||
|
}
|
||||||
|
class DashboardList extends React.PureComponent<Props, State> {
|
||||||
|
|
||||||
|
get canEdit() {
|
||||||
|
return this.hasPerm('can_edit');
|
||||||
|
}
|
||||||
|
|
||||||
|
get canDelete() {
|
||||||
|
return this.hasPerm('can_delete');
|
||||||
|
}
|
||||||
|
|
||||||
|
public static propTypes = {
|
||||||
|
addDangerToast: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
public state: State = {
|
||||||
|
dashboardCount: 0,
|
||||||
|
dashboards: [],
|
||||||
|
deleteCandidate: {},
|
||||||
|
filterTypes: {},
|
||||||
|
labelColumns: {},
|
||||||
|
loading: false,
|
||||||
|
permissions: [],
|
||||||
|
showDeleteModal: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
public columns: any = [];
|
||||||
|
|
||||||
|
public initialSort = [{ id: 'changed_on', desc: true }];
|
||||||
|
|
||||||
|
constructor(props: Props) {
|
||||||
|
super(props);
|
||||||
|
this.setColumns();
|
||||||
|
}
|
||||||
|
|
||||||
|
public setColumns = () => {
|
||||||
|
this.columns = [
|
||||||
|
{
|
||||||
|
Cell: ({
|
||||||
|
row: {
|
||||||
|
original: { url, dashboard_title },
|
||||||
|
},
|
||||||
|
}: any) => <a href={url}>{dashboard_title}</a>,
|
||||||
|
Header: this.state.labelColumns.dashboard_title || '',
|
||||||
|
accessor: 'dashboard_title',
|
||||||
|
filterable: true,
|
||||||
|
sortable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Cell: ({
|
||||||
|
row: {
|
||||||
|
original: { changed_by_name, changed_by_url },
|
||||||
|
},
|
||||||
|
}: any) => <a href={changed_by_url}>{changed_by_name}</a>,
|
||||||
|
Header: this.state.labelColumns.changed_by_name || '',
|
||||||
|
accessor: 'changed_by_fk',
|
||||||
|
sortable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Cell: ({
|
||||||
|
row: {
|
||||||
|
original: { published },
|
||||||
|
},
|
||||||
|
}: any) => (
|
||||||
|
<span className='no-wrap'>{published ? <i className='fa fa-check' /> : ''}</span>
|
||||||
|
),
|
||||||
|
Header: this.state.labelColumns.published || '',
|
||||||
|
accessor: 'published',
|
||||||
|
sortable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Cell: ({
|
||||||
|
row: {
|
||||||
|
original: { changed_on },
|
||||||
|
},
|
||||||
|
}: any) => (
|
||||||
|
<span className='no-wrap'>{moment(changed_on).fromNow()}</span>
|
||||||
|
),
|
||||||
|
Header: this.state.labelColumns.changed_on || '',
|
||||||
|
accessor: 'changed_on',
|
||||||
|
sortable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Cell: ({ row: { state, original } }: any) => {
|
||||||
|
const handleDelete = () => this.handleDashboardDeleteConfirm(original);
|
||||||
|
const handleEdit = () => this.handleDashboardEdit(original);
|
||||||
|
if (!this.canEdit && !this.canDelete) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span className={`actions ${state && state.hover ? '' : 'invisible'}`}>
|
||||||
|
{this.canDelete && (
|
||||||
|
<span
|
||||||
|
role='button'
|
||||||
|
className='action-button'
|
||||||
|
onClick={handleDelete}
|
||||||
|
>
|
||||||
|
<i className='fa fa-trash' />
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{this.canEdit && (
|
||||||
|
<span
|
||||||
|
role='button'
|
||||||
|
className='action-button'
|
||||||
|
onClick={handleEdit}
|
||||||
|
>
|
||||||
|
<i className='fa fa-pencil' />
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
Header: 'Actions',
|
||||||
|
id: 'actions',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public hasPerm = (perm: string) => {
|
||||||
|
if (!this.state.permissions.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Boolean(this.state.permissions.find((p) => p === perm));
|
||||||
|
}
|
||||||
|
|
||||||
|
public handleDashboardEdit = ({ id }: { id: number }) => {
|
||||||
|
window.location.assign(`/dashboard/edit/${id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public handleDashboardDeleteConfirm = (dashboard: any) => {
|
||||||
|
this.setState({
|
||||||
|
deleteCandidate: dashboard,
|
||||||
|
showDeleteModal: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public handleDashboardDelete = () => {
|
||||||
|
const { id, title } = this.state.deleteCandidate;
|
||||||
|
SupersetClient.delete({
|
||||||
|
endpoint: `/api/v1/dashboard/${id}`,
|
||||||
|
}).then(
|
||||||
|
(resp) => {
|
||||||
|
const dashboards = this.state.dashboards.filter((d) => d.id !== id);
|
||||||
|
this.setState({
|
||||||
|
dashboards,
|
||||||
|
deleteCandidate: {},
|
||||||
|
showDeleteModal: false,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
(err: any) => {
|
||||||
|
this.props.addDangerToast(t('There was an issue deleting') + `${title}`);
|
||||||
|
this.setState({ showDeleteModal: false, deleteCandidate: {} });
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public toggleModal = () => {
|
||||||
|
this.setState({ showDeleteModal: !this.state.showDeleteModal });
|
||||||
|
}
|
||||||
|
|
||||||
|
public fetchData = ({
|
||||||
|
pageIndex,
|
||||||
|
pageSize,
|
||||||
|
sortBy,
|
||||||
|
filters,
|
||||||
|
}: FetchDataConfig) => {
|
||||||
|
this.setState({ loading: true });
|
||||||
|
const filterExps = Object.keys(filters).map((fk) => ({
|
||||||
|
col: fk,
|
||||||
|
opr: filters[fk].filterId,
|
||||||
|
value: filters[fk].filterValue,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const queryParams = JSON.stringify({
|
||||||
|
order_column: sortBy[0].id,
|
||||||
|
order_direction: sortBy[0].desc ? 'desc' : 'asc',
|
||||||
|
page: pageIndex,
|
||||||
|
page_size: pageSize,
|
||||||
|
...(filterExps.length ? { filters: filterExps } : {}),
|
||||||
|
});
|
||||||
|
|
||||||
|
return SupersetClient.get({
|
||||||
|
endpoint: `/api/v1/dashboard/?q=${queryParams}`,
|
||||||
|
})
|
||||||
|
.then(({ json = {} }) => {
|
||||||
|
this.setState({ dashboards: json.result, dashboardCount: json.count, labelColumns: json.label_columns });
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
this.props.addDangerToast(
|
||||||
|
t('An error occurred while fetching Dashboards'),
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.setColumns();
|
||||||
|
this.setState({ loading: false });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public componentDidMount() {
|
||||||
|
SupersetClient.get({
|
||||||
|
endpoint: `/api/v1/dashboard/_info`,
|
||||||
|
})
|
||||||
|
.then(({ json = {} }) => {
|
||||||
|
this.setState({ filterTypes: json.filters, permissions: json.permissions });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
const { dashboards, dashboardCount, loading, filterTypes } = this.state;
|
||||||
|
return (
|
||||||
|
<div className='container welcome'>
|
||||||
|
<Panel>
|
||||||
|
<ListView
|
||||||
|
className='dashboard-list-view'
|
||||||
|
title={'Dashboards'}
|
||||||
|
columns={this.columns}
|
||||||
|
data={dashboards}
|
||||||
|
count={dashboardCount}
|
||||||
|
pageSize={PAGE_SIZE}
|
||||||
|
fetchData={this.fetchData}
|
||||||
|
loading={loading}
|
||||||
|
initialSort={this.initialSort}
|
||||||
|
filterTypes={filterTypes}
|
||||||
|
/>
|
||||||
|
</Panel>
|
||||||
|
|
||||||
|
<Modal show={this.state.showDeleteModal} onHide={this.toggleModal}>
|
||||||
|
<Modal.Header closeButton={true} />
|
||||||
|
<Modal.Body>
|
||||||
|
{t('Are you sure you want to delete')}{' '}
|
||||||
|
<b>{this.state.deleteCandidate.dashboard_title}</b>?
|
||||||
|
</Modal.Body>
|
||||||
|
<Modal.Footer>
|
||||||
|
<Button onClick={this.toggleModal}>{t('Cancel')}</Button>
|
||||||
|
<Button bsStyle='danger' onClick={this.handleDashboardDelete}>
|
||||||
|
{t('OK')}
|
||||||
|
</Button>
|
||||||
|
</Modal.Footer>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withToasts(DashboardList);
|
|
@ -23,11 +23,13 @@ import { createStore, applyMiddleware, compose, combineReducers } from 'redux';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
|
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
|
||||||
|
|
||||||
|
import Menu from 'src/components/Menu/Menu';
|
||||||
|
import DashboardList from 'src/views/dashboardList/DashboardList';
|
||||||
|
|
||||||
import messageToastReducer from '../messageToasts/reducers';
|
import messageToastReducer from '../messageToasts/reducers';
|
||||||
import { initEnhancer } from '../reduxUtils';
|
import { initEnhancer } from '../reduxUtils';
|
||||||
import setupApp from '../setup/setupApp';
|
import setupApp from '../setup/setupApp';
|
||||||
import Welcome from './Welcome';
|
import Welcome from './Welcome';
|
||||||
import Menu from '../components/Menu/Menu';
|
|
||||||
import ToastPresenter from '../messageToasts/containers/ToastPresenter';
|
import ToastPresenter from '../messageToasts/containers/ToastPresenter';
|
||||||
|
|
||||||
setupApp();
|
setupApp();
|
||||||
|
@ -50,11 +52,14 @@ const App = () => (
|
||||||
<Router>
|
<Router>
|
||||||
<Menu data={menu} />
|
<Menu data={menu} />
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route path="/superset/welcome">
|
<Route path="/superset/welcome/">
|
||||||
<Welcome user={user} />
|
<Welcome user={user} />
|
||||||
<ToastPresenter />
|
</Route>
|
||||||
|
<Route path="/dashboard/list/">
|
||||||
|
<DashboardList user={user} />
|
||||||
</Route>
|
</Route>
|
||||||
</Switch>
|
</Switch>
|
||||||
|
<ToastPresenter />
|
||||||
</Router>
|
</Router>
|
||||||
</Provider>
|
</Provider>
|
||||||
);
|
);
|
||||||
|
|
|
@ -18,34 +18,106 @@
|
||||||
*/
|
*/
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Table, Tr, Td, unsafe } from 'reactable-arc';
|
|
||||||
import { SupersetClient } from '@superset-ui/connection';
|
|
||||||
import { t } from '@superset-ui/translation';
|
import { t } from '@superset-ui/translation';
|
||||||
|
import { SupersetClient } from '@superset-ui/connection';
|
||||||
|
import moment from 'moment';
|
||||||
|
import { debounce } from 'lodash';
|
||||||
|
import ListView from 'src/components/ListView/ListView';
|
||||||
|
import withToasts from 'src/messageToasts/enhancers/withToasts';
|
||||||
|
|
||||||
import withToasts from '../messageToasts/enhancers/withToasts';
|
const PAGE_SIZE = 25;
|
||||||
import Loading from '../components/Loading';
|
|
||||||
import '../../stylesheets/reactable-pagination.less';
|
|
||||||
|
|
||||||
const propTypes = {
|
|
||||||
search: PropTypes.string,
|
|
||||||
addDangerToast: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
class DashboardTable extends React.PureComponent {
|
class DashboardTable extends React.PureComponent {
|
||||||
constructor(props) {
|
static propTypes = {
|
||||||
super(props);
|
addDangerToast: PropTypes.func.isRequired,
|
||||||
this.state = {
|
search: PropTypes.string,
|
||||||
dashboards: null,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
state = {
|
||||||
|
dashboards: [],
|
||||||
|
dashboard_count: 0,
|
||||||
|
loading: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps) {
|
||||||
|
if (prevProps.search !== this.props.search) {
|
||||||
|
this.fetchDataDebounced({
|
||||||
|
pageSize: PAGE_SIZE,
|
||||||
|
pageIndex: 0,
|
||||||
|
sortBy: this.initialSort,
|
||||||
|
filters: {},
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
columns = [
|
||||||
SupersetClient.get({
|
{
|
||||||
endpoint:
|
accessor: 'dashboard_title',
|
||||||
'/dashboardasync/api/read?_oc_DashboardModelViewAsync=changed_on&_od_DashboardModelViewAsync=desc',
|
Header: 'Dashboard',
|
||||||
|
sortable: true,
|
||||||
|
Cell: ({
|
||||||
|
row: {
|
||||||
|
original: { url, dashboard_title: dashboardTitle },
|
||||||
|
},
|
||||||
|
}) => <a href={url}>{dashboardTitle}</a>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessor: 'changed_by_fk',
|
||||||
|
Header: 'Creator',
|
||||||
|
sortable: true,
|
||||||
|
Cell: ({
|
||||||
|
row: {
|
||||||
|
original: { changed_by_name: changedByName, changedByUrl },
|
||||||
|
},
|
||||||
|
}) => <a href={changedByUrl}>{changedByName}</a>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessor: 'changed_on',
|
||||||
|
Header: 'Modified',
|
||||||
|
sortable: true,
|
||||||
|
Cell: ({
|
||||||
|
row: {
|
||||||
|
original: { changed_on: changedOn },
|
||||||
|
},
|
||||||
|
}) => <span className="no-wrap">{moment(changedOn).fromNow()}</span>,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
initialSort = [{ id: 'changed_on', desc: true }];
|
||||||
|
|
||||||
|
fetchData = ({ pageIndex, pageSize, sortBy, filters }) => {
|
||||||
|
this.setState({ loading: true });
|
||||||
|
const filterExps = Object.keys(filters)
|
||||||
|
.map(fk => ({
|
||||||
|
col: fk,
|
||||||
|
opr: filters[fk].filterId,
|
||||||
|
value: filters[fk].filterValue,
|
||||||
|
}))
|
||||||
|
.concat(
|
||||||
|
this.props.search
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
col: 'dashboard_title',
|
||||||
|
opr: 'ct',
|
||||||
|
value: this.props.search,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: [],
|
||||||
|
);
|
||||||
|
|
||||||
|
const queryParams = JSON.stringify({
|
||||||
|
order_column: sortBy[0].id,
|
||||||
|
order_direction: sortBy[0].desc ? 'desc' : 'asc',
|
||||||
|
page: pageIndex,
|
||||||
|
page_size: pageSize,
|
||||||
|
...(filterExps.length ? { filters: filterExps } : {}),
|
||||||
|
});
|
||||||
|
|
||||||
|
return SupersetClient.get({
|
||||||
|
endpoint: `/api/v1/dashboard/?q=${queryParams}`,
|
||||||
})
|
})
|
||||||
.then(({ json }) => {
|
.then(({ json }) => {
|
||||||
this.setState({ dashboards: json.result });
|
this.setState({ dashboards: json.result, dashboard_count: json.count });
|
||||||
})
|
})
|
||||||
.catch(response => {
|
.catch(response => {
|
||||||
if (response.status === 401) {
|
if (response.status === 401) {
|
||||||
|
@ -59,47 +131,25 @@ class DashboardTable extends React.PureComponent {
|
||||||
t('An error occurred while fetching Dashboards'),
|
t('An error occurred while fetching Dashboards'),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
}
|
.finally(() => this.setState({ loading: false }));
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchDataDebounced = debounce(this.fetchData, 200);
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (this.state.dashboards !== null) {
|
|
||||||
return (
|
return (
|
||||||
<Table
|
<ListView
|
||||||
className="table"
|
columns={this.columns}
|
||||||
sortable={['dashboard', 'creator', 'modified']}
|
data={this.state.dashboards}
|
||||||
filterBy={this.props.search}
|
count={this.state.dashboard_count}
|
||||||
filterable={['dashboard', 'creator']}
|
pageSize={PAGE_SIZE}
|
||||||
itemsPerPage={50}
|
fetchData={this.fetchData}
|
||||||
hideFilterInput
|
loading={this.state.loading}
|
||||||
columns={[
|
initialSort={this.initialSort}
|
||||||
{ key: 'dashboard', label: 'Dashboard' },
|
/>
|
||||||
{ key: 'creator', label: 'Creator' },
|
|
||||||
{ key: 'modified', label: 'Modified' },
|
|
||||||
]}
|
|
||||||
defaultSort={{ column: 'modified', direction: 'desc' }}
|
|
||||||
>
|
|
||||||
{this.state.dashboards.map(o => (
|
|
||||||
<Tr key={o.id}>
|
|
||||||
<Td column="dashboard" value={o.dashboard_title}>
|
|
||||||
<a href={o.url}>{o.dashboard_title}</a>
|
|
||||||
</Td>
|
|
||||||
<Td column="creator" value={o.changed_by_name}>
|
|
||||||
{unsafe(o.creator)}
|
|
||||||
</Td>
|
|
||||||
<Td column="modified" value={o.changed_on} className="text-muted">
|
|
||||||
{unsafe(o.modified)}
|
|
||||||
</Td>
|
|
||||||
</Tr>
|
|
||||||
))}
|
|
||||||
</Table>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return <Loading />;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DashboardTable.propTypes = propTypes;
|
|
||||||
|
|
||||||
export default withToasts(DashboardTable);
|
export default withToasts(DashboardTable);
|
||||||
|
|
|
@ -16,10 +16,11 @@
|
||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import React from 'react';
|
import React, { useState } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Panel, Row, Col, Tabs, Tab, FormControl } from 'react-bootstrap';
|
import { Panel, Row, Col, Tabs, Tab, FormControl } from 'react-bootstrap';
|
||||||
import { t } from '@superset-ui/translation';
|
import { t } from '@superset-ui/translation';
|
||||||
|
import { useQueryParam, StringParam } from 'use-query-params';
|
||||||
import RecentActivity from '../profile/components/RecentActivity';
|
import RecentActivity from '../profile/components/RecentActivity';
|
||||||
import Favorites from '../profile/components/Favorites';
|
import Favorites from '../profile/components/Favorites';
|
||||||
import DashboardTable from './DashboardTable';
|
import DashboardTable from './DashboardTable';
|
||||||
|
@ -28,22 +29,39 @@ const propTypes = {
|
||||||
user: PropTypes.object.isRequired,
|
user: PropTypes.object.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class Welcome extends React.PureComponent {
|
function useSyncQueryState(queryParam, queryParamType, defaultState) {
|
||||||
constructor(props) {
|
const [queryState, setQueryState] = useQueryParam(queryParam, queryParamType);
|
||||||
super(props);
|
const [state, setState] = useState(queryState || defaultState);
|
||||||
this.state = {
|
|
||||||
search: '',
|
const setQueryStateAndState = val => {
|
||||||
|
setQueryState(val);
|
||||||
|
setState(val);
|
||||||
};
|
};
|
||||||
this.onSearchChange = this.onSearchChange.bind(this);
|
|
||||||
}
|
return [state, setQueryStateAndState];
|
||||||
onSearchChange(event) {
|
}
|
||||||
this.setState({ search: event.target.value });
|
|
||||||
}
|
export default function Welcome({ user }) {
|
||||||
render() {
|
const [activeTab, setActiveTab] = useSyncQueryState(
|
||||||
|
'activeTab',
|
||||||
|
StringParam,
|
||||||
|
'all',
|
||||||
|
);
|
||||||
|
|
||||||
|
const [searchQuery, setSearchQuery] = useSyncQueryState(
|
||||||
|
'search',
|
||||||
|
StringParam,
|
||||||
|
'',
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container welcome">
|
<div className="container welcome">
|
||||||
<Tabs defaultActiveKey={1} id="uncontrolled-tab-example">
|
<Tabs
|
||||||
<Tab eventKey={1} title={t('Dashboards')}>
|
activeKey={activeTab}
|
||||||
|
onSelect={setActiveTab}
|
||||||
|
id="uncontrolled-tab-example"
|
||||||
|
>
|
||||||
|
<Tab eventKey="all" title={t('Dashboards')}>
|
||||||
<Panel>
|
<Panel>
|
||||||
<Row>
|
<Row>
|
||||||
<Col md={8}>
|
<Col md={8}>
|
||||||
|
@ -55,16 +73,16 @@ export default class Welcome extends React.PureComponent {
|
||||||
bsSize="sm"
|
bsSize="sm"
|
||||||
style={{ marginTop: '25px' }}
|
style={{ marginTop: '25px' }}
|
||||||
placeholder="Search"
|
placeholder="Search"
|
||||||
value={this.state.search}
|
value={searchQuery}
|
||||||
onChange={this.onSearchChange}
|
onChange={e => setSearchQuery(e.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<hr />
|
<hr />
|
||||||
<DashboardTable search={this.state.search} />
|
<DashboardTable search={searchQuery} />
|
||||||
</Panel>
|
</Panel>
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab eventKey={2} title={t('Recently Viewed')}>
|
<Tab eventKey="recent" title={t('Recently Viewed')}>
|
||||||
<Panel>
|
<Panel>
|
||||||
<Row>
|
<Row>
|
||||||
<Col md={8}>
|
<Col md={8}>
|
||||||
|
@ -72,10 +90,10 @@ export default class Welcome extends React.PureComponent {
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<hr />
|
<hr />
|
||||||
<RecentActivity user={this.props.user} />
|
<RecentActivity user={user} />
|
||||||
</Panel>
|
</Panel>
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab eventKey={3} title={t('Favorites')}>
|
<Tab eventKey="favorites" title={t('Favorites')}>
|
||||||
<Panel>
|
<Panel>
|
||||||
<Row>
|
<Row>
|
||||||
<Col md={8}>
|
<Col md={8}>
|
||||||
|
@ -83,13 +101,12 @@ export default class Welcome extends React.PureComponent {
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<hr />
|
<hr />
|
||||||
<Favorites user={this.props.user} />
|
<Favorites user={user} />
|
||||||
</Panel>
|
</Panel>
|
||||||
</Tab>
|
</Tab>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Welcome.propTypes = propTypes;
|
Welcome.propTypes = propTypes;
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
"outDir": "./dist",
|
"outDir": "./dist",
|
||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
"target": "es5",
|
"target": "es5",
|
||||||
"lib": ["es6", "dom"],
|
"lib": ["es6", "dom", "es2018.promise"],
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"jsx": "react",
|
"jsx": "react",
|
||||||
|
@ -17,7 +17,8 @@
|
||||||
"strictNullChecks": true,
|
"strictNullChecks": true,
|
||||||
"suppressImplicitAnyIndexErrors": true,
|
"suppressImplicitAnyIndexErrors": true,
|
||||||
"noUnusedLocals": true,
|
"noUnusedLocals": true,
|
||||||
"skipLibCheck": true
|
"skipLibCheck": true,
|
||||||
|
"esModuleInterop": true
|
||||||
},
|
},
|
||||||
"include": ["./src/**/*", "./spec/**/*"]
|
"include": ["./src/**/*", "./spec/**/*"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
{
|
{
|
||||||
"extends": ["tslint:recommended", "tslint-react"],
|
"extends": ["tslint:recommended", "tslint-react"],
|
||||||
"jsRules": {
|
"jsRules": {},
|
||||||
},
|
|
||||||
"rules": {
|
"rules": {
|
||||||
"interface-name" : [true, "never-prefix"],
|
"interface-name": [true, "never-prefix"],
|
||||||
"quotemark": [true, "single"]
|
"quotemark": [true, "single"],
|
||||||
|
"jsx-no-multiline-js": false,
|
||||||
|
"jsx-no-lambda": false
|
||||||
},
|
},
|
||||||
"rulesDirectory": []
|
"rulesDirectory": []
|
||||||
}
|
}
|
|
@ -191,13 +191,6 @@ class Dashboard( # pylint: disable=too-many-instance-attributes
|
||||||
return ""
|
return ""
|
||||||
return f"/superset/profile/{self.changed_by.username}"
|
return f"/superset/profile/{self.changed_by.username}"
|
||||||
|
|
||||||
@property
|
|
||||||
def owners_json(self) -> List[Dict[str, Any]]:
|
|
||||||
owners = []
|
|
||||||
for owner in self.owners:
|
|
||||||
owners.append({"name": owner.name})
|
|
||||||
return owners
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def data(self) -> Dict[str, Any]:
|
def data(self) -> Dict[str, Any]:
|
||||||
positions = self.position_json
|
positions = self.position_json
|
||||||
|
|
|
@ -180,7 +180,6 @@ class DashboardRestApi(DashboardMixin, BaseSupersetModelRestApi):
|
||||||
"info": "list",
|
"info": "list",
|
||||||
"related": "list",
|
"related": "list",
|
||||||
}
|
}
|
||||||
exclude_route_methods = ("info",)
|
|
||||||
show_columns = [
|
show_columns = [
|
||||||
"dashboard_title",
|
"dashboard_title",
|
||||||
"slug",
|
"slug",
|
||||||
|
@ -199,7 +198,6 @@ class DashboardRestApi(DashboardMixin, BaseSupersetModelRestApi):
|
||||||
"dashboard_title",
|
"dashboard_title",
|
||||||
"url",
|
"url",
|
||||||
"published",
|
"published",
|
||||||
"owners_json",
|
|
||||||
"changed_by.username",
|
"changed_by.username",
|
||||||
"changed_by_name",
|
"changed_by_name",
|
||||||
"changed_by_url",
|
"changed_by_url",
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
# KIND, either express or implied. See the License for the
|
# KIND, either express or implied. See the License for the
|
||||||
# specific language governing permissions and limitations
|
# specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
import json
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from flask import g, redirect, request, Response
|
from flask import g, redirect, request, Response
|
||||||
|
@ -30,10 +31,12 @@ from superset.utils import core as utils
|
||||||
from ..base import (
|
from ..base import (
|
||||||
BaseSupersetView,
|
BaseSupersetView,
|
||||||
check_ownership,
|
check_ownership,
|
||||||
|
common_bootstrap_payload,
|
||||||
DeleteMixin,
|
DeleteMixin,
|
||||||
generate_download_headers,
|
generate_download_headers,
|
||||||
SupersetModelView,
|
SupersetModelView,
|
||||||
)
|
)
|
||||||
|
from ..utils import bootstrap_user_data
|
||||||
from .mixin import DashboardMixin
|
from .mixin import DashboardMixin
|
||||||
|
|
||||||
|
|
||||||
|
@ -43,6 +46,21 @@ class DashboardModelView(
|
||||||
route_base = "/dashboard"
|
route_base = "/dashboard"
|
||||||
datamodel = SQLAInterface(models.Dashboard)
|
datamodel = SQLAInterface(models.Dashboard)
|
||||||
|
|
||||||
|
@has_access
|
||||||
|
@expose("/list/")
|
||||||
|
def list(self):
|
||||||
|
payload = {
|
||||||
|
"user": bootstrap_user_data(g.user),
|
||||||
|
"common": common_bootstrap_payload(),
|
||||||
|
}
|
||||||
|
return self.render_template(
|
||||||
|
"superset/welcome.html",
|
||||||
|
entry="welcome",
|
||||||
|
bootstrap_data=json.dumps(
|
||||||
|
payload, default=utils.pessimistic_json_iso_dttm_ser
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
@action("mulexport", __("Export"), __("Export dashboards?"), "fa-database")
|
@action("mulexport", __("Export"), __("Export dashboards?"), "fa-database")
|
||||||
def mulexport(self, items): # pylint: disable=no-self-use
|
def mulexport(self, items): # pylint: disable=no-self-use
|
||||||
if not isinstance(items, list):
|
if not isinstance(items, list):
|
||||||
|
|
|
@ -36,6 +36,8 @@ if not app.config["ENABLE_JAVASCRIPT_CONTROLS"]:
|
||||||
|
|
||||||
|
|
||||||
def bootstrap_user_data(user, include_perms=False):
|
def bootstrap_user_data(user, include_perms=False):
|
||||||
|
if user.is_anonymous:
|
||||||
|
return {}
|
||||||
payload = {
|
payload = {
|
||||||
"username": user.username,
|
"username": user.username,
|
||||||
"firstName": user.first_name,
|
"firstName": user.first_name,
|
||||||
|
|
|
@ -308,7 +308,7 @@ class DashboardTests(SupersetTestCase):
|
||||||
resp = self.get_resp("/chart/list/")
|
resp = self.get_resp("/chart/list/")
|
||||||
self.assertNotIn("birth_names</a>", resp)
|
self.assertNotIn("birth_names</a>", resp)
|
||||||
|
|
||||||
resp = self.get_resp("/dashboard/list/")
|
resp = self.get_resp("/api/v1/dashboard/")
|
||||||
self.assertNotIn("/superset/dashboard/births/", resp)
|
self.assertNotIn("/superset/dashboard/births/", resp)
|
||||||
|
|
||||||
self.grant_public_access_to_table(table)
|
self.grant_public_access_to_table(table)
|
||||||
|
@ -316,7 +316,7 @@ class DashboardTests(SupersetTestCase):
|
||||||
# Try access after adding appropriate permissions.
|
# Try access after adding appropriate permissions.
|
||||||
self.assertIn("birth_names", self.get_resp("/chart/list/"))
|
self.assertIn("birth_names", self.get_resp("/chart/list/"))
|
||||||
|
|
||||||
resp = self.get_resp("/dashboard/list/")
|
resp = self.get_resp("/api/v1/dashboard/")
|
||||||
self.assertIn("/superset/dashboard/births/", resp)
|
self.assertIn("/superset/dashboard/births/", resp)
|
||||||
|
|
||||||
self.assertIn("Births", self.get_resp("/superset/dashboard/births/"))
|
self.assertIn("Births", self.get_resp("/superset/dashboard/births/"))
|
||||||
|
@ -325,7 +325,7 @@ class DashboardTests(SupersetTestCase):
|
||||||
resp = self.get_resp("/chart/list/")
|
resp = self.get_resp("/chart/list/")
|
||||||
self.assertNotIn("wb_health_population</a>", resp)
|
self.assertNotIn("wb_health_population</a>", resp)
|
||||||
|
|
||||||
resp = self.get_resp("/dashboard/list/")
|
resp = self.get_resp("/api/v1/dashboard/")
|
||||||
self.assertNotIn("/superset/dashboard/world_health/", resp)
|
self.assertNotIn("/superset/dashboard/world_health/", resp)
|
||||||
|
|
||||||
def test_dashboard_with_created_by_can_be_accessed_by_public_users(self):
|
def test_dashboard_with_created_by_can_be_accessed_by_public_users(self):
|
||||||
|
@ -374,7 +374,7 @@ class DashboardTests(SupersetTestCase):
|
||||||
gamma_user = security_manager.find_user("gamma")
|
gamma_user = security_manager.find_user("gamma")
|
||||||
self.login(gamma_user.username)
|
self.login(gamma_user.username)
|
||||||
|
|
||||||
resp = self.get_resp("/dashboard/list/")
|
resp = self.get_resp("/api/v1/dashboard/")
|
||||||
self.assertNotIn("/superset/dashboard/empty_dashboard/", resp)
|
self.assertNotIn("/superset/dashboard/empty_dashboard/", resp)
|
||||||
|
|
||||||
def test_users_can_view_published_dashboard(self):
|
def test_users_can_view_published_dashboard(self):
|
||||||
|
@ -404,7 +404,7 @@ class DashboardTests(SupersetTestCase):
|
||||||
db.session.merge(hidden_dash)
|
db.session.merge(hidden_dash)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
resp = self.get_resp("/dashboard/list/")
|
resp = self.get_resp("/api/v1/dashboard/")
|
||||||
self.assertNotIn(f"/superset/dashboard/{hidden_dash_slug}/", resp)
|
self.assertNotIn(f"/superset/dashboard/{hidden_dash_slug}/", resp)
|
||||||
self.assertIn(f"/superset/dashboard/{published_dash_slug}/", resp)
|
self.assertIn(f"/superset/dashboard/{published_dash_slug}/", resp)
|
||||||
|
|
||||||
|
@ -432,7 +432,7 @@ class DashboardTests(SupersetTestCase):
|
||||||
|
|
||||||
self.login(user.username)
|
self.login(user.username)
|
||||||
|
|
||||||
resp = self.get_resp("/dashboard/list/")
|
resp = self.get_resp("/api/v1/dashboard/")
|
||||||
self.assertIn(f"/superset/dashboard/{my_dash_slug}/", resp)
|
self.assertIn(f"/superset/dashboard/{my_dash_slug}/", resp)
|
||||||
self.assertNotIn(f"/superset/dashboard/{not_my_dash_slug}/", resp)
|
self.assertNotIn(f"/superset/dashboard/{not_my_dash_slug}/", resp)
|
||||||
|
|
||||||
|
@ -465,7 +465,7 @@ class DashboardTests(SupersetTestCase):
|
||||||
|
|
||||||
self.login(user.username)
|
self.login(user.username)
|
||||||
|
|
||||||
resp = self.get_resp("/dashboard/list/")
|
resp = self.get_resp("/api/v1/dashboard/")
|
||||||
self.assertIn(f"/superset/dashboard/{fav_dash_slug}/", resp)
|
self.assertIn(f"/superset/dashboard/{fav_dash_slug}/", resp)
|
||||||
|
|
||||||
def test_user_can_not_view_unpublished_dash(self):
|
def test_user_can_not_view_unpublished_dash(self):
|
||||||
|
@ -485,7 +485,7 @@ class DashboardTests(SupersetTestCase):
|
||||||
|
|
||||||
# list dashboards as a gamma user
|
# list dashboards as a gamma user
|
||||||
self.login(gamma_user.username)
|
self.login(gamma_user.username)
|
||||||
resp = self.get_resp("/dashboard/list/")
|
resp = self.get_resp("/api/v1/dashboard/")
|
||||||
self.assertNotIn(f"/superset/dashboard/{slug}/", resp)
|
self.assertNotIn(f"/superset/dashboard/{slug}/", resp)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -458,7 +458,7 @@ class RolePermissionTests(SupersetTestCase):
|
||||||
|
|
||||||
def test_gamma_user_schema_access_to_dashboards(self):
|
def test_gamma_user_schema_access_to_dashboards(self):
|
||||||
self.login(username="gamma")
|
self.login(username="gamma")
|
||||||
data = str(self.client.get("dashboard/list/").data)
|
data = str(self.client.get("api/v1/dashboard/").data)
|
||||||
self.assertIn("/superset/dashboard/world_health/", data)
|
self.assertIn("/superset/dashboard/world_health/", data)
|
||||||
self.assertNotIn("/superset/dashboard/births/", data)
|
self.assertNotIn("/superset/dashboard/births/", data)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue