From 4208ca76e066248d1ca64fd3c532f690d4c1d163 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CA=88=E1=B5=83=E1=B5=A2?= Date: Mon, 19 Oct 2020 20:23:16 -0700 Subject: [PATCH] refactor: replace AvatarIcon instances with FacePile (#11279) --- superset-frontend/package-lock.json | 131 ++++++++---------- superset-frontend/package.json | 1 - .../src/components/AvatarIcon.tsx | 60 -------- .../components/FacePile/FacePile.stories.tsx | 60 ++++++++ .../src/components/FacePile/FacePile.test.tsx | 72 ++++++++++ .../src/components/FacePile/index.tsx | 72 ++++++++++ .../src/components/FacePile/utils.tsx | 49 +++++++ superset-frontend/src/types/Owner.ts | 2 +- .../src/views/CRUD/chart/ChartList.tsx | 13 +- .../views/CRUD/dashboard/DashboardList.tsx | 26 +--- .../views/CRUD/data/dataset/DatasetList.tsx | 22 +-- 11 files changed, 320 insertions(+), 188 deletions(-) delete mode 100644 superset-frontend/src/components/AvatarIcon.tsx create mode 100644 superset-frontend/src/components/FacePile/FacePile.stories.tsx create mode 100644 superset-frontend/src/components/FacePile/FacePile.test.tsx create mode 100644 superset-frontend/src/components/FacePile/index.tsx create mode 100644 superset-frontend/src/components/FacePile/utils.tsx diff --git a/superset-frontend/package-lock.json b/superset-frontend/package-lock.json index df7d8221d0..a3aa136c35 100644 --- a/superset-frontend/package-lock.json +++ b/superset-frontend/package-lock.json @@ -21003,28 +21003,28 @@ "dependencies": { "abbrev": { "version": "1.1.1", - "resolved": "", + "resolved": false, "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "dev": true, "optional": true }, "ansi-regex": { "version": "2.1.1", - "resolved": "", + "resolved": false, "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", "dev": true, "optional": true }, "aproba": { "version": "1.2.0", - "resolved": "", + "resolved": false, "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", "dev": true, "optional": true }, "are-we-there-yet": { "version": "1.1.5", - "resolved": "", + "resolved": false, "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", "dev": true, "optional": true, @@ -21035,14 +21035,14 @@ }, "balanced-match": { "version": "1.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true, "optional": true }, "brace-expansion": { "version": "1.1.11", - "resolved": "", + "resolved": false, "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "optional": true, @@ -21053,35 +21053,35 @@ }, "code-point-at": { "version": "1.1.0", - "resolved": "", + "resolved": false, "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", "dev": true, "optional": true }, "concat-map": { "version": "0.0.1", - "resolved": "", + "resolved": false, "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true, "optional": true }, "console-control-strings": { "version": "1.1.0", - "resolved": "", + "resolved": false, "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", "dev": true, "optional": true }, "core-util-is": { "version": "1.0.2", - "resolved": "", + "resolved": false, "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "dev": true, "optional": true }, "debug": { "version": "4.1.1", - "resolved": "", + "resolved": false, "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "dev": true, "optional": true, @@ -21091,35 +21091,35 @@ }, "deep-extend": { "version": "0.6.0", - "resolved": "", + "resolved": false, "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "dev": true, "optional": true }, "delegates": { "version": "1.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", "dev": true, "optional": true }, "detect-libc": { "version": "1.0.3", - "resolved": "", + "resolved": false, "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", "dev": true, "optional": true }, "fs.realpath": { "version": "1.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true, "optional": true }, "gauge": { "version": "2.7.4", - "resolved": "", + "resolved": false, "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", "dev": true, "optional": true, @@ -21136,7 +21136,7 @@ }, "glob": { "version": "7.1.3", - "resolved": "", + "resolved": false, "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "dev": true, "optional": true, @@ -21151,14 +21151,14 @@ }, "has-unicode": { "version": "2.0.1", - "resolved": "", + "resolved": false, "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", "dev": true, "optional": true }, "iconv-lite": { "version": "0.4.24", - "resolved": "", + "resolved": false, "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "dev": true, "optional": true, @@ -21168,7 +21168,7 @@ }, "ignore-walk": { "version": "3.0.1", - "resolved": "", + "resolved": false, "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", "dev": true, "optional": true, @@ -21178,7 +21178,7 @@ }, "inflight": { "version": "1.0.6", - "resolved": "", + "resolved": false, "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "dev": true, "optional": true, @@ -21189,21 +21189,21 @@ }, "inherits": { "version": "2.0.3", - "resolved": "", + "resolved": false, "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", "dev": true, "optional": true }, "ini": { "version": "1.3.5", - "resolved": "", + "resolved": false, "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", "dev": true, "optional": true }, "is-fullwidth-code-point": { "version": "1.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "dev": true, "optional": true, @@ -21213,14 +21213,14 @@ }, "isarray": { "version": "1.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "dev": true, "optional": true }, "minimatch": { "version": "3.0.4", - "resolved": "", + "resolved": false, "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, "optional": true, @@ -21237,14 +21237,14 @@ }, "ms": { "version": "2.1.1", - "resolved": "", + "resolved": false, "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", "dev": true, "optional": true }, "needle": { "version": "2.3.0", - "resolved": "", + "resolved": false, "integrity": "sha512-QBZu7aAFR0522EyaXZM0FZ9GLpq6lvQ3uq8gteiDUp7wKdy0lSd2hPlgFwVuW1CBkfEs9PfDQsQzZghLs/psdg==", "dev": true, "optional": true, @@ -21256,7 +21256,7 @@ }, "node-pre-gyp": { "version": "0.12.0", - "resolved": "", + "resolved": false, "integrity": "sha512-4KghwV8vH5k+g2ylT+sLTjy5wmUOb9vPhnM8NHvRf9dHmnW/CndrFXy2aRPaPST6dugXSdHXfeaHQm77PIz/1A==", "dev": true, "optional": true, @@ -21275,7 +21275,7 @@ }, "nopt": { "version": "4.0.1", - "resolved": "", + "resolved": false, "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", "dev": true, "optional": true, @@ -21286,14 +21286,14 @@ }, "npm-bundled": { "version": "1.0.6", - "resolved": "", + "resolved": false, "integrity": "sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==", "dev": true, "optional": true }, "npm-packlist": { "version": "1.4.1", - "resolved": "", + "resolved": false, "integrity": "sha512-+TcdO7HJJ8peiiYhvPxsEDhF3PJFGUGRcFsGve3vxvxdcpO2Z4Z7rkosRM0kWj6LfbK/P0gu3dzk5RU1ffvFcw==", "dev": true, "optional": true, @@ -21304,7 +21304,7 @@ }, "npmlog": { "version": "4.1.2", - "resolved": "", + "resolved": false, "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", "dev": true, "optional": true, @@ -21317,21 +21317,21 @@ }, "number-is-nan": { "version": "1.0.1", - "resolved": "", + "resolved": false, "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", "dev": true, "optional": true }, "object-assign": { "version": "4.1.1", - "resolved": "", + "resolved": false, "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "dev": true, "optional": true }, "once": { "version": "1.4.0", - "resolved": "", + "resolved": false, "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, "optional": true, @@ -21341,21 +21341,21 @@ }, "os-homedir": { "version": "1.0.2", - "resolved": "", + "resolved": false, "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", "dev": true, "optional": true }, "os-tmpdir": { "version": "1.0.2", - "resolved": "", + "resolved": false, "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "dev": true, "optional": true }, "osenv": { "version": "0.1.5", - "resolved": "", + "resolved": false, "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", "dev": true, "optional": true, @@ -21366,21 +21366,21 @@ }, "path-is-absolute": { "version": "1.0.1", - "resolved": "", + "resolved": false, "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true, "optional": true }, "process-nextick-args": { "version": "2.0.0", - "resolved": "", + "resolved": false, "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", "dev": true, "optional": true }, "rc": { "version": "1.2.8", - "resolved": "", + "resolved": false, "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "dev": true, "optional": true, @@ -21393,7 +21393,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "", + "resolved": false, "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "optional": true, @@ -21409,7 +21409,7 @@ }, "rimraf": { "version": "2.6.3", - "resolved": "", + "resolved": false, "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", "dev": true, "optional": true, @@ -21419,49 +21419,49 @@ }, "safe-buffer": { "version": "5.1.2", - "resolved": "", + "resolved": false, "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true, "optional": true }, "safer-buffer": { "version": "2.1.2", - "resolved": "", + "resolved": false, "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true, "optional": true }, "sax": { "version": "1.2.4", - "resolved": "", + "resolved": false, "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", "dev": true, "optional": true }, "semver": { "version": "5.7.0", - "resolved": "", + "resolved": false, "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", "dev": true, "optional": true }, "set-blocking": { "version": "2.0.0", - "resolved": "", + "resolved": false, "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true, "optional": true }, "signal-exit": { "version": "3.0.2", - "resolved": "", + "resolved": false, "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "dev": true, "optional": true }, "string-width": { "version": "1.0.2", - "resolved": "", + "resolved": false, "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, "optional": true, @@ -21473,7 +21473,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "", + "resolved": false, "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "optional": true, @@ -21483,7 +21483,7 @@ }, "strip-ansi": { "version": "3.0.1", - "resolved": "", + "resolved": false, "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "optional": true, @@ -21493,21 +21493,21 @@ }, "strip-json-comments": { "version": "2.0.1", - "resolved": "", + "resolved": false, "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", "dev": true, "optional": true }, "util-deprecate": { "version": "1.0.2", - "resolved": "", + "resolved": false, "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "dev": true, "optional": true }, "wide-align": { "version": "1.1.3", - "resolved": "", + "resolved": false, "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", "dev": true, "optional": true, @@ -21517,7 +21517,7 @@ }, "wrappy": { "version": "1.0.2", - "resolved": "", + "resolved": false, "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true, "optional": true @@ -38970,23 +38970,6 @@ "prop-types": "^15.5.8" } }, - "react-avatar": { - "version": "3.9.7", - "resolved": "https://registry.npmjs.org/react-avatar/-/react-avatar-3.9.7.tgz", - "integrity": "sha512-UX1prYgo4gS1g2u16tZbx/Vy45M/BxyHHexIoRj6m9hI3ZR0FdHTDt66X5GpTtf6PRYE8KlvwHte1x5n8B0/XQ==", - "requires": { - "core-js": "^3.6.1", - "is-retina": "^1.0.3", - "md5": "^2.0.0" - }, - "dependencies": { - "core-js": { - "version": "3.6.5", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz", - "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==" - } - } - }, "react-base16-styling": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/react-base16-styling/-/react-base16-styling-0.5.3.tgz", diff --git a/superset-frontend/package.json b/superset-frontend/package.json index 460cccf496..d3d6b7641b 100644 --- a/superset-frontend/package.json +++ b/superset-frontend/package.json @@ -125,7 +125,6 @@ "re-resizable": "^6.6.1", "react": "^16.13.1", "react-ace": "^5.10.0", - "react-avatar": "^3.9.7", "react-bootstrap": "^0.33.1", "react-bootstrap-dialog": "^0.13.0", "react-bootstrap-slider": "2.1.5", diff --git a/superset-frontend/src/components/AvatarIcon.tsx b/superset-frontend/src/components/AvatarIcon.tsx deleted file mode 100644 index 01ff1f10dd..0000000000 --- a/superset-frontend/src/components/AvatarIcon.tsx +++ /dev/null @@ -1,60 +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. - */ -import React from 'react'; -import { getCategoricalSchemeRegistry } from '@superset-ui/core'; -import Avatar, { ConfigProvider } from 'react-avatar'; -import TooltipWrapper from 'src/components/TooltipWrapper'; - -interface Props { - firstName: string; - lastName: string; - uniqueKey: string; - iconSize: number; - textSize: number; -} - -const colorList = getCategoricalSchemeRegistry().get(); - -export default function AvatarIcon({ - uniqueKey, - firstName, - lastName, - iconSize, - textSize, -}: Props) { - const fullName = `${firstName} ${lastName}`; - - return ( - - - - - - ); -} diff --git a/superset-frontend/src/components/FacePile/FacePile.stories.tsx b/superset-frontend/src/components/FacePile/FacePile.stories.tsx new file mode 100644 index 0000000000..d524cf758e --- /dev/null +++ b/superset-frontend/src/components/FacePile/FacePile.stories.tsx @@ -0,0 +1,60 @@ +/** + * 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 { withKnobs, number } from '@storybook/addon-knobs'; +import FacePile from '.'; + +export default { + title: 'FacePile', + component: FacePile, + decorators: [withKnobs], +}; + +const firstNames = [ + 'James', + 'Mary', + 'John', + 'Patricia', + 'Mohamed', + 'Venkat', + 'Lao', + 'Robert', + 'Jennifer', + 'Michael', + 'Linda', +]; +const lastNames = [ + 'Smith', + 'Johnson', + 'Williams', + 'Saeed', + 'Jones', + 'Brown', + 'Tzu', +]; + +const users = [...new Array(10)].map((_, i) => ({ + first_name: firstNames[Math.floor(Math.random() * firstNames.length)], + last_name: lastNames[Math.floor(Math.random() * lastNames.length)], + id: i, +})); + +export const SupersetFacePile = () => { + return ; +}; diff --git a/superset-frontend/src/components/FacePile/FacePile.test.tsx b/superset-frontend/src/components/FacePile/FacePile.test.tsx new file mode 100644 index 0000000000..9d202061b7 --- /dev/null +++ b/superset-frontend/src/components/FacePile/FacePile.test.tsx @@ -0,0 +1,72 @@ +/** + * 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 { styledMount as mount } from 'spec/helpers/theming'; + +import { Avatar } from 'src/common/components'; +import FacePile from '.'; +import { getRandomColor } from './utils'; + +const users = [...new Array(10)].map((_, i) => ({ + first_name: 'user', + last_name: `${i}`, + id: i, +})); + +describe('FacePile', () => { + const wrapper = mount(); + + it('is a valid element', () => { + expect(wrapper.find(FacePile)).toExist(); + }); + + it('renders an Avatar', () => { + expect(wrapper.find(Avatar)).toExist(); + }); + + it('hides overflow', () => { + expect(wrapper.find(Avatar).length).toBe(5); + }); +}); + +describe('utils', () => { + describe('getRandomColor', () => { + const colors = ['color1', 'color2', 'color3']; + + it('produces the same color for the same input values', () => { + const name = 'foo'; + expect(getRandomColor(name, colors)).toEqual( + getRandomColor(name, colors), + ); + }); + + it('produces a different color for different input values', () => { + expect(getRandomColor('foo', colors)).not.toEqual( + getRandomColor('bar', colors), + ); + }); + + it('handles non-ascii input values', () => { + expect(getRandomColor('泰', colors)).toMatchInlineSnapshot(`"color1"`); + expect(getRandomColor('مُحَمَّد‎', colors)).toMatchInlineSnapshot( + `"color2"`, + ); + }); + }); +}); diff --git a/superset-frontend/src/components/FacePile/index.tsx b/superset-frontend/src/components/FacePile/index.tsx new file mode 100644 index 0000000000..9c2af3d7cc --- /dev/null +++ b/superset-frontend/src/components/FacePile/index.tsx @@ -0,0 +1,72 @@ +/** + * 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 { getCategoricalSchemeRegistry, styled } from '@superset-ui/core'; +import { Avatar, Tooltip } from 'src/common/components'; +import { getRandomColor } from './utils'; + +interface FacePileProps { + users: { first_name: string; last_name: string; id: number }[]; + maxCount?: number; +} + +const colorList = getCategoricalSchemeRegistry().get()?.colors ?? []; + +const StyledAvatar = styled(Avatar)` + width: ${({ theme }) => theme.gridUnit * 6}px; + height: ${({ theme }) => theme.gridUnit * 6}px; + line-height: ${({ theme }) => theme.gridUnit * 6}px; + font-size: ${({ theme }) => theme.typography.sizes.xl}px; +`; + +// to apply styling to the maxCount avatar +const StyledGroup = styled(Avatar.Group)` + .ant-avatar { + width: ${({ theme }) => theme.gridUnit * 6}px; + height: ${({ theme }) => theme.gridUnit * 6}px; + line-height: ${({ theme }) => theme.gridUnit * 6}px; + font-size: ${({ theme }) => theme.typography.sizes.xl}px; + } +`; + +export default function FacePile({ users, maxCount = 4 }: FacePileProps) { + return ( + + {users.map(({ first_name, last_name, id }) => { + const name = `${first_name} ${last_name}`; + const uniqueKey = `${id}-${first_name}-${last_name}`; + const color = getRandomColor(uniqueKey, colorList); + return ( + + + {first_name[0].toLocaleUpperCase()} + {last_name[0].toLocaleUpperCase()} + + + ); + })} + + ); +} diff --git a/superset-frontend/src/components/FacePile/utils.tsx b/superset-frontend/src/components/FacePile/utils.tsx new file mode 100644 index 0000000000..3dde8dea94 --- /dev/null +++ b/superset-frontend/src/components/FacePile/utils.tsx @@ -0,0 +1,49 @@ +/** + * 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. + */ + +// https://en.wikipedia.org/wiki/Linear_congruential_generator +function stringAsciiPRNG(value: string, m: number) { + // Xn+1 = (a * Xn + c) % m + // 0 < a < m + // 0 <= c < m + // 0 <= X0 < m + + const charCodes = [...value].map(letter => letter.charCodeAt(0)); + const len = charCodes.length; + + const a = (len % (m - 1)) + 1; + const c = charCodes.reduce((current, next) => current + next) % m; + + let random = charCodes[0] % m; + + [...new Array(len)].forEach(() => { + random = (a * random + c) % m; + }); + + return random; +} + +export function getRandomColor(sampleValue: string, colorList: string[]) { + // if no value is passed, always return transparent color for consistency + if (!sampleValue) return 'transparent'; + + // value based random color index, + // ensuring the same sampleValue always resolves to the same color + return colorList[stringAsciiPRNG(sampleValue, colorList.length)]; +} diff --git a/superset-frontend/src/types/Owner.ts b/superset-frontend/src/types/Owner.ts index 890115e9d4..8e7d63f25b 100644 --- a/superset-frontend/src/types/Owner.ts +++ b/superset-frontend/src/types/Owner.ts @@ -23,7 +23,7 @@ export default interface Owner { first_name: string; - id: string; + id: number; last_name: string; username: string; } diff --git a/superset-frontend/src/views/CRUD/chart/ChartList.tsx b/superset-frontend/src/views/CRUD/chart/ChartList.tsx index 3daf531775..ae64ae54fd 100644 --- a/superset-frontend/src/views/CRUD/chart/ChartList.tsx +++ b/superset-frontend/src/views/CRUD/chart/ChartList.tsx @@ -25,7 +25,7 @@ import { createFetchRelated, createErrorHandler } from 'src/views/CRUD/utils'; import { useListViewResource, useFavoriteStatus } from 'src/views/CRUD/hooks'; import ConfirmStatusChange from 'src/components/ConfirmStatusChange'; import SubMenu, { SubMenuProps } from 'src/components/Menu/SubMenu'; -import AvatarIcon from 'src/components/AvatarIcon'; +import FacePile from 'src/components/FacePile'; import Icon from 'src/components/Icon'; import FaveStar from 'src/components/FaveStar'; import ListView, { @@ -479,16 +479,7 @@ function ChartList(props: ChartListProps) { imgFallbackURL="/static/assets/images/chart-card-fallback.png" imgPosition="bottom" description={t('Last modified %s', chart.changed_on_delta_humanized)} - coverLeft={(chart.owners || []).slice(0, 5).map(owner => ( - - ))} + coverLeft={} coverRight={ } diff --git a/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx b/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx index c51ffd6830..55bcf24745 100644 --- a/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx +++ b/superset-frontend/src/views/CRUD/dashboard/DashboardList.tsx @@ -24,9 +24,8 @@ import { createFetchRelated, createErrorHandler } from 'src/views/CRUD/utils'; import { useListViewResource, useFavoriteStatus } from 'src/views/CRUD/hooks'; import ConfirmStatusChange from 'src/components/ConfirmStatusChange'; import SubMenu, { SubMenuProps } from 'src/components/Menu/SubMenu'; -import AvatarIcon from 'src/components/AvatarIcon'; +import FacePile from 'src/components/FacePile'; import ListView, { ListViewProps, Filters } from 'src/components/ListView'; -import ExpandableList from 'src/components/ExpandableList'; import Owner from 'src/types/Owner'; import withToasts from 'src/messageToasts/enhancers/withToasts'; import Icon from 'src/components/Icon'; @@ -246,17 +245,9 @@ function DashboardList(props: DashboardListProps) { { Cell: ({ row: { - original: { owners }, + original: { owners = [] }, }, - }: any) => ( - - `${firstName} ${lastName}`, - )} - display={2} - /> - ), + }: any) => , Header: t('Owners'), accessor: 'owners', disableSortBy: true, @@ -496,16 +487,7 @@ function DashboardList(props: DashboardListProps) { 'Last modified %s', dashboard.changed_on_delta_humanized, )} - coverLeft={(dashboard.owners || []).slice(0, 5).map(owner => ( - - ))} + coverLeft={} actions={ {renderFaveStar(dashboard.id)} diff --git a/superset-frontend/src/views/CRUD/data/dataset/DatasetList.tsx b/superset-frontend/src/views/CRUD/data/dataset/DatasetList.tsx index f1b05ed6a7..8b8fcb9126 100644 --- a/superset-frontend/src/views/CRUD/data/dataset/DatasetList.tsx +++ b/superset-frontend/src/views/CRUD/data/dataset/DatasetList.tsx @@ -39,11 +39,11 @@ import SubMenu, { ButtonProps, } from 'src/components/Menu/SubMenu'; import { commonMenuData } from 'src/views/CRUD/data/common'; -import AvatarIcon from 'src/components/AvatarIcon'; import Owner from 'src/types/Owner'; import withToasts from 'src/messageToasts/enhancers/withToasts'; import TooltipWrapper from 'src/components/TooltipWrapper'; import Icon from 'src/components/Icon'; +import FacePile from 'src/components/FacePile'; import AddDatasetModal from './AddDatasetModal'; const PAGE_SIZE = 25; @@ -233,25 +233,9 @@ const DatasetList: FunctionComponent = ({ { Cell: ({ row: { - original: { owners, table_name: tableName }, + original: { owners = [], table_name: tableName }, }, - }: any) => { - if (!owners) { - return null; - } - return owners - .slice(0, 5) - .map((owner: Owner) => ( - - )); - }, + }: any) => , Header: t('Owners'), id: 'owners', disableSortBy: true,