From 5339d31ed19d47db5d75ddf5042bcb0bab56e289 Mon Sep 17 00:00:00 2001 From: Lily Kuang Date: Wed, 10 Jun 2020 11:55:51 -0700 Subject: [PATCH] feat: implement secondary navigation for datasets (#9982) --- .../views/datasetList/DatasetList_spec.jsx | 4 + .../src/components/Menu/Menu.less | 4 + .../src/components/Menu/SubMenu.tsx | 122 ++++++++++++++++++ .../src/views/datasetList/DatasetList.tsx | 100 +++++++------- 4 files changed, 186 insertions(+), 44 deletions(-) create mode 100644 superset-frontend/src/components/Menu/SubMenu.tsx diff --git a/superset-frontend/spec/javascripts/views/datasetList/DatasetList_spec.jsx b/superset-frontend/spec/javascripts/views/datasetList/DatasetList_spec.jsx index c393a98df..dd34ae827 100644 --- a/superset-frontend/spec/javascripts/views/datasetList/DatasetList_spec.jsx +++ b/superset-frontend/spec/javascripts/views/datasetList/DatasetList_spec.jsx @@ -24,6 +24,8 @@ import fetchMock from 'fetch-mock'; import DatasetList from 'src/views/datasetList/DatasetList'; import ListView from 'src/components/ListView/ListView'; +import { ThemeProvider } from 'emotion-theming'; +import { supersetTheme } from '@superset-ui/style'; // store needed for withToasts(datasetTable) const mockStore = configureStore([thunk]); @@ -67,6 +69,8 @@ describe('DatasetList', () => { const mockedProps = {}; const wrapper = mount(, { context: { store }, + wrappingComponent: ThemeProvider, + wrappingComponentProps: { theme: supersetTheme }, }); it('renders', () => { diff --git a/superset-frontend/src/components/Menu/Menu.less b/superset-frontend/src/components/Menu/Menu.less index 5128153a3..4f037bafb 100644 --- a/superset-frontend/src/components/Menu/Menu.less +++ b/superset-frontend/src/components/Menu/Menu.less @@ -29,6 +29,10 @@ } } + .navbar-inverse { + border-bottom: 2px solid @gray-bg; + } + .version-info { padding: 5px 20px; color: @gray-heading; diff --git a/superset-frontend/src/components/Menu/SubMenu.tsx b/superset-frontend/src/components/Menu/SubMenu.tsx new file mode 100644 index 000000000..437deec77 --- /dev/null +++ b/superset-frontend/src/components/Menu/SubMenu.tsx @@ -0,0 +1,122 @@ +/** + * 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 styled from '@superset-ui/style'; +import { Button, Nav, Navbar, MenuItem } from 'react-bootstrap'; + +const StyledHeader = styled.header` + margin-top: -20px; + .navbar-header .navbar-brand { + font-weight: ${({ theme }) => theme.typography.weights.bold}; + } + + .navbar-right { + .btn-default { + background-color: ${({ theme }) => theme.colors.primary.base}; + border-radius: 4px; + border: none; + color: ${({ theme }) => theme.colors.secondary.light5}; + font-size: ${({ theme }) => theme.typography.sizes.s}; + font-weight: ${({ theme }) => theme.typography.weights.bold}; + margin: 8px 43px; + padding: 8px 51px 8px 43px; + text-transform: uppercase; + i { + padding: 4px ${({ theme }) => theme.typography.sizes.xs}; + } + } + } + + .navbar-nav { + li { + a { + font-size: ${({ theme }) => theme.typography.sizes.s}; + padding: 8px; + margin: 8px; + color: ${({ theme }) => theme.colors.secondary.dark1}; + } + } + + li.active > a, + li > a:hover { + background-color: ${({ theme }) => theme.colors.secondary.light4}; + border-bottom: none; + border-radius: 4px; + } + } +`; + +interface Props { + createButton: { name: string; url: string | null }; + canCreate: boolean; + label: string; + name: string; + childs: Array<{ label: string; name: string; url: string }>; +} + +interface State { + selectedMenu: string; +} + +class SubMenu extends React.PureComponent { + state: State = { + selectedMenu: this.props.childs[0] && this.props.childs[0].label, + }; + + handleClick = (item: string) => () => { + this.setState({ selectedMenu: item }); + }; + + render() { + const { canCreate, childs, label, createButton } = this.props; + + return ( + + + + {label} + + + {canCreate && ( + + )} + + + ); + } +} + +export default SubMenu; diff --git a/superset-frontend/src/views/datasetList/DatasetList.tsx b/superset-frontend/src/views/datasetList/DatasetList.tsx index c8322904a..3e32279f1 100644 --- a/superset-frontend/src/views/datasetList/DatasetList.tsx +++ b/superset-frontend/src/views/datasetList/DatasetList.tsx @@ -24,9 +24,9 @@ import React from 'react'; import rison from 'rison'; // @ts-ignore import { Panel } from 'react-bootstrap'; -import Link from 'src/components/Link'; import ConfirmStatusChange from 'src/components/ConfirmStatusChange'; import ListView from 'src/components/ListView/ListView'; +import SubMenu from 'src/components/Menu/SubMenu'; import { FetchDataConfig, FilterOperatorMap, @@ -241,6 +241,28 @@ class DatasetList extends React.PureComponent { }, ]; + menu = { + label: 'Data', + name: 'Data', + createButton: { + name: t('Dataset'), + url: '/tablemodelview/add', + }, + childs: [ + { + name: 'Datasets', + label: t('Datasets'), + url: '/tablemodelview/list/?_flt_1_is_sqllab_view=y', + }, + { name: 'Databases', label: t('Databases'), url: '/databaseview/list/' }, + { + name: 'Saved Queries', + label: t('Saved Queries'), + url: '/sqllab/my_queries/', + }, + ], + }; + hasPerm = (perm: string) => { if (!this.state.permissions.length) { return false; @@ -388,42 +410,32 @@ class DatasetList extends React.PureComponent { const { datasets, datasetCount, loading, filters } = this.state; return ( -
- - - - {confirmDelete => { - const bulkActions = []; - if (this.canDelete) { - bulkActions.push({ - key: 'delete', - name: ( - <> - Delete - - ), - onSelect: confirmDelete, - }); - } - return ( - <> - {this.canCreate && ( - - - - - - )} + <> + +
+ + + + {confirmDelete => { + const bulkActions = []; + if (this.canDelete) { + bulkActions.push({ + key: 'delete', + name: ( + <> + Delete + + ), + onSelect: confirmDelete, + }); + } + return ( { filters={filters} bulkActions={bulkActions} /> - - ); - }} - - - -
+ ); + }} +
+
+
+
+ ); } }