deprecate tslint and configure eslint for typescript (#9172)

This commit is contained in:
ʈᵃᵢ 2020-02-20 09:54:33 -08:00 committed by GitHub
parent e55fe43ca6
commit 74423e5d19
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 709 additions and 486 deletions

View File

@ -26,3 +26,4 @@ vendor/*
docs/*
src/dashboard/deprecated/*
**/node_modules
*.d.ts

View File

@ -1,89 +0,0 @@
/**
* 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.
*/
{
"extends": ["airbnb", "prettier"],
"parser": "babel-eslint",
"parserOptions": {
"ecmaFeatures": {
"experimentalObjectRestSpread": true
}
},
"env": {
"browser": true
},
"plugins": ["prettier", "react"],
"rules": {
"camelcase": [
"error",
{
"allow": ["^UNSAFE_"],
"properties": "never"
}
],
"class-methods-use-this": 0,
"func-names": 0,
"guard-for-in": 0,
"import/extensions": [
"error",
{
".js": "always",
".jsx": "always",
".ts": "always",
".tsx": "always",
".json": "always"
}
],
"import/no-named-as-default": 0,
"import/prefer-default-export": 0,
"indent": 0,
"jsx-a11y/anchor-has-content": 0,
"jsx-a11y/href-no-hash": 0,
"jsx-a11y/no-static-element-interactions": 0,
"new-cap": 0,
"no-bitwise": 0,
"no-confusing-arrow": 0,
"no-continue": 0,
"no-mixed-operators": 0,
"no-multi-assign": 0,
"no-multi-spaces": 0,
"no-plusplus": 0,
"no-prototype-builtins": 0,
"no-restricted-properties": 0,
"no-restricted-syntax": 0,
"padded-blocks": 0,
"prefer-arrow-callback": 0,
"prefer-template": 0,
"react/forbid-prop-types": 0,
"react/jsx-no-bind": 0,
"react/no-array-index-key": 0,
"react/no-string-refs": 0,
"react/no-unescaped-entities": 0,
"react/no-unused-prop-types": 0,
"react/require-default-props": 0,
"react/jsx-fragments": 1,
"react/prop-types": 0,
"prettier/prettier": "error"
},
"settings": {
"import/resolver": "webpack",
"react": {
"version": "detect"
}
}
}

View File

@ -0,0 +1,167 @@
/**
* 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.
*/
module.exports = {
extends: ['airbnb', 'prettier'],
parser: 'babel-eslint',
parserOptions: {
ecmaFeatures: {
experimentalObjectRestSpread: true,
},
},
env: {
browser: true,
},
plugins: ['prettier', 'react'],
overrides: [
{
files: ['*.ts', '*.tsx'],
parser: '@typescript-eslint/parser',
extends: [
'airbnb',
'plugin:@typescript-eslint/recommended',
'prettier',
'prettier/@typescript-eslint',
],
plugins: ['@typescript-eslint', 'prettier', 'react'],
rules: {
'@typescript-eslint/ban-ts-ignore': 0,
'@typescript-eslint/camelcase': [
'error',
{
allow: ['^UNSAFE_'],
properties: 'never',
},
],
'@typescript-eslint/no-explicit-any': 0,
'@typescript-eslint/explicit-function-return-type': 0,
camelcase: 0,
'class-methods-use-this': 0,
'func-names': 0,
'guard-for-in': 0,
'import/extensions': [
'error',
{
'.ts': 'always',
'.tsx': 'always',
'.json': 'always',
},
],
'import/no-named-as-default': 0,
'import/no-named-as-default-member': 0,
'import/prefer-default-export': 0,
indent: 0,
'jsx-a11y/anchor-has-content': 0,
'jsx-a11y/href-no-hash': 0,
'jsx-a11y/no-static-element-interactions': 0,
'new-cap': 0,
'no-bitwise': 0,
'no-confusing-arrow': 0,
'no-continue': 0,
'no-mixed-operators': 0,
'no-multi-assign': 0,
'no-multi-spaces': 0,
'no-plusplus': 0,
'no-prototype-builtins': 0,
'no-restricted-properties': 0,
'no-restricted-syntax': 0,
'no-unused-vars': 0,
'padded-blocks': 0,
'prefer-arrow-callback': 0,
'prefer-template': 0,
'react/forbid-prop-types': 0,
'react/jsx-filename-extension': [1, { extensions: ['.jsx', '.tsx'] }],
'react/jsx-no-bind': 0,
'react/no-array-index-key': 0,
'react/no-string-refs': 0,
'react/no-unescaped-entities': 0,
'react/no-unused-prop-types': 0,
'react/require-default-props': 0,
'react/jsx-fragments': 1,
'react/prop-types': 0,
'prettier/prettier': 'error',
},
settings: {
'import/resolver': 'webpack',
react: {
version: 'detect',
},
},
},
],
rules: {
camelcase: [
'error',
{
allow: ['^UNSAFE_'],
properties: 'never',
},
],
'class-methods-use-this': 0,
'func-names': 0,
'guard-for-in': 0,
'import/extensions': [
'error',
{
'.js': 'always',
'.jsx': 'always',
'.ts': 'always',
'.tsx': 'always',
'.json': 'always',
},
],
'import/no-named-as-default': 0,
'import/prefer-default-export': 0,
indent: 0,
'jsx-a11y/anchor-has-content': 0,
'jsx-a11y/href-no-hash': 0,
'jsx-a11y/no-static-element-interactions': 0,
'new-cap': 0,
'no-bitwise': 0,
'no-confusing-arrow': 0,
'no-continue': 0,
'no-mixed-operators': 0,
'no-multi-assign': 0,
'no-multi-spaces': 0,
'no-plusplus': 0,
'no-prototype-builtins': 0,
'no-restricted-properties': 0,
'no-restricted-syntax': 0,
'no-unused-vars': 0,
'padded-blocks': 0,
'prefer-arrow-callback': 0,
'prefer-template': 0,
'react/forbid-prop-types': 0,
'react/jsx-filename-extension': [1, { extensions: ['.jsx', '.tsx'] }],
'react/jsx-no-bind': 0,
'react/no-array-index-key': 0,
'react/no-string-refs': 0,
'react/no-unescaped-entities': 0,
'react/no-unused-prop-types': 0,
'react/require-default-props': 0,
'react/jsx-fragments': 1,
'react/prop-types': 0,
'prettier/prettier': 'error',
},
settings: {
'import/resolver': 'webpack',
react: {
version: 'detect',
},
},
};

View File

@ -4724,6 +4724,11 @@
}
}
},
"@types/classnames": {
"version": "2.2.9",
"resolved": "https://registry.npmjs.org/@types/classnames/-/classnames-2.2.9.tgz",
"integrity": "sha512-MNl+rT5UmZeilaPxAVs6YaPC2m6aA8rofviZbhbxpPpl61uKodfdQVsBtgJGTqGizEf02oW3tsVe7FYB8kK14A=="
},
"@types/clone": {
"version": "0.1.30",
"resolved": "https://registry.npmjs.org/@types/clone/-/clone-0.1.30.tgz",
@ -4765,6 +4770,12 @@
"@types/trusted-types": "*"
}
},
"@types/eslint-visitor-keys": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz",
"integrity": "sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==",
"dev": true
},
"@types/events": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz",
@ -4842,6 +4853,12 @@
"integrity": "sha512-DC8xTuW/6TYgvEg3HEXS7cu9OijFqprVDXXiOcdOKZCU/5PJNLZU37VVvmZHdtMiGOa8wAA/We+JzbdxFzQTRQ==",
"dev": true
},
"@types/json-schema": {
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.4.tgz",
"integrity": "sha512-8+KAKzEvSUdeo+kmqnKrqgeE+LcA0tjYWFY7RPProVYwnqDjukzO+3b6dLD56rYX5TdWejnEOLJYOIeh4CXKuA==",
"dev": true
},
"@types/lodash": {
"version": "4.14.147",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.147.tgz",
@ -5013,6 +5030,149 @@
"integrity": "sha512-SOhuU4wNBxhhTHxYaiG5NY4HBhDIDnJF60GU+2LqHAdKKer86//e4yg69aENCtQ04n0ovz+tq2YPME5t5yp4pw==",
"dev": true
},
"@typescript-eslint/eslint-plugin": {
"version": "2.20.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.20.0.tgz",
"integrity": "sha512-cimIdVDV3MakiGJqMXw51Xci6oEDEoPkvh8ggJe2IIzcc0fYqAxOXN6Vbeanahz6dLZq64W+40iUEc9g32FLDQ==",
"dev": true,
"requires": {
"@typescript-eslint/experimental-utils": "2.20.0",
"eslint-utils": "^1.4.3",
"functional-red-black-tree": "^1.0.1",
"regexpp": "^3.0.0",
"tsutils": "^3.17.1"
},
"dependencies": {
"eslint-utils": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz",
"integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==",
"dev": true,
"requires": {
"eslint-visitor-keys": "^1.1.0"
}
},
"eslint-visitor-keys": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz",
"integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==",
"dev": true
},
"regexpp": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.0.0.tgz",
"integrity": "sha512-Z+hNr7RAVWxznLPuA7DIh8UNX1j9CDrUQxskw9IrBE1Dxue2lyXT+shqEIeLUjrokxIP8CMy1WkjgG3rTsd5/g==",
"dev": true
},
"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"
}
}
}
},
"@typescript-eslint/experimental-utils": {
"version": "2.20.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-2.20.0.tgz",
"integrity": "sha512-fEBy9xYrwG9hfBLFEwGW2lKwDRTmYzH3DwTmYbT+SMycmxAoPl0eGretnBFj/s+NfYBG63w/5c3lsvqqz5mYag==",
"dev": true,
"requires": {
"@types/json-schema": "^7.0.3",
"@typescript-eslint/typescript-estree": "2.20.0",
"eslint-scope": "^5.0.0"
}
},
"@typescript-eslint/parser": {
"version": "2.20.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-2.20.0.tgz",
"integrity": "sha512-o8qsKaosLh2qhMZiHNtaHKTHyCHc3Triq6aMnwnWj7budm3xAY9owSZzV1uon5T9cWmJRJGzTFa90aex4m77Lw==",
"dev": true,
"requires": {
"@types/eslint-visitor-keys": "^1.0.0",
"@typescript-eslint/experimental-utils": "2.20.0",
"@typescript-eslint/typescript-estree": "2.20.0",
"eslint-visitor-keys": "^1.1.0"
},
"dependencies": {
"eslint-visitor-keys": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz",
"integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==",
"dev": true
}
}
},
"@typescript-eslint/typescript-estree": {
"version": "2.20.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.20.0.tgz",
"integrity": "sha512-WlFk8QtI8pPaE7JGQGxU7nGcnk1ccKAJkhbVookv94ZcAef3m6oCE/jEDL6dGte3JcD7reKrA0o55XhBRiVT3A==",
"dev": true,
"requires": {
"debug": "^4.1.1",
"eslint-visitor-keys": "^1.1.0",
"glob": "^7.1.6",
"is-glob": "^4.0.1",
"lodash": "^4.17.15",
"semver": "^6.3.0",
"tsutils": "^3.17.1"
},
"dependencies": {
"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"
}
},
"eslint-visitor-keys": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz",
"integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==",
"dev": true
},
"glob": {
"version": "7.1.6",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
"integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
"dev": true,
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.0.4",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
}
},
"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
},
"semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
"dev": true
},
"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"
}
}
}
},
"@vx/axis": {
"version": "0.0.140",
"resolved": "https://registry.npmjs.org/@vx/axis/-/axis-0.0.140.tgz",
@ -10615,12 +10775,12 @@
}
},
"eslint-config-prettier": {
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-2.10.0.tgz",
"integrity": "sha512-Mhl90VLucfBuhmcWBgbUNtgBiK955iCDK1+aHAz7QfDQF6wuzWZ6JjihZ3ejJoGlJWIuko7xLqNm8BA5uenKhA==",
"version": "6.10.0",
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.10.0.tgz",
"integrity": "sha512-AtndijGte1rPILInUdHjvKEGbIV06NuvPrqlIEaEaWtbtvJh464mDeyGMdZEQMsGvC0ZVkiex1fSNcC4HAbRGg==",
"dev": true,
"requires": {
"get-stdin": "^5.0.1"
"get-stdin": "^6.0.0"
}
},
"eslint-import-resolver-node": {
@ -10814,13 +10974,12 @@
"dev": true
},
"eslint-plugin-prettier": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-2.7.0.tgz",
"integrity": "sha512-CStQYJgALoQBw3FsBzH0VOVDRnJ/ZimUlpLm226U8qgqYJfPOY/CPK6wyRInMxh73HSKg5wyRwdS4BVYYHwokA==",
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.2.tgz",
"integrity": "sha512-GlolCC9y3XZfv3RQfwGew7NnuFDKsfI4lbvRK+PIIo23SFH+LemGs4cKwzAaRa+Mdb+lQO/STaIayno8T5sJJA==",
"dev": true,
"requires": {
"fast-diff": "^1.1.1",
"jest-docblock": "^21.0.0"
"prettier-linter-helpers": "^1.0.0"
}
},
"eslint-plugin-react": {
@ -12686,9 +12845,9 @@
"dev": true
},
"get-stdin": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-5.0.1.tgz",
"integrity": "sha1-Ei4WFZHiH/TFJTAwVpPyDmOTo5g=",
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz",
"integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==",
"dev": true
},
"get-stream": {
@ -15007,12 +15166,6 @@
}
}
},
"jest-docblock": {
"version": "21.2.0",
"resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-21.2.0.tgz",
"integrity": "sha512-5IZ7sY9dBAYSV+YjQ0Ovb540Ku7AO9Z5o2Cg789xj167iQuZ2cG+z0f3Uct6WeYLbU6aQiM2pCs7sZ+4dotydw==",
"dev": true
},
"jest-each": {
"version": "24.8.0",
"resolved": "https://registry.npmjs.org/jest-each/-/jest-each-24.8.0.tgz",
@ -20562,6 +20715,15 @@
"integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==",
"dev": true
},
"prettier-linter-helpers": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
"integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==",
"dev": true,
"requires": {
"fast-diff": "^1.1.2"
}
},
"pretty-format": {
"version": "24.8.0",
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.8.0.tgz",
@ -24496,93 +24658,6 @@
"integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==",
"dev": true
},
"tslint": {
"version": "5.20.1",
"resolved": "https://registry.npmjs.org/tslint/-/tslint-5.20.1.tgz",
"integrity": "sha512-EcMxhzCFt8k+/UP5r8waCf/lzmeSyVlqxqMEDQE7rWYiQky8KpIBz1JAoYXfROHrPZ1XXd43q8yQnULOLiBRQg==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.0.0",
"builtin-modules": "^1.1.1",
"chalk": "^2.3.0",
"commander": "^2.12.1",
"diff": "^4.0.1",
"glob": "^7.1.1",
"js-yaml": "^3.13.1",
"minimatch": "^3.0.4",
"mkdirp": "^0.5.1",
"resolve": "^1.3.2",
"semver": "^5.3.0",
"tslib": "^1.8.0",
"tsutils": "^2.29.0"
},
"dependencies": {
"ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dev": true,
"requires": {
"color-convert": "^1.9.0"
}
},
"chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"dev": true,
"requires": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"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": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"requires": {
"has-flag": "^3.0.0"
}
}
}
},
"tslint-react": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/tslint-react/-/tslint-react-4.1.0.tgz",
"integrity": "sha512-Y7CbFn09X7Mpg6rc7t/WPbmjx9xPI8p1RsQyiGCLWgDR6sh3+IBSlT+bEkc0PSZcWwClOkqq2wPsID8Vep6szQ==",
"dev": true,
"requires": {
"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": {
"version": "2.29.0",
"resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz",
"integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==",
"dev": true,
"requires": {
"tslib": "^1.8.1"
}
},
"tty-browserify": {
"version": "0.0.0",
"resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz",

View File

@ -15,8 +15,8 @@
"dev-server": "node --max_old_space_size=4096 ./node_modules/webpack-dev-server/bin/webpack-dev-server.js --mode=development --progress",
"prod": "node --max_old_space_size=4096 ./node_modules/webpack/bin/webpack.js --mode=production --colors --progress",
"build": "cross-env NODE_OPTIONS=--max_old_space_size=8192 NODE_ENV=production webpack --mode=production --colors --progress",
"lint": "eslint --ignore-path=.eslintignore --ext .js,.jsx . && tslint -c tslint.json ./{src,spec}/**/*.ts{,x}",
"lint-fix": "eslint --fix --ignore-path=.eslintignore --ext .js,.jsx . && tslint -c tslint.json --fix ./{src,spec}/**/*.ts{,x} && npm run clean-css",
"lint": "eslint --ignore-path=.eslintignore --ext .js,.jsx,.ts,.tsx .",
"lint-fix": "eslint --fix --ignore-path=.eslintignore --ext .js,.jsx,.ts,tsx . && npm run clean-css",
"clean-css": "prettier --write '{src,stylesheets}/**/*.{css,less,sass,scss}'"
},
"repository": {
@ -82,6 +82,7 @@
"@superset-ui/query": "^0.12.2",
"@superset-ui/time-format": "^0.12.4",
"@superset-ui/translation": "^0.12.0",
"@types/classnames": "^2.2.9",
"@types/react-json-tree": "^0.6.11",
"@vx/responsive": "0.0.172",
"abortcontroller-polyfill": "^1.1.9",
@ -163,6 +164,8 @@
"@types/react": "^16.4.18",
"@types/react-dom": "^16.0.9",
"@types/react-table": "^7.0.2",
"@typescript-eslint/eslint-plugin": "^2.20.0",
"@typescript-eslint/parser": "^2.20.0",
"babel-core": "^7.0.0-bridge.0",
"babel-eslint": "^10.0.3",
"babel-jest": "^24.8.0",
@ -180,14 +183,14 @@
"enzyme-adapter-react-16": "^1.14.0",
"eslint": "^6.2.2",
"eslint-config-airbnb": "^15.0.1",
"eslint-config-prettier": "^2.9.0",
"eslint-config-prettier": "^6.10.0",
"eslint-import-resolver-webpack": "^0.10.1",
"eslint-plugin-cypress": "^2.0.1",
"eslint-plugin-import": "^2.2.0",
"eslint-plugin-jest": "^21.24.1",
"eslint-plugin-jsx-a11y": "^5.1.1",
"eslint-plugin-no-only-tests": "^2.0.1",
"eslint-plugin-prettier": "^2.6.0",
"eslint-plugin-prettier": "^3.1.2",
"eslint-plugin-react": "^7.16.0",
"exports-loader": "^0.7.0",
"fetch-mock": "^7.0.0-alpha.6",
@ -215,8 +218,6 @@
"ts-jest": "^24.0.2",
"ts-loader": "^5.4.5",
"tslib": "^1.10.0",
"tslint": "^5.20.1",
"tslint-react": "^4.1.0",
"typescript": "^3.5.3",
"url-loader": "^1.0.1",
"webpack": "^4.19.0",

View File

@ -40,7 +40,9 @@ describe('Stringify utility testing', () => {
};
expect(safeStringify(noncircular)).toEqual(JSON.stringify(noncircular));
// Checking that it works with quick-deepish-copies as well.
expect(JSON.parse(safeStringify(noncircular))).toEqual(JSON.parse(JSON.stringify(noncircular)));
expect(JSON.parse(safeStringify(noncircular))).toEqual(
JSON.parse(JSON.stringify(noncircular)),
);
});
it('handles simple circular json as expected', () => {
@ -93,7 +95,9 @@ describe('Stringify utility testing', () => {
e: {},
},
};
expect(safeStringify(emptyObjectValues)).toEqual(JSON.stringify(emptyObjectValues));
expect(safeStringify(emptyObjectValues)).toEqual(
JSON.stringify(emptyObjectValues),
);
});
it('does not remove nested same keys', () => {

View File

@ -52,7 +52,7 @@ const Link = ({
if (tooltip) {
return (
<OverlayTrigger
overlay={<Tooltip id='tooltip'>{tooltip}</Tooltip>}
overlay={<Tooltip id="tooltip">{tooltip}</Tooltip>}
placement={placement}
delayShow={300}
delayHide={150}

View File

@ -95,7 +95,7 @@ const SQL_DATA_TYPES = [
const allKeywords = SQL_KEYWORDS.concat(SQL_DATA_TYPES);
const sqlKeywords = allKeywords.map((keyword) => ({
const sqlKeywords = allKeywords.map(keyword => ({
meta: 'sql',
name: keyword,
score: SQL_KEYWORD_AUTOCOMPLETE_SCORE,

View File

@ -34,7 +34,6 @@ interface State {
open: boolean;
}
export default class ConfirmStatusChange extends React.Component<Props, State> {
public state = {
callbackArgs: [],
open: false,
@ -42,33 +41,33 @@ export default class ConfirmStatusChange extends React.Component<Props, State> {
public showConfirm = (...callbackArgs: any[]) => {
// check if any args are DOM events, if so, call persist
callbackArgs.forEach((arg) => arg && typeof arg.persist === 'function' && arg.persist());
callbackArgs.forEach(
arg => arg && typeof arg.persist === 'function' && arg.persist(),
);
this.setState({
callbackArgs,
open: true,
});
}
};
public hide = () => this.setState({ open: false, callbackArgs: [] });
public confirm = () => {
this.props.onConfirm(...this.state.callbackArgs);
this.hide();
}
};
public render() {
return (
<>
{this.props.children && this.props.children(this.showConfirm)}
<Modal show={this.state.open} onHide={this.hide}>
<Modal.Header closeButton={true} >{this.props.title}</Modal.Header>
<Modal.Body>
{this.props.description}
</Modal.Body>
<Modal.Header closeButton>{this.props.title}</Modal.Header>
<Modal.Body>{this.props.description}</Modal.Body>
<Modal.Footer>
<Button onClick={this.hide}>{t('Cancel')}</Button>
<Button bsStyle='danger' onClick={this.confirm}>
<Button bsStyle="danger" onClick={this.confirm}>
{t('OK')}
</Button>
</Modal.Footer>

View File

@ -31,7 +31,13 @@ import {
import IndeterminateCheckbox from '../IndeterminateCheckbox';
import './ListViewStyles.less';
import TableCollection from './TableCollection';
import { FetchDataConfig, FilterToggle, FilterType, FilterTypeMap, SortColumn } from './types';
import {
FetchDataConfig,
FilterToggle,
FilterType,
FilterTypeMap,
SortColumn,
} from './types';
import { convertFilters, removeFromList, useListViewState } from './utils';
interface Props {
@ -45,7 +51,11 @@ interface Props {
title?: string;
initialSort?: SortColumn[];
filterTypes?: FilterTypeMap;
bulkActions?: Array<{ key?: string, name: React.ReactNode, onSelect: (rows: any[]) => any }>;
bulkActions?: Array<{
key?: string;
name: React.ReactNode;
onSelect: (rows: any[]) => any;
}>;
}
const bulkSelectColumnConfig = {
@ -102,7 +112,9 @@ const ListView: FunctionComponent<Props> = ({
initialPageSize,
initialSort,
});
const filterableColumns = useMemo(() => columns.filter((c) => c.filterable), [columns]);
const filterableColumns = useMemo(() => columns.filter(c => c.filterable), [
columns,
]);
const filterable = Boolean(columns.length);
const removeFilterAndApply = (index: number) => {
@ -114,25 +126,26 @@ const ListView: FunctionComponent<Props> = ({
return (
<div className={`superset-list-view ${className}`}>
{title && filterable && (
<div className='header'>
<div className="header">
<Row>
<Col md={10}>
<h2>{t(title)}</h2>
</Col>
{filterable && (
<Col md={2}>
<div className='filter-dropdown'>
<div className="filter-dropdown">
<DropdownButton
id='filter-picker'
bsSize='small'
id="filter-picker"
bsSize="small"
bsStyle={'default'}
noCaret={true}
title={(
noCaret
title={
<>
<i className='fa fa-filter text-primary' />
{' '}{t('Filter List')}
<i className="fa fa-filter text-primary" />
{' '}
{t('Filter List')}
</>
)}
}
>
{filterableColumns
.map(({ id, accessor, Header }) => ({
@ -143,8 +156,8 @@ const ListView: FunctionComponent<Props> = ({
<MenuItem
key={ft.id}
eventKey={ft}
onSelect={
(fltr: FilterToggle) => setFilterToggles([...filterToggles, fltr])
onSelect={(fltr: FilterToggle) =>
setFilterToggles([...filterToggles, fltr])
}
>
{ft.Header}
@ -157,51 +170,54 @@ const ListView: FunctionComponent<Props> = ({
</Row>
<hr />
{filterToggles.map((ft, i) => (
<div key={`${ft.Header}-${i}`} className='filter-inputs'>
<div key={`${ft.Header}-${i}`} className="filter-inputs">
<Row>
<Col className='text-center filter-column' md={2}>
<Col className="text-center filter-column" md={2}>
<span>{ft.Header}</span>
</Col>
<Col md={2}>
<FormControl
componentClass='select'
bsSize='small'
componentClass="select"
bsSize="small"
value={ft.filterId}
placeholder={filterTypes[ft.id] ? filterTypes[ft.id][0].name : ''}
placeholder={
filterTypes[ft.id] ? 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>
),
)}
{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'
type="text"
bsSize="small"
value={ft.value || ''}
onChange={
(e: React.KeyboardEvent<HTMLInputElement>) =>
updateFilterToggle(i, {
value: e.currentTarget.value,
})
onChange={(e: React.KeyboardEvent<HTMLInputElement>) =>
updateFilterToggle(i, {
value: e.currentTarget.value,
})
}
/>
</Col>
<Col md={1}>
<div
className='filter-close'
role='button'
className="filter-close"
role="button"
tabIndex={0}
onClick={() => removeFilterAndApply(i)}
>
<i className='fa fa-close text-primary' />
<i className="fa fa-close text-primary" />
</div>
</Col>
</Row>
@ -214,11 +230,11 @@ const ListView: FunctionComponent<Props> = ({
<Col md={10} />
<Col md={2}>
<Button
data-test='apply-filters'
disabled={filtersApplied ? true : false}
bsStyle='primary'
data-test="apply-filters"
disabled={!!filtersApplied}
bsStyle="primary"
onClick={applyFilters}
bsSize='small'
bsSize="small"
>
{t('Apply')}
</Button>
@ -228,9 +244,8 @@ const ListView: FunctionComponent<Props> = ({
</>
)}
</div>
)
}
<div className='body'>
)}
<div className="body">
<TableCollection
getTableProps={getTableProps}
getTableBodyProps={getTableBodyProps}
@ -240,33 +255,33 @@ const ListView: FunctionComponent<Props> = ({
loading={loading}
/>
</div>
<div className='footer'>
<div className="footer">
<Row>
<Col md={2}>
<div className='form-actions-container'>
<div className='btn-group'>
<div className="form-actions-container">
<div className="btn-group">
{bulkActions.length > 0 && (
<DropdownButton
id='bulk-actions'
bsSize='small'
bsStyle='default'
noCaret={true}
title={(
id="bulk-actions"
bsSize="small"
bsStyle="default"
noCaret
title={
<>
{t('Actions')} <span className='caret' />
{t('Actions')} <span className="caret" />
</>
)}
}
>
{bulkActions.map((action) => (
{bulkActions.map(action => (
<MenuItem
id={action.name}
key={action.key || action.name}
eventKey={selectedFlatRows}
onSelect={
(selectedRows: typeof selectedFlatRows) => {
action.onSelect(selectedRows.map((r: any) => r.original));
}
}
onSelect={(selectedRows: typeof selectedFlatRows) => {
action.onSelect(
selectedRows.map((r: any) => r.original),
);
}}
>
{action.name}
</MenuItem>
@ -276,7 +291,7 @@ const ListView: FunctionComponent<Props> = ({
</div>
</div>
</Col>
<Col md={8} className='text-center'>
<Col md={8} className="text-center">
<Pagination
prev={canPreviousPage}
first={pageIndex > 1}
@ -284,23 +299,24 @@ const ListView: FunctionComponent<Props> = ({
last={pageIndex < pageCount - 2}
items={pageCount}
activePage={pageIndex + 1}
ellipsis={true}
boundaryLinks={true}
ellipsis
boundaryLinks
maxButtons={5}
onSelect={(p: number) => gotoPage(p - 1)}
/>
</Col>
<Col md={2}>
<span className='pull-right'>
<span className="pull-right">
{t('showing')}{' '}
<strong>
{pageSize * pageIndex + (rows.length && 1)}-{pageSize * pageIndex + rows.length}
{pageSize * pageIndex + (rows.length && 1)}-
{pageSize * pageIndex + rows.length}
</strong>{' '}
{t('of')} <strong>{count}</strong>
</span>
</Col>
</Row>
</div >
</div>
</div>
);
};

View File

@ -17,6 +17,7 @@
* under the License.
*/
import React from 'react';
import cx from 'classnames';
import { Cell, HeaderGroup, Row } from 'react-table';
interface Props<D extends object = {}> {
@ -37,23 +38,24 @@ export default function TableCollection({
loading,
}: Props<any>) {
return (
<table {...getTableProps()} className='table table-hover'>
<table {...getTableProps()} className="table table-hover">
<thead>
{headerGroups.map((headerGroup) => (
{headerGroups.map(headerGroup => (
<tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map((column: any) => (
<th {...column.getHeaderProps(column.getSortByToggleProps())} data-test='sort-header'>
<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'
}`}
className={cx('text-primary fa', {
'fa-sort': !column.isSorted,
'fa-sort-down': column.isSorted && column.isSortedDesc,
'fa-sort-up': column.isSorted && !column.isSortedDesc,
})}
/>
)}
</th>
@ -62,7 +64,7 @@ export default function TableCollection({
))}
</thead>
<tbody {...getTableBodyProps()}>
{rows.map((row) => {
{rows.map(row => {
prepareRow(row);
const loadingProps = loading ? { className: 'table-row-loader' } : {};
return (
@ -70,7 +72,9 @@ export default function TableCollection({
{...row.getRowProps()}
{...loadingProps}
onMouseEnter={() => row.setState && row.setState({ hover: true })}
onMouseLeave={() => row.setState && row.setState({ hover: false })}
onMouseLeave={() =>
row.setState && row.setState({ hover: false })
}
>
{row.cells.map((cell: Cell<any>) => {
const columnCellProps = cell.column.cellProps || {};

View File

@ -55,7 +55,7 @@ function updateInList(list: any[], index: number, update: any): any[] {
export function convertFilters(fts: FilterToggle[]) {
return fts
.filter((ft: FilterToggle) => ft.value)
.map((ft) => ({ value: null, filterId: ft.filterId || 'sw', ...ft }));
.map(ft => ({ value: null, filterId: ft.filterId || 'sw', ...ft }));
}
interface UseListViewConfig {

View File

@ -51,7 +51,8 @@
cursor: default;
z-index: @z-index-max;
&, .menu-item {
&,
.menu-item {
display: flex;
flex-direction: row;
align-items: center;

View File

@ -31,10 +31,10 @@ export function safeStringify(object: any): string {
// We've seen this object before
try {
// Quick deep copy to duplicate if this is a repeat rather than a circle.
return JSON.parse(JSON.stringify(value));
return JSON.parse(JSON.stringify(value));
} catch (err) {
// Discard key if value cannot be duplicated.
return;
return; // eslint-disable-line consistent-return
}
}
// Store the value in our cache.

View File

@ -22,7 +22,7 @@ import moment from 'moment';
import PropTypes from 'prop-types';
import React from 'react';
// @ts-ignore
import { Button, Modal, Panel } from 'react-bootstrap';
import { Panel } from 'react-bootstrap';
import ConfirmStatusChange from 'src/components/ConfirmStatusChange';
import ListView from 'src/components/ListView/ListView';
import { FetchDataConfig, FilterTypeMap } from 'src/components/ListView/types';
@ -55,20 +55,11 @@ interface Chart {
}
class ChartList extends React.PureComponent<Props, State> {
get canEdit() {
return this.hasPerm('can_edit');
}
get canDelete() {
return this.hasPerm('can_delete');
}
public static propTypes = {
static propTypes = {
addDangerToast: PropTypes.func.isRequired,
};
public state: State = {
state: State = {
chartCount: 0,
charts: [],
filterTypes: {},
@ -78,15 +69,34 @@ class ChartList extends React.PureComponent<Props, State> {
permissions: [],
};
public initialSort = [{ id: 'changed_on', desc: true }];
componentDidMount() {
SupersetClient.get({
endpoint: `/api/v1/chart/_info`,
}).then(({ json = {} }) => {
this.setState({
filterTypes: json.filters,
permissions: json.permissions,
});
});
}
public columns = [
get canEdit() {
return this.hasPerm('can_edit');
}
get canDelete() {
return this.hasPerm('can_delete');
}
initialSort = [{ id: 'changed_on', desc: true }];
columns = [
{
Cell: ({
row: {
original: { url, slice_name },
original: { url, slice_name: sliceName },
},
}: any) => <a href={url}>{slice_name}</a>,
}: any) => <a href={url}>{sliceName}</a>,
Header: t('Chart'),
accessor: 'slice_name',
filterable: true,
@ -95,9 +105,9 @@ class ChartList extends React.PureComponent<Props, State> {
{
Cell: ({
row: {
original: { viz_type },
original: { viz_type: vizType },
},
}: any) => viz_type,
}: any) => vizType,
Header: t('Visualization Type'),
accessor: 'viz_type',
sortable: true,
@ -105,9 +115,12 @@ class ChartList extends React.PureComponent<Props, State> {
{
Cell: ({
row: {
original: { datasource_name_text, datasource_link },
original: {
datasource_name_text: dsNameTxt,
datasource_link: dsLink,
},
},
}: any) => <a href={datasource_link}>{datasource_name_text}</a>,
}: any) => <a href={dsLink}>{dsNameTxt}</a>,
Header: t('Datasource'),
accessor: 'datasource_name_text',
sortable: true,
@ -115,9 +128,12 @@ class ChartList extends React.PureComponent<Props, State> {
{
Cell: ({
row: {
original: { changed_by_name, changed_by_url },
original: {
changed_by_name: changedByName,
changed_by_url: changedByUrl,
},
},
}: any) => <a href={changed_by_url}>{changed_by_name}</a>,
}: any) => <a href={changedByName}>{changedByUrl}</a>,
Header: t('Creator'),
accessor: 'creator',
sortable: true,
@ -125,11 +141,9 @@ class ChartList extends React.PureComponent<Props, State> {
{
Cell: ({
row: {
original: { changed_on },
original: { changed_on: changedOn },
},
}: any) => (
<span className='no-wrap'>{moment(changed_on).fromNow()}</span>
),
}: any) => <span className="no-wrap">{moment(changedOn).fromNow()}</span>,
Header: t('Last Modified'),
accessor: 'changed_on',
sortable: true,
@ -143,31 +157,40 @@ class ChartList extends React.PureComponent<Props, State> {
}
return (
<span className={`actions ${state && state.hover ? '' : 'invisible'}`}>
<span
className={`actions ${state && state.hover ? '' : 'invisible'}`}
>
{this.canDelete && (
<ConfirmStatusChange
title={t('Please Confirm')}
description={<>{t('Are you sure you want to delete')} <b>{original.slice_name}</b>?</>}
description={
<>
{t('Are you sure you want to delete')}{' '}
<b>{original.slice_name}</b>?
</>
}
onConfirm={handleDelete}
>
{(confirmDelete) => (
{confirmDelete => (
<span
role='button'
className='action-button'
role="button"
tabIndex={0}
className="action-button"
onClick={confirmDelete}
>
<i className='fa fa-trash' />
<i className="fa fa-trash" />
</span>
)}
</ConfirmStatusChange>
)}
{this.canEdit && (
<span
role='button'
className='action-button'
role="button"
tabIndex={0}
className="action-button"
onClick={handleEdit}
>
<i className='fa fa-pencil' />
<i className="fa fa-pencil" />
</span>
)}
</span>
@ -178,37 +201,42 @@ class ChartList extends React.PureComponent<Props, State> {
},
];
public hasPerm = (perm: string) => {
hasPerm = (perm: string) => {
if (!this.state.permissions.length) {
return false;
}
return this.state.permissions.some((p) => p === perm);
}
return this.state.permissions.some(p => p === perm);
};
public handleChartEdit = ({ id }: { id: number }) => {
handleChartEdit = ({ id }: { id: number }) => {
window.location.assign(`/chart/edit/${id}`);
}
};
public handleChartDelete = ({ id, slice_name }: Chart) => {
handleChartDelete = ({ id, slice_name: sliceName }: Chart) => {
SupersetClient.delete({
endpoint: `/api/v1/chart/${id}`,
}).then(
(resp) => {
() => {
const { lastFetchDataConfig } = this.state;
if (lastFetchDataConfig) {
this.fetchData(lastFetchDataConfig);
}
this.props.addSuccessToast(t('Deleted: %(slice_name)', slice_name));
this.props.addSuccessToast(t('Deleted: %(slice_name)', sliceName));
},
(err: any) => {
this.props.addDangerToast(t('There was an issue deleting: %(slice_name)', slice_name));
() => {
this.props.addDangerToast(
t('There was an issue deleting: %(slice_name)', sliceName),
);
},
);
}
public handleBulkDashboardDelete = (charts: Chart[]) => {
};
handleBulkDashboardDelete = (charts: Chart[]) => {
SupersetClient.delete({
endpoint: `/api/v1/dashboard/?q=!(${charts.map(({ id }) => id).join(',')})`,
endpoint: `/api/v1/dashboard/?q=!(${charts
.map(({ id }) => id)
.join(',')})`,
}).then(
({ json = {} }) => {
const { lastFetchDataConfig } = this.state;
@ -219,19 +247,16 @@ class ChartList extends React.PureComponent<Props, State> {
},
(err: any) => {
console.error(err);
this.props.addDangerToast(t('There was an issue deleting the selected dashboards'));
this.props.addDangerToast(
t('There was an issue deleting the selected dashboards'),
);
},
);
}
};
public fetchData = ({
pageIndex,
pageSize,
sortBy,
filters,
}: FetchDataConfig) => {
fetchData = ({ pageIndex, pageSize, sortBy, filters }: FetchDataConfig) => {
this.setState({ loading: true });
const filterExps = Object.keys(filters).map((fk) => ({
const filterExps = Object.keys(filters).map(fk => ({
col: fk,
opr: filters[fk].filterId,
value: filters[fk].filterValue,
@ -249,50 +274,48 @@ class ChartList extends React.PureComponent<Props, State> {
endpoint: `/api/v1/chart/?q=${queryParams}`,
})
.then(({ json = {} }) => {
this.setState({ charts: json.result, chartCount: json.count, labelColumns: json.label_columns });
this.setState({
charts: json.result,
chartCount: json.count,
labelColumns: json.label_columns,
});
})
.catch(() => {
this.props.addDangerToast(
t('An error occurred while fetching Charts'),
);
this.props.addDangerToast(t('An error occurred while fetching Charts'));
})
.finally(() => {
this.setState({ loading: false });
});
}
};
public componentDidMount() {
SupersetClient.get({
endpoint: `/api/v1/chart/_info`,
})
.then(({ json = {} }) => {
this.setState({ filterTypes: json.filters, permissions: json.permissions });
});
}
public render() {
render() {
const { charts, chartCount, loading, filterTypes } = this.state;
return (
<div className='container welcome'>
<div className="container welcome">
<Panel>
<ConfirmStatusChange
title={t('Please confirm')}
description={t('Are you sure you want to delete the selected charts?')}
description={t(
'Are you sure you want to delete the selected charts?',
)}
onConfirm={this.handleBulkDashboardDelete}
>
{(confirmDelete) => {
{confirmDelete => {
const bulkActions = [];
if (this.canDelete) {
bulkActions.push({
key: 'delete',
name: <><i className='fa fa-trash' /> Delete</>,
name: (
<>
<i className="fa fa-trash" /> Delete
</>
),
onSelect: confirmDelete,
});
}
return (
<ListView
className='chart-list-view'
className="chart-list-view"
title={'Charts'}
columns={this.columns}
data={charts}
@ -308,7 +331,7 @@ class ChartList extends React.PureComponent<Props, State> {
}}
</ConfirmStatusChange>
</Panel>
</div >
</div>
);
}
}

View File

@ -22,7 +22,7 @@ import moment from 'moment';
import PropTypes from 'prop-types';
import React from 'react';
// @ts-ignore
import { Button, Modal, Panel } from 'react-bootstrap';
import { Panel } from 'react-bootstrap';
import ConfirmStatusChange from 'src/components/ConfirmStatusChange';
import ListView from 'src/components/ListView/ListView';
import { FetchDataConfig, FilterTypeMap } from 'src/components/ListView/types';
@ -57,6 +57,30 @@ interface Dashboard {
}
class DashboardList extends React.PureComponent<Props, State> {
static propTypes = {
addDangerToast: PropTypes.func.isRequired,
};
state: State = {
dashboardCount: 0,
dashboards: [],
filterTypes: {},
labelColumns: {},
lastFetchDataConfig: null,
loading: false,
permissions: [],
};
componentDidMount() {
SupersetClient.get({
endpoint: `/api/v1/dashboard/_info`,
}).then(({ json = {} }) => {
this.setState({
filterTypes: json.filters,
permissions: json.permissions,
});
});
}
get canEdit() {
return this.hasPerm('can_edit');
@ -70,29 +94,15 @@ class DashboardList extends React.PureComponent<Props, State> {
return this.hasPerm('can_mulexport');
}
public static propTypes = {
addDangerToast: PropTypes.func.isRequired,
};
initialSort = [{ id: 'changed_on', desc: true }];
public state: State = {
dashboardCount: 0,
dashboards: [],
filterTypes: {},
labelColumns: {},
lastFetchDataConfig: null,
loading: false,
permissions: [],
};
public initialSort = [{ id: 'changed_on', desc: true }];
public columns = [
columns = [
{
Cell: ({
row: {
original: { url, dashboard_title },
original: { url, dashboard_title: dashboardTitle },
},
}: any) => <a href={url}>{dashboard_title}</a>,
}: any) => <a href={url}>{dashboardTitle}</a>,
Header: t('Title'),
accessor: 'dashboard_title',
filterable: true,
@ -101,9 +111,12 @@ class DashboardList extends React.PureComponent<Props, State> {
{
Cell: ({
row: {
original: { changed_by_name, changed_by_url },
original: {
changed_by_name: changedByName,
changed_by_url: changedByUrl,
},
},
}: any) => <a href={changed_by_url}>{changed_by_name}</a>,
}: any) => <a href={changedByUrl}>{changedByName}</a>,
Header: t('Changed By Name'),
accessor: 'changed_by_fk',
sortable: true,
@ -114,8 +127,10 @@ class DashboardList extends React.PureComponent<Props, State> {
original: { published },
},
}: any) => (
<span className='no-wrap'>{published ? <i className='fa fa-check' /> : ''}</span>
),
<span className="no-wrap">
{published ? <i className="fa fa-check" /> : ''}
</span>
),
Header: t('Published'),
accessor: 'published',
sortable: true,
@ -123,11 +138,9 @@ class DashboardList extends React.PureComponent<Props, State> {
{
Cell: ({
row: {
original: { changed_on },
original: { changed_on: changedOn },
},
}: any) => (
<span className='no-wrap'>{moment(changed_on).fromNow()}</span>
),
}: any) => <span className="no-wrap">{moment(changedOn).fromNow()}</span>,
Header: t('Changed On'),
accessor: 'changed_on',
sortable: true,
@ -141,40 +154,50 @@ class DashboardList extends React.PureComponent<Props, State> {
return null;
}
return (
<span className={`actions ${state && state.hover ? '' : 'invisible'}`}>
<span
className={`actions ${state && state.hover ? '' : 'invisible'}`}
>
{this.canDelete && (
<ConfirmStatusChange
title={t('Please Confirm')}
description={<>{t('Are you sure you want to delete')} <b>{original.dashboard_title}</b>?</>}
description={
<>
{t('Are you sure you want to delete')}{' '}
<b>{original.dashboard_title}</b>?
</>
}
onConfirm={handleDelete}
>
{(confirmDelete) => (
{confirmDelete => (
<span
role='button'
className='action-button'
role="button"
tabIndex={0}
className="action-button"
onClick={confirmDelete}
>
<i className='fa fa-trash' />
<i className="fa fa-trash" />
</span>
)}
</ConfirmStatusChange>
)}
{this.canExport && (
<span
role='button'
className='action-button'
role="button"
tabIndex={0}
className="action-button"
onClick={handleExport}
>
<i className='fa fa-database' />
<i className="fa fa-database" />
</span>
)}
{this.canEdit && (
<span
role='button'
className='action-button'
role="button"
tabIndex={0}
className="action-button"
onClick={handleEdit}
>
<i className='fa fa-pencil' />
<i className="fa fa-pencil" />
</span>
)}
</span>
@ -185,12 +208,23 @@ class DashboardList extends React.PureComponent<Props, State> {
},
];
public handleDashboardEdit = ({ id }: { id: number }) => {
window.location.assign(`/dashboard/edit/${id}`);
}
hasPerm = (perm: string) => {
if (!this.state.permissions.length) {
return false;
}
public handleDashboardDelete = ({ id, dashboard_title }: Dashboard) => {
return SupersetClient.delete({
return Boolean(this.state.permissions.find(p => p === perm));
};
handleDashboardEdit = ({ id }: { id: number }) => {
window.location.assign(`/dashboard/edit/${id}`);
};
handleDashboardDelete = ({
id,
dashboard_title: dashboardTitle,
}: Dashboard) =>
SupersetClient.delete({
endpoint: `/api/v1/dashboard/${id}`,
}).then(
() => {
@ -198,18 +232,21 @@ class DashboardList extends React.PureComponent<Props, State> {
if (lastFetchDataConfig) {
this.fetchData(lastFetchDataConfig);
}
this.props.addSuccessToast(t('Deleted') + ` ${dashboard_title}`);
this.props.addSuccessToast(`${t('Deleted')} ${dashboardTitle}`);
},
(err: any) => {
console.error(err);
this.props.addDangerToast(t('There was an issue deleting') + `${dashboard_title}`);
this.props.addDangerToast(
`${t('There was an issue deleting')}${dashboardTitle}`,
);
},
);
}
public handleBulkDashboardDelete = (dashboards: Dashboard[]) => {
handleBulkDashboardDelete = (dashboards: Dashboard[]) => {
SupersetClient.delete({
endpoint: `/api/v1/dashboard/?q=!(${dashboards.map(({ id }) => id).join(',')})`,
endpoint: `/api/v1/dashboard/?q=!(${dashboards
.map(({ id }) => id)
.join(',')})`,
}).then(
({ json = {} }) => {
const { lastFetchDataConfig } = this.state;
@ -220,21 +257,20 @@ class DashboardList extends React.PureComponent<Props, State> {
},
(err: any) => {
console.error(err);
this.props.addDangerToast(t('There was an issue deleting the selected dashboards'));
this.props.addDangerToast(
t('There was an issue deleting the selected dashboards'),
);
},
);
}
};
public handleBulkDashboardExport = (dashboards: Dashboard[]) => {
return window.location.href = `/ api / v1 / dashboard /export/?q=!(${dashboards.map(({ id }) => id).join(',')})`;
}
handleBulkDashboardExport = (dashboards: Dashboard[]) => {
window.location.href = `/api/v1/dashboard/export/?q=!(${dashboards
.map(({ id }) => id)
.join(',')})`;
};
public fetchData = ({
pageIndex,
pageSize,
sortBy,
filters,
}: FetchDataConfig) => {
fetchData = ({ pageIndex, pageSize, sortBy, filters }: FetchDataConfig) => {
// set loading state, cache the last config for fetching data in this component.
this.setState({
lastFetchDataConfig: {
@ -263,7 +299,11 @@ class DashboardList extends React.PureComponent<Props, State> {
endpoint: `/api/v1/dashboard/?q=${queryParams}`,
})
.then(({ json = {} }) => {
this.setState({ dashboards: json.result, dashboardCount: json.count, labelColumns: json.label_columns });
this.setState({
dashboards: json.result,
dashboardCount: json.count,
labelColumns: json.label_columns,
});
})
.catch(() => {
this.props.addDangerToast(
@ -273,46 +313,47 @@ class DashboardList extends React.PureComponent<Props, State> {
.finally(() => {
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() {
render() {
const { dashboards, dashboardCount, loading, filterTypes } = this.state;
return (
<div className='container welcome' >
<div className="container welcome">
<Panel>
<ConfirmStatusChange
title={t('Please confirm')}
description={t('Are you sure you want to delete the selected dashboards?')}
description={t(
'Are you sure you want to delete the selected dashboards?',
)}
onConfirm={this.handleBulkDashboardDelete}
>
{(confirmDelete) => {
{confirmDelete => {
const bulkActions = [];
if (this.canDelete) {
bulkActions.push({
key: 'delete',
name: <><i className='fa fa-trash' /> Delete</>,
name: (
<>
<i className="fa fa-trash" /> Delete
</>
),
onSelect: confirmDelete,
});
}
if (this.canExport) {
bulkActions.push({
key: 'export',
name: <><i className='fa fa-database' /> Export</>,
name: (
<>
<i className="fa fa-database" /> Export
</>
),
onSelect: this.handleBulkDashboardExport,
});
}
return (
<ListView
className='dashboard-list-view'
className="dashboard-list-view"
title={'Dashboards'}
columns={this.columns}
data={dashboards}
@ -331,14 +372,6 @@ class DashboardList extends React.PureComponent<Props, State> {
</div>
);
}
private hasPerm = (perm: string) => {
if (!this.state.permissions.length) {
return false;
}
return Boolean(this.state.permissions.find((p) => p === perm));
}
}
export default withToasts(DashboardList);

View File

@ -1,24 +1,26 @@
{
"compilerOptions": {
"baseUrl": ".",
"outDir": "./dist",
"module": "commonjs",
"target": "es5",
"lib": ["es6", "dom", "es2018.promise"],
"sourceMap": true,
"allowJs": true,
"jsx": "react",
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"baseUrl": ".",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"importHelpers": true,
"jsx": "react",
"lib": ["dom", "esnext"],
"module": "esnext",
"moduleResolution": "node",
"noImplicitAny": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noImplicitAny": true,
"importHelpers": true,
"noUnusedLocals": true,
"outDir": "./dist",
"pretty": true,
"skipLibCheck": true,
"sourceMap": true,
"strictNullChecks": true,
"suppressImplicitAnyIndexErrors": true,
"noUnusedLocals": true,
"skipLibCheck": true,
"esModuleInterop": true
"target": "es5"
},
"include": ["./src/**/*", "./spec/**/*"]
}

View File

@ -1,14 +0,0 @@
{
"extends": ["tslint:recommended", "tslint-react"],
"jsRules": {
"no-console": false
},
"rules": {
"interface-name": [true, "never-prefix"],
"quotemark": [true, "single"],
"jsx-no-multiline-js": false,
"jsx-no-lambda": false,
"no-console": false
},
"rulesDirectory": []
}