diff --git a/.github/workflows/tech-debt.yml b/.github/workflows/tech-debt.yml new file mode 100644 index 0000000000000..bf412227f2483 --- /dev/null +++ b/.github/workflows/tech-debt.yml @@ -0,0 +1,31 @@ +name: Upload Technical Debt Metrics to Google Sheets + +on: + push: + branches: + - main + - master + +jobs: + process-and-upload: + runs-on: ubuntu-latest + + steps: + - name: Checkout Repository + uses: actions/checkout@v3 + + - name: Set up Node.js + uses: actions/setup-node@v3 + with: + node-version: '16' + + - name: Install Dependencies + run: npm install + working-directory: ./superset-frontend + + - name: Run Script + env: + SPREADSHEET_ID: '1oABNnzxJYzwUrHjr_c9wfYEq9dFL1ScVof9LlaAdxvo' + SERVICE_ACCOUNT_KEY: ${{ secrets.GSHEET_KEY }} + run: npm run lint-stats + working-directory: ./superset-frontend diff --git a/superset-frontend/.eslintrc.js b/superset-frontend/.eslintrc.js index 5f57860ad97ed..d533f61dad52a 100644 --- a/superset-frontend/.eslintrc.js +++ b/superset-frontend/.eslintrc.js @@ -42,6 +42,7 @@ module.exports = { 'prettier', 'prettier/react', 'plugin:react-hooks/recommended', + 'plugin:react-prefer-function-component/recommended', ], parser: '@babel/eslint-parser', parserOptions: { @@ -74,6 +75,7 @@ module.exports = { 'lodash', 'theme-colors', 'translation-vars', + 'react-prefer-function-component', ], overrides: [ { @@ -267,7 +269,12 @@ module.exports = { message: 'Please use the theme directly from the ThemeProvider rather than importing supersetTheme.', }, + { + name: 'lodash/memoize', + message: 'Lodash Memoize is unsafe! Please use memoize-one instead', + }, ], + patterns: ['antd/*'], }, ], 'no-shadow': 0, // re-enable up for discussion @@ -276,6 +283,7 @@ module.exports = { 'prefer-object-spread': 1, 'prefer-destructuring': ['error', { object: true, array: false }], 'react/destructuring-assignment': 0, // re-enable up for discussion + 'react/forbid-component-props': 1, 'react/forbid-prop-types': 0, 'react/jsx-filename-extension': [1, { extensions: ['.jsx', '.tsx'] }], 'react/jsx-fragments': 1, @@ -289,6 +297,7 @@ module.exports = { 'react/require-default-props': 0, 'react/sort-comp': 0, // TODO: re-enable in separate PR 'react/static-property-placement': 0, // disabled temporarily + 'react-prefer-function-component/react-prefer-function-component': 1, 'prettier/prettier': 'error', }, ignorePatterns, diff --git a/superset-frontend/cypress-base/.eslintrc b/superset-frontend/cypress-base/.eslintrc index 9d94eb106dadf..ed9d54f5f1899 100644 --- a/superset-frontend/cypress-base/.eslintrc +++ b/superset-frontend/cypress-base/.eslintrc @@ -18,11 +18,8 @@ */ { "parser": "@typescript-eslint/parser", - "plugins": ["cypress", "@typescript-eslint"], - "extends": [ - "plugin:@typescript-eslint/recommended", - "plugin:cypress/recommended" - ], + "plugins": ["cypress"], + "extends": ["plugin:cypress/recommended"], "rules": { "import/no-unresolved": 0, "@typescript-eslint/explicit-function-return-type": 0, diff --git a/superset-frontend/cypress-base/tsconfig.json b/superset-frontend/cypress-base/tsconfig.json index e8879cba59372..dd24831f87ae9 100644 --- a/superset-frontend/cypress-base/tsconfig.json +++ b/superset-frontend/cypress-base/tsconfig.json @@ -11,5 +11,6 @@ "noEmit": true }, "files": ["cypress/support/index.d.ts", "./node_modules/@applitools/eyes-cypress/eyes-index.d.ts"], - "include": ["node_modules/cypress", "cypress/**/*.ts"] + "include": ["cypress/**/*.ts", "./cypress.config.ts"], + "exclude": ["node_modules"] } diff --git a/superset-frontend/package-lock.json b/superset-frontend/package-lock.json index 30036c2a35aad..926f929fc4156 100644 --- a/superset-frontend/package-lock.json +++ b/superset-frontend/package-lock.json @@ -72,6 +72,7 @@ "fs-extra": "^10.0.0", "fuse.js": "^7.0.0", "geolib": "^2.0.24", + "googleapis": "^130.0.0", "html-webpack-plugin": "^5.3.2", "immer": "^9.0.6", "interweave": "^13.0.0", @@ -229,6 +230,7 @@ "eslint-plugin-prettier": "^4.0.0", "eslint-plugin-react": "^7.22.0", "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-prefer-function-component": "^0.0.7", "eslint-plugin-testing-library": "^3.10.2", "eslint-plugin-theme-colors": "file:tools/eslint-plugin-theme-colors", "eslint-plugin-translation-vars": "file:tools/eslint-plugin-translation-vars", @@ -25419,6 +25421,11 @@ "node": ">=0.4.0" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, "node_modules/buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", @@ -30982,6 +30989,14 @@ "safer-buffer": "^2.1.0" } }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/echarts": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/echarts/-/echarts-5.4.1.tgz", @@ -32301,6 +32316,12 @@ "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" } }, + "node_modules/eslint-plugin-react-prefer-function-component": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-prefer-function-component/-/eslint-plugin-react-prefer-function-component-0.0.7.tgz", + "integrity": "sha512-W4npdWHC5xdKFmuQ41wwqlFvqrk8euMp5nbv5obvFbsWit7c9gSmEEwde/nE+yQpHUsP1gObN+JsEtB+4bBrMQ==", + "dev": true + }, "node_modules/eslint-plugin-react/node_modules/doctrine": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", @@ -34789,6 +34810,125 @@ "node": ">=8" } }, + "node_modules/gaxios": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.1.1.tgz", + "integrity": "sha512-bw8smrX+XlAoo9o1JAksBwX+hi/RG15J+NTSxmNPIclKC3ZVK6C2afwY8OSdRvOK0+ZLecUJYtj2MmjOt3Dm0w==", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/gaxios/node_modules/agent-base": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", + "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/gaxios/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/gaxios/node_modules/https-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", + "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/gaxios/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gaxios/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/gaxios/node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/gaxios/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/gaxios/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/gaxios/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/gcp-metadata": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.0.tgz", + "integrity": "sha512-Jh/AIwwgaxan+7ZUUmRLCjtchyDiqh4KjBJ5tW3plBZb5iL/BPcso8A5DlzeD9qlw0duCamnNdpFjxwaT0KyKg==", + "dependencies": { + "gaxios": "^6.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -35683,6 +35823,76 @@ "node": ">=8" } }, + "node_modules/google-auth-library": { + "version": "9.4.2", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.4.2.tgz", + "integrity": "sha512-rTLO4gjhqqo3WvYKL5IdtlCvRqeQ4hxUx/p4lObobY2xotFW3bCQC+Qf1N51CYOfiqfMecdMwW9RIo7dFWYjqw==", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^6.1.1", + "gcp-metadata": "^6.1.0", + "gtoken": "^7.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/googleapis": { + "version": "130.0.0", + "resolved": "https://registry.npmjs.org/googleapis/-/googleapis-130.0.0.tgz", + "integrity": "sha512-+ZSOowVv+vGBTueu1Ot9O7EqC0U4PS9l7fUjzc0ThCT4w4g+r78Vgn17q7eGBB5JMu4hxYC1hbbm1U/MCnYFdg==", + "dependencies": { + "google-auth-library": "^9.0.0", + "googleapis-common": "^7.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/googleapis-common": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-7.0.1.tgz", + "integrity": "sha512-mgt5zsd7zj5t5QXvDanjWguMdHAcJmmDrF9RkInCecNsyV7S7YtGqm5v2IWONNID88osb7zmx5FtrAP12JfD0w==", + "dependencies": { + "extend": "^3.0.2", + "gaxios": "^6.0.3", + "google-auth-library": "^9.0.0", + "qs": "^6.7.0", + "url-template": "^2.0.8", + "uuid": "^9.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/googleapis-common/node_modules/qs": { + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", + "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/googleapis-common/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -35735,6 +35945,18 @@ "dev": true, "optional": true }, + "node_modules/gtoken": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.0.1.tgz", + "integrity": "sha512-KcFVtoP1CVFtQu0aSk3AyAt2og66PFhZAlkUOuWKwzMLoulHXG5W5wE5xAnHb+yl3/wEFoqGW7/cDGMU8igDZQ==", + "dependencies": { + "gaxios": "^6.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/h3-js": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/h3-js/-/h3-js-3.7.2.tgz", @@ -43860,6 +44082,25 @@ "integrity": "sha512-ApcjaOdVTJ7y4r08xI5wIqpvwS48Q0PBG4DJROcEkH1f8MdAiNFyFxz3xoL0LWAVwjrwPYZdVHHxhRHcx/uGLA==", "dev": true }, + "node_modules/jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, "node_modules/kdbush": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-3.0.0.tgz", @@ -62344,6 +62585,11 @@ "requires-port": "^1.0.0" } }, + "node_modules/url-template": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", + "integrity": "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==" + }, "node_modules/url/node_modules/punycode": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", @@ -90699,6 +90945,11 @@ "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-0.0.1.tgz", "integrity": "sha1-kbx0sR6kBbyRa8aqkI+q+ltKrEs=" }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, "buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", @@ -95067,6 +95318,14 @@ "safer-buffer": "^2.1.0" } }, + "ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, "echarts": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/echarts/-/echarts-5.4.1.tgz", @@ -96289,6 +96548,12 @@ "dev": true, "requires": {} }, + "eslint-plugin-react-prefer-function-component": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-prefer-function-component/-/eslint-plugin-react-prefer-function-component-0.0.7.tgz", + "integrity": "sha512-W4npdWHC5xdKFmuQ41wwqlFvqrk8euMp5nbv5obvFbsWit7c9gSmEEwde/nE+yQpHUsP1gObN+JsEtB+4bBrMQ==", + "dev": true + }, "eslint-plugin-testing-library": { "version": "3.10.2", "resolved": "https://registry.npmjs.org/eslint-plugin-testing-library/-/eslint-plugin-testing-library-3.10.2.tgz", @@ -97990,6 +98255,90 @@ } } }, + "gaxios": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.1.1.tgz", + "integrity": "sha512-bw8smrX+XlAoo9o1JAksBwX+hi/RG15J+NTSxmNPIclKC3ZVK6C2afwY8OSdRvOK0+ZLecUJYtj2MmjOt3Dm0w==", + "requires": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9" + }, + "dependencies": { + "agent-base": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", + "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "requires": { + "debug": "^4.3.4" + } + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "https-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", + "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", + "requires": { + "agent-base": "^7.0.2", + "debug": "4" + } + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==" + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "requires": { + "whatwg-url": "^5.0.0" + } + }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + } + } + }, + "gcp-metadata": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.0.tgz", + "integrity": "sha512-Jh/AIwwgaxan+7ZUUmRLCjtchyDiqh4KjBJ5tW3plBZb5iL/BPcso8A5DlzeD9qlw0duCamnNdpFjxwaT0KyKg==", + "requires": { + "gaxios": "^6.0.0", + "json-bigint": "^1.0.0" + } + }, "gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -98662,6 +99011,56 @@ } } }, + "google-auth-library": { + "version": "9.4.2", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.4.2.tgz", + "integrity": "sha512-rTLO4gjhqqo3WvYKL5IdtlCvRqeQ4hxUx/p4lObobY2xotFW3bCQC+Qf1N51CYOfiqfMecdMwW9RIo7dFWYjqw==", + "requires": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^6.1.1", + "gcp-metadata": "^6.1.0", + "gtoken": "^7.0.0", + "jws": "^4.0.0" + } + }, + "googleapis": { + "version": "130.0.0", + "resolved": "https://registry.npmjs.org/googleapis/-/googleapis-130.0.0.tgz", + "integrity": "sha512-+ZSOowVv+vGBTueu1Ot9O7EqC0U4PS9l7fUjzc0ThCT4w4g+r78Vgn17q7eGBB5JMu4hxYC1hbbm1U/MCnYFdg==", + "requires": { + "google-auth-library": "^9.0.0", + "googleapis-common": "^7.0.0" + } + }, + "googleapis-common": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-7.0.1.tgz", + "integrity": "sha512-mgt5zsd7zj5t5QXvDanjWguMdHAcJmmDrF9RkInCecNsyV7S7YtGqm5v2IWONNID88osb7zmx5FtrAP12JfD0w==", + "requires": { + "extend": "^3.0.2", + "gaxios": "^6.0.3", + "google-auth-library": "^9.0.0", + "qs": "^6.7.0", + "url-template": "^2.0.8", + "uuid": "^9.0.0" + }, + "dependencies": { + "qs": { + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", + "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", + "requires": { + "side-channel": "^1.0.4" + } + }, + "uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==" + } + } + }, "gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -98708,6 +99107,15 @@ "dev": true, "optional": true }, + "gtoken": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.0.1.tgz", + "integrity": "sha512-KcFVtoP1CVFtQu0aSk3AyAt2og66PFhZAlkUOuWKwzMLoulHXG5W5wE5xAnHb+yl3/wEFoqGW7/cDGMU8igDZQ==", + "requires": { + "gaxios": "^6.0.0", + "jws": "^4.0.0" + } + }, "h3-js": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/h3-js/-/h3-js-3.7.2.tgz", @@ -104876,6 +105284,25 @@ "integrity": "sha512-ApcjaOdVTJ7y4r08xI5wIqpvwS48Q0PBG4DJROcEkH1f8MdAiNFyFxz3xoL0LWAVwjrwPYZdVHHxhRHcx/uGLA==", "dev": true }, + "jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "requires": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, "kdbush": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-3.0.0.tgz", @@ -118872,6 +119299,11 @@ "requires-port": "^1.0.0" } }, + "url-template": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", + "integrity": "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==" + }, "use": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", diff --git a/superset-frontend/package.json b/superset-frontend/package.json index af516ff428486..6f543e020e9e7 100644 --- a/superset-frontend/package.json +++ b/superset-frontend/package.json @@ -53,6 +53,7 @@ "format": "npm run _prettier -- --write", "lint": "npm run eslint -- . && npm run type", "lint-fix": "npm run eslint -- . --fix", + "lint-stats": "eslint -f ./scripts/eslint-metrics-uploader.js --ignore-path=.eslintignore --ext .js,.jsx,.ts,.tsx . ", "plugins:build": "node ./scripts/build.js", "plugins:build-assets": "node ./scripts/copyAssets.js", "plugins:build-storybook": "cd packages/superset-ui-demo && npm run build-storybook", @@ -138,6 +139,7 @@ "fs-extra": "^10.0.0", "fuse.js": "^7.0.0", "geolib": "^2.0.24", + "googleapis": "^130.0.0", "html-webpack-plugin": "^5.3.2", "immer": "^9.0.6", "interweave": "^13.0.0", @@ -295,6 +297,7 @@ "eslint-plugin-prettier": "^4.0.0", "eslint-plugin-react": "^7.22.0", "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-prefer-function-component": "^0.0.7", "eslint-plugin-testing-library": "^3.10.2", "eslint-plugin-theme-colors": "file:tools/eslint-plugin-theme-colors", "eslint-plugin-translation-vars": "file:tools/eslint-plugin-translation-vars", diff --git a/superset-frontend/scripts/eslint-metrics-uploader.js b/superset-frontend/scripts/eslint-metrics-uploader.js new file mode 100644 index 0000000000000..db10bed18b09b --- /dev/null +++ b/superset-frontend/scripts/eslint-metrics-uploader.js @@ -0,0 +1,126 @@ +const { google } = require('googleapis'); + +const { SPREADSHEET_ID } = process.env; +const SERVICE_ACCOUNT_KEY = JSON.parse(process.env.SERVICE_ACCOUNT_KEY); + +const auth = new google.auth.GoogleAuth({ + credentials: SERVICE_ACCOUNT_KEY, + scopes: ['https://www.googleapis.com/auth/spreadsheets'], +}); + +const sheets = google.sheets({ version: 'v4', auth }); +const DATETIME = new Date().toISOString().replace(/T/, ' ').replace(/\..+/, ''); + +async function writeToGoogleSheet(data, range, headers, append = false) { + const request = { + spreadsheetId: SPREADSHEET_ID, + range, + valueInputOption: 'USER_ENTERED', + resource: { values: append ? data : [headers, ...data] }, + }; + + const method = append ? 'append' : 'update'; + await sheets.spreadsheets.values[method](request); +} + +// Process ESLint results and prepare data for Google Sheets +module.exports = async results => { + const enrichedRules = { + 'react-prefer-function-component/react-prefer-function-component': { + description: 'We prefer function components to class-based components', + }, + 'react/jsx-filename-extension': { + description: + 'We prefer Typescript - all JSX files should be converted to TSX', + }, + 'react/forbid-component-props': { + description: + 'We prefer Emotion for styling rather than `className` or `style` props', + }, + // Ignore thie rule here - the messages in eslintrc_metrics.js are sufficient descriptions, broken down by file type. + 'no-restricted-imports': { + description: + "This rule catches several things that shouldn't be used anymore. LESS, antD, enzyme, etc. See individual occurrence messages for details", + }, + 'no-console': { + description: + "We don't want a bunch of console noise, but you can use the `logger` from `@superset-ui/core` when there's a reason to.", + }, + }; + + // Consolidate data processing into a single loop for efficiency + const metricsByRule = {}; + let occurrencesData = []; + + results.forEach(result => { + result.messages.forEach(({ ruleId, line, column, message }) => { + const ruleData = metricsByRule[ruleId] || { count: 0 }; + ruleData.count += 1; + metricsByRule[ruleId] = ruleData; + + occurrencesData.push({ + rule: ruleId, + message, + file: result.filePath, + line, + column, + ts: DATETIME, + }); + }); + }); + + // Transform data for Google Sheets + const metricsData = Object.entries(metricsByRule).map(([rule, { count }]) => [ + 'ESLint', + rule, + enrichedRules[rule]?.description || 'N/A', + `${count}`, + DATETIME, + ]); + + occurrencesData = occurrencesData.map( + ({ rule, message, file, line, column }) => [ + rule, + enrichedRules[rule]?.description || 'N/A', + message, + file, + `${line}`, + `${column}`, + DATETIME, + ], + ); + + const aggregatedHistoryHeaders = [ + 'Process', + 'Rule', + 'Description', + 'Count', + 'Timestamp', + ]; + const eslintBacklogHeaders = [ + 'Rule', + 'Rule Description', + 'ESLint Message', + 'File', + 'Line', + 'Column', + 'Timestamp', + ]; + + try { + await writeToGoogleSheet( + metricsData, + 'Aggregated History!A:E', + aggregatedHistoryHeaders, + true, + ); + + await writeToGoogleSheet( + occurrencesData, + 'ESLint Backlog!A:G', + eslintBacklogHeaders, + ); + } catch (error) { + console.error('Error writing to Google Sheets:', error); + } +};