diff --git a/frontend/src/metabase/components/EmptyState.jsx b/frontend/src/metabase/components/EmptyState.jsx index 476bb2824a8f6356466b5af459402dd5ac15b9cc..119b4c6f9a67f8d904df1a159a1dac969b862613 100644 --- a/frontend/src/metabase/components/EmptyState.jsx +++ b/frontend/src/metabase/components/EmptyState.jsx @@ -1,12 +1,11 @@ /* @flow */ import React, {PropTypes} from "react"; import {Link} from "react-router"; -import cx from "classnames"; /* * EmptyState is a component that can * 1) introduce a new section of Metabase to a user, and encourages the user to take an action - * 2) indicate an empty result after an user-triggered search/query + * 2) indicate an empty result after a user-triggered search/query */ diff --git a/frontend/src/metabase/dashboards/components/DashboardList.jsx b/frontend/src/metabase/dashboards/components/DashboardList.jsx index fcd9b5e5a5560a52724193223b6aa3d3f8132b85..db4fea30a7ce50c86d2920a53f331f009ee4a897 100644 --- a/frontend/src/metabase/dashboards/components/DashboardList.jsx +++ b/frontend/src/metabase/dashboards/components/DashboardList.jsx @@ -19,7 +19,7 @@ type DashboardListItemType = { const enhance = withState('hover', 'setHover', false) const DashboardListItem = enhance(({dashboard, hover, setHover}: DashboardListItemType) => - <li key={dashboard.id} className="Grid-cell flex-retain-width"> + <li className="Grid-cell flex-retain-width"> <Link to={Urls.dashboard(dashboard.id)} data-metabase-event={"Navbar;Dashboards;Open Dashboard;" + dashboard.id} className="flex align-center border-box p2 bg-white bg-brand-hover rounded hover-parent hover--display no-decoration" @@ -54,9 +54,10 @@ export default class DashboardList extends Component { render() { const {dashboards} = this.props; + console.log('daash:', dashboards) return ( <ol className="Grid Grid--guttersXl Grid--1of2 large-Grid--1of3"> - { dashboards.map(dash => <DashboardListItem dashboard={dash}/>)} + { dashboards.map(dash => <DashboardListItem key={dash.id} dashboard={dash}/>)} </ol> ); } diff --git a/frontend/src/metabase/dashboards/containers/Dashboards.spec.data.js b/frontend/src/metabase/dashboards/containers/Dashboards.spec.data.js new file mode 100644 index 0000000000000000000000000000000000000000..9e91094898e3e5b9c672697eaec36e8e659a66b5 --- /dev/null +++ b/frontend/src/metabase/dashboards/containers/Dashboards.spec.data.js @@ -0,0 +1,81 @@ +import reducers from 'metabase/reducers-main'; +import { combineReducers, createStore, applyMiddleware} from 'redux' + +import thunk from "redux-thunk"; +import promise from "redux-promise"; +import logger from "redux-logger"; + +const createTestStore = (initialState) => + createStore(combineReducers(reducers), initialState, applyMiddleware(thunk, promise, logger())) + +export const noDashboardsStore = createTestStore({dashboards: {dashboardListing: []}}); + +// Dumped from redux state tree 4/10/17 +export const twoDashboardsStore = createTestStore({ + dashboards: { + dashboardListing: [ + { + description: 'For seeing the usual response times, feedback topics, our response rate, how often customers are directed to our knowledge base instead of providing a customized response', + creator: { + email: 'atte@metabase.com', + first_name: 'Atte', + last_login: '2017-04-10T23:56:12.562Z', + is_qbnewb: false, + is_superuser: false, + id: 1, + last_name: 'Keinänen', + date_joined: '2017-03-17T03:37:27.396Z', + common_name: 'Atte Keinänen' + }, + enable_embedding: false, + show_in_getting_started: false, + name: 'Customer Feedback Analysis', + caveats: null, + creator_id: 1, + updated_at: '2017-04-05T00:23:20.991Z', + made_public_by_id: null, + embedding_params: null, + id: 3, + parameters: [ + { + name: 'Date Range', + slug: 'date_range', + id: 'fc68dfa5', + type: 'date/range', + 'default': '2017-03-07~2017-03-14' + } + ], + created_at: '2017-03-28T23:24:16.891Z', + public_uuid: null, + points_of_interest: null + }, + { + description: 'For seeing our progress over time, and if there are bottlenecks in our product development process', + creator: { + email: 'atte@metabase.com', + first_name: 'Atte', + last_login: '2017-04-10T23:56:12.562Z', + is_qbnewb: false, + is_superuser: false, + id: 1, + last_name: 'Keinänen', + date_joined: '2017-03-17T03:37:27.396Z', + common_name: 'Atte Keinänen' + }, + enable_embedding: false, + show_in_getting_started: false, + name: 'Kanban Breakdown Chart', + caveats: null, + creator_id: 1, + updated_at: '2017-04-04T20:41:50.616Z', + made_public_by_id: null, + embedding_params: null, + id: 7, + parameters: [], + created_at: '2017-04-04T20:39:01.263Z', + public_uuid: null, + points_of_interest: null + } + ] + } +}); diff --git a/frontend/src/metabase/dashboards/containers/Dashboards.spec.js b/frontend/src/metabase/dashboards/containers/Dashboards.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..207d0f45c3a775149146032360507fda0d41f684 --- /dev/null +++ b/frontend/src/metabase/dashboards/containers/Dashboards.spec.js @@ -0,0 +1,28 @@ +import React from 'react'; +import renderer from 'react-test-renderer'; +import {Provider} from 'react-redux'; + +import Dashboards from './Dashboards'; +import {noDashboardsStore, twoDashboardsStore} from './Dashboards.spec.data'; + +describe('Dashboards list view', () => { + it('should render correctly without dashboards', () => { + const tree = renderer.create( + <Provider store={noDashboardsStore}> + <Dashboards /> + </Provider>).toJSON(); + + console.log(tree); + expect(tree).toMatchSnapshot() + }) + + it('should render correctly with two dashboards', () => { + const tree = renderer.create( + <Provider store={twoDashboardsStore}> + <Dashboards /> + </Provider>).toJSON(); + + console.log(tree); + expect(tree).toMatchSnapshot() + }) +}) \ No newline at end of file diff --git a/frontend/src/metabase/dashboards/containers/__snapshots__/Dashboards.spec.js.snap b/frontend/src/metabase/dashboards/containers/__snapshots__/Dashboards.spec.js.snap new file mode 100644 index 0000000000000000000000000000000000000000..453d329920f4091d0fff1e66f7d6221e96d63a91 --- /dev/null +++ b/frontend/src/metabase/dashboards/containers/__snapshots__/Dashboards.spec.js.snap @@ -0,0 +1,183 @@ +exports[`Dashboards list view should render correctly with two dashboards 1`] = ` +<div + className="relative mx4" + style={undefined}> + <div> + <div + className="flex align-center pt4 pb1"> + <div + className="flex align-center"> + <h2 + className="mr1"> + Dashboards + </h2> + </div> + <svg + className="flex-align-right px4 cursor-pointer text-grey-5 text-brand-hover" + fill="currentcolor" + height={20} + name="add" + onClick={[Function]} + size={20} + viewBox="0 0 32 32" + width={20}> + <path + d="M19,13 L19,2 L14,2 L14,13 L2,13 L2,18 L14,18 L14,30 L19,30 L19,18 L30,18 L30,13 L19,13 Z" /> + </svg> + </div> + <div + className="flex align-center pb1"> + <div + className="searchHeader"> + <svg + className="searchIcon" + fill="currentcolor" + height={18} + name="search" + size={18} + viewBox="0 0 32 32" + width={18}> + <path + d="M12 0 A12 12 0 0 0 0 12 A12 12 0 0 0 12 24 A12 12 0 0 0 18.5 22.25 L28 32 L32 28 L22.25 18.5 A12 12 0 0 0 24 12 A12 12 0 0 0 12 0 M12 4 A8 8 0 0 1 12 20 A8 8 0 0 1 12 4 " /> + </svg> + <input + className="input searchBox" + onChange={[Function]} + placeholder="Filter this list..." + type="text" + value="" /> + </div> + </div> + <ol + className="Grid Grid--guttersXl Grid--1of2 large-Grid--1of3"> + <li + className="Grid-cell flex-retain-width"> + <a + className="flex align-center border-box p2 bg-white bg-brand-hover rounded hover-parent hover--display no-decoration" + data-metabase-event="Navbar;Dashboards;Open Dashboard;3" + onClick={[Function]} + onMouseEnter={[Function]} + onMouseLeave={[Function]} + style={ + Object { + "border": "1px solid rgba(220,225,228,0.50)", + "boxShadow": "0 1px 3px 0 rgba(220,220,220,0.50)", + "height": "80px", + } + }> + <svg + className="pr2 text-grey-1" + fill="currentcolor" + height={32} + name="dashboard" + size={32} + viewBox="0 0 32 32" + width={32}> + <path + d="M32,29 L32,4 L32,0 L0,0 L0,8 L28,8 L28,28 L4,28 L4,8 L0,8 L0,29.5 L0,32 L32,32 L32,29 Z M7.27272727,18.9090909 L17.4545455,18.9090909 L17.4545455,23.2727273 L7.27272727,23.2727273 L7.27272727,18.9090909 Z M7.27272727,12.0909091 L24.7272727,12.0909091 L24.7272727,16.4545455 L7.27272727,16.4545455 L7.27272727,12.0909091 Z M20.3636364,18.9090909 L24.7272727,18.9090909 L24.7272727,23.2727273 L20.3636364,23.2727273 L20.3636364,18.9090909 Z" /> + </svg> + <div + className="flex-full flex-retain-width"> + <h4 + className="text-ellipsis text-nowrap overflow-hidden text-brand" + style={ + Object { + "marginBottom": "0.2em", + } + }> + Customer Feedback Analysis + </h4> + <div + className="text-small text-uppercase text-bold text-grey-3"> + Mar 28, 2017 + </div> + </div> + </a> + </li> + <li + className="Grid-cell flex-retain-width"> + <a + className="flex align-center border-box p2 bg-white bg-brand-hover rounded hover-parent hover--display no-decoration" + data-metabase-event="Navbar;Dashboards;Open Dashboard;7" + onClick={[Function]} + onMouseEnter={[Function]} + onMouseLeave={[Function]} + style={ + Object { + "border": "1px solid rgba(220,225,228,0.50)", + "boxShadow": "0 1px 3px 0 rgba(220,220,220,0.50)", + "height": "80px", + } + }> + <svg + className="pr2 text-grey-1" + fill="currentcolor" + height={32} + name="dashboard" + size={32} + viewBox="0 0 32 32" + width={32}> + <path + d="M32,29 L32,4 L32,0 L0,0 L0,8 L28,8 L28,28 L4,28 L4,8 L0,8 L0,29.5 L0,32 L32,32 L32,29 Z M7.27272727,18.9090909 L17.4545455,18.9090909 L17.4545455,23.2727273 L7.27272727,23.2727273 L7.27272727,18.9090909 Z M7.27272727,12.0909091 L24.7272727,12.0909091 L24.7272727,16.4545455 L7.27272727,16.4545455 L7.27272727,12.0909091 Z M20.3636364,18.9090909 L24.7272727,18.9090909 L24.7272727,23.2727273 L20.3636364,23.2727273 L20.3636364,18.9090909 Z" /> + </svg> + <div + className="flex-full flex-retain-width"> + <h4 + className="text-ellipsis text-nowrap overflow-hidden text-brand" + style={ + Object { + "marginBottom": "0.2em", + } + }> + Kanban Breakdown Chart + </h4> + <div + className="text-small text-uppercase text-bold text-grey-3"> + Apr 4, 2017 + </div> + </div> + </a> + </li> + </ol> + </div> +</div> +`; + +exports[`Dashboards list view should render correctly without dashboards 1`] = ` +<div + className="relative mx4 flex-full flex align-center justify-center" + style={undefined}> + <div + className="mt2"> + <div + className="text-centered text-brand-light my2"> + <img + alt={ + <div> + Put the charts and graphs you look at + <br /> + frequently in a single, handy place. + </div> + } + className="mln2" + height="250px" + src="/app/img/dashboard_illustration.png" + srcSet="/app/img/dashboard_illustration@2x.png 2x" /> + <div + className="flex justify-center"> + <div> + Put the charts and graphs you look at + <br /> + frequently in a single, handy place. + </div> + } + </div> + <a + className="Button Button--primary mt3" + onClick={[Function]}> + Create a dashboard + </a> + </div> + </div> +</div> +`; diff --git a/frontend/src/metabase/lib/formatting.js b/frontend/src/metabase/lib/formatting.js index 1a2a30921af916035253fe3af8f0f1092a0d90f7..f6db24482f44966263512c399afdcf79bdefa09f 100644 --- a/frontend/src/metabase/lib/formatting.js +++ b/frontend/src/metabase/lib/formatting.js @@ -1,7 +1,7 @@ import d3 from "d3"; import inflection from "inflection"; import moment from "moment"; -import Humanize from "humanize"; +import Humanize from "humanize-plus"; import React from "react"; import ExternalLink from "metabase/components/ExternalLink.jsx"; diff --git a/frontend/test/__mocks__/fileMock.js b/frontend/test/__mocks__/fileMock.js new file mode 100644 index 0000000000000000000000000000000000000000..84c1da6fdcb26b510616f8dbbec6416945fd4104 --- /dev/null +++ b/frontend/test/__mocks__/fileMock.js @@ -0,0 +1 @@ +module.exports = 'test-file-stub'; \ No newline at end of file diff --git a/frontend/test/__mocks__/styleMock.js b/frontend/test/__mocks__/styleMock.js new file mode 100644 index 0000000000000000000000000000000000000000..a0995453769c8a76594b879c09c58064530c155a --- /dev/null +++ b/frontend/test/__mocks__/styleMock.js @@ -0,0 +1 @@ +module.exports = {}; \ No newline at end of file diff --git a/package.json b/package.json index 77edb5a62183b55dc27d90deac3fb94b2cfdd11f..e64f568cf3c2348e5fd78006a1491bbf80562e67 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "history": "^4.5.0", "humanize-plus": "^1.8.1", "icepick": "^1.1.0", + "identity-obj-proxy": "^3.0.0", "inflection": "^1.7.1", "isomorphic-fetch": "^2.2.1", "js-cookie": "^2.1.2", @@ -173,11 +174,19 @@ ] }, "jest": { + "moduleNameMapper": { + "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/frontend/test/__mocks__/fileMock.js", + "\\.(css|less)$": "identity-obj-proxy", + "^promise-loader": "<rootDir>/frontend/test/__mocks__/fileMock.js" + }, "testPathIgnorePatterns": [ "<rootDir>/frontend/test/" ], "modulePaths": [ "<rootDir>/frontend/src" + ], + "setupFiles": [ + "<rootDir>/frontend/test/metabase-bootstrap.js" ] } } diff --git a/yarn.lock b/yarn.lock index a343edb47001c17cfd759be5ee61eccd6f0f3c6e..6ffd6c53e4f5982cc4058bdaae93cac107909059 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3603,6 +3603,10 @@ har-validator@~2.0.6: is-my-json-valid "^2.12.4" pinkie-promise "^2.0.0" +harmony-reflect@^1.4.6: + version "1.5.1" + resolved "https://registry.yarnpkg.com/harmony-reflect/-/harmony-reflect-1.5.1.tgz#b54ca617b00cc8aef559bbb17b3d85431dc7e329" + has-ansi@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-0.1.0.tgz#84f265aae8c0e6a88a12d7022894b7568894c62e" @@ -3833,6 +3837,12 @@ icss-replace-symbols@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/icss-replace-symbols/-/icss-replace-symbols-1.0.2.tgz#cb0b6054eb3af6edc9ab1d62d01933e2d4c8bfa5" +identity-obj-proxy@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/identity-obj-proxy/-/identity-obj-proxy-3.0.0.tgz#94d2bda96084453ef36fbc5aaec37e0f79f1fc14" + dependencies: + harmony-reflect "^1.4.6" + ieee754@^1.1.4: version "1.1.8" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4"