From 3a134dafd23668abc61c6bfec2d93eb09be3e413 Mon Sep 17 00:00:00 2001
From: Kyle Doherty <kdoh@users.noreply.github.com>
Date: Mon, 10 Oct 2016 16:20:15 -0700
Subject: [PATCH] permission tweaks (#3458)

* change 'raw queries' to 'SQL queries'

* standardize confirm content

* admin edit header

* autosize columns

* Update react-virtualized to version 8 to prevent conflict with .Grid selectors

* shrinkwrap

* cleaner subtitle hiding

* remove unused reference
---
 .../components/FixedHeaderGrid.jsx            | 153 +++++++++---------
 .../components/PermissionsConfirm.jsx         |   2 +-
 .../components/PermissionsEditor.jsx          |   5 +-
 .../components/PermissionsGrid.jsx            |  94 +++++------
 .../metabase/admin/permissions/selectors.js   |   1 -
 .../metabase/components/ConfirmContent.jsx    |   2 +-
 frontend/src/metabase/components/EditBar.jsx  |  22 ++-
 .../src/metabase/css/components/header.css    |  13 ++
 .../questions/components/LabelIconPicker.jsx  |  12 +-
 .../visualizations/TableInteractive.jsx       |   2 +-
 npm-shrinkwrap.json                           |  38 ++++-
 package.json                                  |   2 +-
 12 files changed, 200 insertions(+), 146 deletions(-)

diff --git a/frontend/src/metabase/admin/permissions/components/FixedHeaderGrid.jsx b/frontend/src/metabase/admin/permissions/components/FixedHeaderGrid.jsx
index c33a67fcd2d..a1d8ebf9f0a 100644
--- a/frontend/src/metabase/admin/permissions/components/FixedHeaderGrid.jsx
+++ b/frontend/src/metabase/admin/permissions/components/FixedHeaderGrid.jsx
@@ -2,7 +2,7 @@
 
 import React, { Component, PropTypes } from "react";
 
-import { Grid, AutoSizer, ScrollSync } from 'react-virtualized'
+import { Grid, ScrollSync } from 'react-virtualized'
 import 'react-virtualized/styles.css';
 import S from "./FixedHeaderGrid.css";
 
@@ -13,93 +13,94 @@ const SCROLLBAR_SIZE = 50;
 
 const FixedHeaderGrid = ({
     className,
-
-    rowsCount,
-    columnsCount,
+    rowCount,
+    columnCount,
     renderCell,
     columnWidth,
     rowHeight,
-
     renderColumnHeader,
     columnHeaderHeight,
-
     rowHeaderWidth,
     renderRowHeader,
-
     renderCorner,
-
+    width,
+    height,
     paddingBottom = 25
 }) =>
     <div className={cx(className, S.fixedHeaderGrid, "relative")}>
-        <AutoSizer>
-        {({ height, width }) =>
-            <ScrollSync>
-                {({ clientHeight, clientWidth, onScroll, scrollHeight, scrollLeft, scrollTop, scrollWidth }) =>
-                    <div>
-                        {/* CORNER */}
-                        <div style={{ position: "absolute", top: 0, left: 0, width: rowHeaderWidth, height: columnHeaderHeight, overflow: "hidden" }}>
-                            {renderCorner()}
-                        </div>
-                        {/* COLUMN HEADERS */}
-                        <div style={{ position: "absolute", top: 0, left: rowHeaderWidth, height: columnHeaderHeight, overflow: "hidden" }}>
-                            <Grid
-                                width={width - rowHeaderWidth}
-                                height={columnHeaderHeight + SCROLLBAR_SIZE}
-                                renderCell={(props) =>
-                                    // HACK: offsets the additional height needed to hide the scrollbars
-                                    <div style={{ height: columnHeaderHeight, position: "relative" }}>{renderColumnHeader(props)}</div>
-                                }
-                                columnsCount={columnsCount}
-                                rowsCount={1}
-                                columnWidth={columnWidth}
-                                rowHeight={columnHeaderHeight + SCROLLBAR_SIZE}
-                                onScroll={({ scrollLeft }) => onScroll({ scrollLeft })}
-                                scrollLeft={scrollLeft}
-                            />
-                        </div>
-                        {/* ROW HEADERS */}
-                        <div style={{ position: "absolute", top: columnHeaderHeight, left: 0, width: rowHeaderWidth, overflow: "hidden" }}>
-                            <Grid
-                                width={rowHeaderWidth + SCROLLBAR_SIZE}
-                                height={height - columnHeaderHeight}
-                                renderCell={(props) =>
-                                    // HACK: pad the bottom with a phantom cell
-                                    props.rowIndex >= rowsCount ? <div /> :
-                                    // HACK: offsets the additional width needed to hide the scrollbars
-                                    <div style={{ width: rowHeaderWidth, position: "relative" }}>{renderRowHeader(props)}</div>
-                                }
-                                columnsCount={1}
-                                rowsCount={rowsCount + 1}
-                                columnWidth={rowHeaderWidth + SCROLLBAR_SIZE}
-                                rowHeight={(index) => index >= rowsCount ? paddingBottom : rowHeight}
-                                onScroll={({ scrollTop }) => onScroll({ scrollTop })}
-                                scrollTop={scrollTop}
-                            />
-                        </div>
-                        {/* CELLS */}
-                        <div style={{ position: "absolute", top: columnHeaderHeight, left: rowHeaderWidth, overflow: "hidden" }}>
-                            <Grid
-                                width={width - rowHeaderWidth}
-                                height={height - columnHeaderHeight}
-                                renderCell={(props) =>
-                                    // HACK: pad the bottom with a phantom cell
-                                    props.rowIndex >= rowsCount ? <div /> :
-                                    renderCell(props)
-                                }
-                                columnsCount={columnsCount}
-                                rowsCount={rowsCount + 1}
-                                columnWidth={columnWidth}
-                                rowHeight={(index) => index >= rowsCount ? paddingBottom : rowHeight}
-                                onScroll={({ scrollTop, scrollLeft }) => onScroll({ scrollTop, scrollLeft })}
-                                scrollTop={scrollTop}
-                                scrollLeft={scrollLeft}
-                            />
-                        </div>
+        <ScrollSync>
+            {({ clientHeight, clientWidth, onScroll, scrollHeight, scrollLeft, scrollTop, scrollWidth }) =>
+                <div>
+                    {/* CORNER */}
+                    <div style={{ position: "absolute", top: 0, left: 0, width: rowHeaderWidth, height: columnHeaderHeight, overflow: "hidden" }}>
+                        {renderCorner()}
+                    </div>
+                    {/* COLUMN HEADERS */}
+                    <div style={{ position: "absolute", top: 0, left: rowHeaderWidth, height: columnHeaderHeight, overflow: "hidden" }}>
+                        <Grid
+                            width={width - rowHeaderWidth}
+                            height={columnHeaderHeight + SCROLLBAR_SIZE}
+                            cellRenderer={({ key, style, columnIndex, rowIndex }) =>
+                                <div key={key} style={style}>
+                                    {/* HACK: offsets the additional height needed to hide the scrollbars */}
+                                    <div style={{ height: columnHeaderHeight, position: "relative" }}>
+                                        {renderColumnHeader({ columnIndex })}
+                                    </div>
+                                </div>
+                            }
+                            columnCount={columnCount}
+                            rowCount={1}
+                            columnWidth={columnWidth}
+                            rowHeight={columnHeaderHeight + SCROLLBAR_SIZE}
+                            onScroll={({ scrollLeft }) => onScroll({ scrollLeft })}
+                            scrollLeft={scrollLeft}
+                        />
+                    </div>
+                    {/* ROW HEADERS */}
+                    <div style={{ position: "absolute", top: columnHeaderHeight, left: 0, width: rowHeaderWidth, overflow: "hidden" }}>
+                        <Grid
+                            width={rowHeaderWidth + SCROLLBAR_SIZE}
+                            height={height - columnHeaderHeight}
+                            cellRenderer={({ key, style, columnIndex, rowIndex }) =>
+                                <div key={key} style={style}>
+                                    {/* HACK: offsets the additional width needed to hide the scrollbars */}
+                                    <div style={{ width: rowHeaderWidth, position: "relative" }}>
+                                        {/*  HACK: pad the bottom with a phantom cell */}
+                                        {rowIndex >= rowCount ? null : renderRowHeader({ rowIndex })}
+                                    </div>
+                                </div>
+                            }
+                            columnCount={1}
+                            rowCount={rowCount + 1}
+                            columnWidth={rowHeaderWidth + SCROLLBAR_SIZE}
+                            rowHeight={(index) => index >= rowCount ? paddingBottom : rowHeight}
+                            onScroll={({ scrollTop }) => onScroll({ scrollTop })}
+                            scrollTop={scrollTop}
+                        />
+                    </div>
+                    {/* CELLS */}
+                    <div style={{ position: "absolute", top: columnHeaderHeight, left: rowHeaderWidth, overflow: "hidden" }}>
+                        <Grid
+                            width={width - rowHeaderWidth}
+                            height={height - columnHeaderHeight}
+                            cellRenderer={({ key, style, columnIndex, rowIndex }) =>
+                                <div key={key} style={style}>
+                                    {/*  HACK: pad the bottom with a phantom cell */}
+                                    {rowIndex >= rowCount ? null : renderCell({ columnIndex, rowIndex })}
+                                </div>
+                            }
+                            columnCount={columnCount}
+                            rowCount={rowCount + 1}
+                            columnWidth={columnWidth}
+                            rowHeight={(index) => index >= rowCount ? paddingBottom : rowHeight}
+                            onScroll={({ scrollTop, scrollLeft }) => onScroll({ scrollTop, scrollLeft })}
+                            scrollTop={scrollTop}
+                            scrollLeft={scrollLeft}
+                        />
                     </div>
-                }
-            </ScrollSync>
-        }
-        </AutoSizer>
+                </div>
+            }
+        </ScrollSync>
     </div>
 
 export default FixedHeaderGrid;
diff --git a/frontend/src/metabase/admin/permissions/components/PermissionsConfirm.jsx b/frontend/src/metabase/admin/permissions/components/PermissionsConfirm.jsx
index b3d6a702e24..92abdcaa9d6 100644
--- a/frontend/src/metabase/admin/permissions/components/PermissionsConfirm.jsx
+++ b/frontend/src/metabase/admin/permissions/components/PermissionsConfirm.jsx
@@ -26,7 +26,7 @@ const TableAccessChange = ({ tables, verb, color }) => {
 
 
 const PermissionsConfirm = ({ diff }) =>
-    <div className="mx4">
+    <div>
         {Object.values(diff.groups).map(group =>
             Object.values(group.databases).map(database =>
                 <div>
diff --git a/frontend/src/metabase/admin/permissions/components/PermissionsEditor.jsx b/frontend/src/metabase/admin/permissions/components/PermissionsEditor.jsx
index 36e1df04a50..973464077d7 100644
--- a/frontend/src/metabase/admin/permissions/components/PermissionsEditor.jsx
+++ b/frontend/src/metabase/admin/permissions/components/PermissionsEditor.jsx
@@ -15,14 +15,13 @@ const PermissionsEditor = ({ grid, onUpdatePermission, onSave, onCancel, isDirty
         <div className="flex-full flex flex-column">
             { isDirty &&
                 <EditBar
+                    admin
                     title="You've made changes to permissions."
                     buttons={[
                         <Confirm
                             title="Discard changes?"
                             action={onCancel}
-                            content={
-                                <div>No changes to permissions will be made.</div>
-                            }
+                            content="No changes to permissions will be made."
                         >
                             <button className="Button Button--borderless">
                                 Cancel
diff --git a/frontend/src/metabase/admin/permissions/components/PermissionsGrid.jsx b/frontend/src/metabase/admin/permissions/components/PermissionsGrid.jsx
index 155fa61732e..30ff01fce0b 100644
--- a/frontend/src/metabase/admin/permissions/components/PermissionsGrid.jsx
+++ b/frontend/src/metabase/admin/permissions/components/PermissionsGrid.jsx
@@ -11,6 +11,7 @@ import ConfirmContent from "metabase/components/ConfirmContent.jsx";
 import Modal from "metabase/components/Modal.jsx";
 
 import FixedHeaderGrid from "./FixedHeaderGrid.jsx";
+import { AutoSizer } from 'react-virtualized'
 
 import { capitalize, pluralize } from "metabase/lib/formatting";
 import cx from "classnames";
@@ -34,11 +35,11 @@ const getBorderStyles = ({ isFirstColumn, isLastColumn, isFirstRow, isLastRow })
 const CELL_HEIGHT = 100;
 const CELL_WIDTH = 246;
 const HEADER_HEIGHT = 65;
-const HEADER_WIDTH = 170;
+const HEADER_WIDTH = 240;
 
 const PERMISSIONS_UI = {
     "native": {
-        header: "Raw Queries"
+        header: "SQL Queries"
     },
     "schemas": {
         header: "Data Access"
@@ -279,51 +280,54 @@ const PermissionsGrid = ({ className, grid, onUpdatePermission }) => {
         ({ id: id, ...PERMISSIONS_UI[id], ...permission })
     );
     return (
-        <FixedHeaderGrid
-            className={className}
-
-            rowsCount={grid.entities.length}
-            columnsCount={grid.groups.length}
-
-            columnWidth={CELL_WIDTH}
-            rowHeight={CELL_HEIGHT}
-            columnHeaderHeight={HEADER_HEIGHT}
-            rowHeaderWidth={HEADER_WIDTH}
-
-            renderCell={({ columnIndex, rowIndex }) =>
-                <PermissionsCell
-                    group={grid.groups[columnIndex]}
-                    permissions={permissions}
-                    entity={grid.entities[rowIndex]}
-                    onUpdatePermission={onUpdatePermission}
-                    isFirstRow={rowIndex === 0}
-                    isLastRow={rowIndex === grid.entities.length - 1}
-                    isFirstColumn={columnIndex === 0}
-                    isLastColumn={columnIndex === grid.groups.length - 1}
-                />
-            }
-            renderColumnHeader={({ columnIndex }) =>
-                <GroupColumnHeader
-                    group={grid.groups[columnIndex]}
-                    permissions={permissions}
-                    isFirstColumn={columnIndex === 0}
-                    isLastColumn={columnIndex === grid.groups.length - 1}
-                />
-            }
-            renderRowHeader={({ rowIndex }) =>
-                <EntityRowHeader
-                    type={grid.type}
-                    entity={grid.entities[rowIndex]}
-                    isFirstRow={rowIndex === 0}
-                    isLastRow={rowIndex === grid.entities.length - 1}
-                />
-            }
-            renderCorner={() =>
-                <CornerHeader
-                    grid={grid}
+        <AutoSizer>
+            {({ height, width }) =>
+                <FixedHeaderGrid
+                    height={height}
+                    width={width}
+                    className={className}
+                    rowCount={grid.entities.length}
+                    columnCount={grid.groups.length}
+                    columnWidth={Math.max(CELL_WIDTH, (width - HEADER_WIDTH) / grid.groups.length)}
+                    rowHeight={CELL_HEIGHT}
+                    columnHeaderHeight={HEADER_HEIGHT}
+                    rowHeaderWidth={HEADER_WIDTH}
+                    renderCell={({ columnIndex, rowIndex }) =>
+                        <PermissionsCell
+                            group={grid.groups[columnIndex]}
+                            permissions={permissions}
+                            entity={grid.entities[rowIndex]}
+                            onUpdatePermission={onUpdatePermission}
+                            isFirstRow={rowIndex === 0}
+                            isLastRow={rowIndex === grid.entities.length - 1}
+                            isFirstColumn={columnIndex === 0}
+                            isLastColumn={columnIndex === grid.groups.length - 1}
+                        />
+                    }
+                    renderColumnHeader={({ columnIndex }) =>
+                        <GroupColumnHeader
+                            group={grid.groups[columnIndex]}
+                            permissions={permissions}
+                            isFirstColumn={columnIndex === 0}
+                            isLastColumn={columnIndex === grid.groups.length - 1}
+                        />
+                    }
+                    renderRowHeader={({ rowIndex }) =>
+                        <EntityRowHeader
+                            type={grid.type}
+                            entity={grid.entities[rowIndex]}
+                            isFirstRow={rowIndex === 0}
+                            isLastRow={rowIndex === grid.entities.length - 1}
+                        />
+                    }
+                    renderCorner={() =>
+                        <CornerHeader
+                            grid={grid}
+                        />
+                    }
                 />
             }
-        />
+        </AutoSizer>
     );
 }
 
diff --git a/frontend/src/metabase/admin/permissions/selectors.js b/frontend/src/metabase/admin/permissions/selectors.js
index 475050cbc51..6246c65825b 100644
--- a/frontend/src/metabase/admin/permissions/selectors.js
+++ b/frontend/src/metabase/admin/permissions/selectors.js
@@ -242,7 +242,6 @@ export const getDatabasesPermissionsGrid = createSelector(
                         databaseId: database.id
                     },
                     name: database.name,
-                    subtitle: database.details.dbname,
                     link:
                         schemas.length === 0 || (schemas.length === 1 && schemas[0] === "") ?
                             { name: "View tables", url: `/admin/permissions/databases/${database.id}/tables` }
diff --git a/frontend/src/metabase/components/ConfirmContent.jsx b/frontend/src/metabase/components/ConfirmContent.jsx
index bb4b7731846..fcd21da1fab 100644
--- a/frontend/src/metabase/components/ConfirmContent.jsx
+++ b/frontend/src/metabase/components/ConfirmContent.jsx
@@ -7,7 +7,7 @@ const ConfirmContent = ({ title, content, onClose, onAction, message = "Are you
         title={title}
         closeFn={onClose}
     >
-        {content}
+        <div className="mx4">{content}</div>
 
         <div className="Form-inputs mb4">
             <p>{message}</p>
diff --git a/frontend/src/metabase/components/EditBar.jsx b/frontend/src/metabase/components/EditBar.jsx
index 7b7a17b8f98..60998d8e454 100644
--- a/frontend/src/metabase/components/EditBar.jsx
+++ b/frontend/src/metabase/components/EditBar.jsx
@@ -1,4 +1,5 @@
 import React, { Component, PropTypes } from 'react';
+import cx from "classnames";
 
 class EditBar extends Component {
     static propTypes = {
@@ -8,14 +9,29 @@ class EditBar extends Component {
             PropTypes.element,
             PropTypes.array
         ]).isRequired,
+        admin: PropTypes.bool
+    }
+
+    static defaultProps = {
+        admin: false
     }
 
     render () {
-        const { title, subtitle, buttons } = this.props;
+        const { admin, buttons, subtitle, title } = this.props;
         return (
-            <div className="EditHeader wrapper py1 flex align-center" ref="editHeader">
+            <div
+                className={cx(
+                    'EditHeader wrapper py1 flex align-center',
+                    { 'EditHeader--admin' : admin }
+                )}
+                ref="editHeader"
+            >
                 <span className="EditHeader-title">{title}</span>
-                { subtitle && <span className="EditHeader-subtitle mx1">{subtitle}</span> }
+                { subtitle && (
+                    <span className="EditHeader-subtitle mx1">
+                        {subtitle}
+                    </span>
+                )}
                 <span className="flex-align-right">
                     {buttons}
                 </span>
diff --git a/frontend/src/metabase/css/components/header.css b/frontend/src/metabase/css/components/header.css
index 1edb7d5e475..e3fb14b9504 100644
--- a/frontend/src/metabase/css/components/header.css
+++ b/frontend/src/metabase/css/components/header.css
@@ -38,7 +38,12 @@
     background-color: #6CAFED;
 }
 
+.EditHeader.EditHeader--admin {
+    background-color: #8C95A2;
+}
+
 .EditHeader-title {
+    font-weight: 700;
     color: white;
 }
 
@@ -64,3 +69,11 @@
     color: white;
     background-color: var(--brand-color);
 }
+
+.EditHeader.EditHeader--admin .Button {
+    color: white;
+}
+
+.EditHeader.EditHeader--admin .Button.Button--primary {
+    color: var(--brand-color);
+}
diff --git a/frontend/src/metabase/questions/components/LabelIconPicker.jsx b/frontend/src/metabase/questions/components/LabelIconPicker.jsx
index b864375175a..c78d4898592 100644
--- a/frontend/src/metabase/questions/components/LabelIconPicker.jsx
+++ b/frontend/src/metabase/questions/components/LabelIconPicker.jsx
@@ -7,7 +7,7 @@ import Icon from "metabase/components/Icon.jsx";
 import LabelIcon from "metabase/components/LabelIcon.jsx";
 import PopoverWithTrigger from "metabase/components/PopoverWithTrigger.jsx";
 
-import { VirtualScroll } from "react-virtualized";
+import { List } from "react-virtualized";
 import "react-virtualized/styles.css";
 
 import * as colors from "metabase/lib/colors";
@@ -79,16 +79,16 @@ export default class LabelIconPicker extends Component {
                 triggerElement={<LabelIconButton value={value} />}
                 ref="popover"
             >
-                <VirtualScroll
+                <List
                   width={WIDTH}
                   height={HEIGHT}
-                  rowsCount={ROWS.length}
+                  rowCount={ROWS.length}
                   rowHeight={ROW_HEIGHT}
-                  rowRenderer={ (index) =>
+                  rowRenderer={ ({ index, key, style }) =>
                       ROWS[index].type === "header" ?
-                          <div className={S.sectionHeader}>{ROWS[index].title}</div>
+                          <div key={key} style={style} className={S.sectionHeader}>{ROWS[index].title}</div>
                       :
-                          <ul className={S.list}>
+                          <ul key={key} style={style} className={S.list}>
                               { ROWS[index].icons.map(icon =>
                                   <li key={icon} className={S.option} onClick={() => { onChange(icon); this.refs.popover.close() }}>
                                       <LabelIcon icon={icon} size={28} />
diff --git a/frontend/src/metabase/visualizations/TableInteractive.jsx b/frontend/src/metabase/visualizations/TableInteractive.jsx
index 4779ffe7d1e..d647f5a1be8 100644
--- a/frontend/src/metabase/visualizations/TableInteractive.jsx
+++ b/frontend/src/metabase/visualizations/TableInteractive.jsx
@@ -293,7 +293,7 @@ export default class TableInteractive extends Component {
                     ref="table"
                     rowHeight={35}
                     rowGetter={this.rowGetter}
-                    rowsCount={this.props.data.rows.length}
+                    rowCount={this.props.data.rows.length}
                     width={this.state.width}
                     height={this.state.height}
                     headerHeight={50}
diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json
index f62012dab79..bc1f2271113 100644
--- a/npm-shrinkwrap.json
+++ b/npm-shrinkwrap.json
@@ -5392,6 +5392,25 @@
                         }
                       }
                     },
+                    "babel-plugin-transform-object-rest-spread": {
+                      "version": "6.16.0",
+                      "dependencies": {
+                        "babel-plugin-syntax-object-rest-spread": {
+                          "version": "6.13.0"
+                        },
+                        "babel-runtime": {
+                          "version": "6.11.6",
+                          "dependencies": {
+                            "core-js": {
+                              "version": "2.4.1"
+                            },
+                            "regenerator-runtime": {
+                              "version": "0.9.5"
+                            }
+                          }
+                        }
+                      }
+                    },
                     "babel-plugin-transform-object-rest-spread": {
                       "version": "6.16.0",
                       "dependencies": {
@@ -11992,18 +12011,21 @@
       "version": "1.1.0"
     },
     "react-virtualized": {
-      "version": "6.3.2",
+      "version": "8.0.12",
       "dependencies": {
-        "dom-helpers": {
-          "version": "2.4.0"
-        },
-        "raf": {
-          "version": "3.3.0",
+        "babel-runtime": {
+          "version": "6.11.6",
           "dependencies": {
-            "performance-now": {
-              "version": "0.2.0"
+            "core-js": {
+              "version": "2.4.1"
+            },
+            "regenerator-runtime": {
+              "version": "0.9.5"
             }
           }
+        },
+        "dom-helpers": {
+          "version": "2.4.0"
         }
       }
     },
diff --git a/package.json b/package.json
index c43ada6b61a..ac005c8ee93 100644
--- a/package.json
+++ b/package.json
@@ -50,7 +50,7 @@
     "react-router": "^2.6.0",
     "react-router-redux": "^4.0.5",
     "react-sortable": "^1.0.1",
-    "react-virtualized": "^6.1.2",
+    "react-virtualized": "^8.0.12",
     "recompose": "^0.20.2",
     "redux": "^3.5.2",
     "redux-actions": "^0.9.1",
-- 
GitLab