mirror of
https://github.com/apache/superset.git
synced 2026-04-19 16:14:52 +00:00
chore: type welcome (#10317)
This commit is contained in:
29
superset-frontend/package-lock.json
generated
29
superset-frontend/package-lock.json
generated
@@ -7658,6 +7658,11 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz",
|
||||
"integrity": "sha512-mky/O83TXmGY39P1H9YbUpjV6l6voRYlufqfFCvel8l1phuy8HRjdWc1rrPuN53ITBJlbyMSV6z3niOySO5pgQ=="
|
||||
},
|
||||
"@types/fetch-mock": {
|
||||
"version": "7.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/fetch-mock/-/fetch-mock-7.3.2.tgz",
|
||||
"integrity": "sha512-NCEfv49jmDsBAixjMjEHKVgmVQlJ+uK56FOc+2roYPExnXCZDpi6mJOHQ3v23BiO84hBDStND9R2itJr7PNoow=="
|
||||
},
|
||||
"@types/glob": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz",
|
||||
@@ -7678,6 +7683,11 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/history": {
|
||||
"version": "4.7.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.6.tgz",
|
||||
"integrity": "sha512-GRTZLeLJ8ia00ZH8mxMO8t0aC9M1N9bN461Z2eaRurJo6Fpa+utgCwLzI4jQHcrdzuzp5WPN9jRwpsCQ1VhJ5w=="
|
||||
},
|
||||
"@types/istanbul-lib-coverage": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz",
|
||||
@@ -7837,6 +7847,25 @@
|
||||
"redux": "^3.6.0"
|
||||
}
|
||||
},
|
||||
"@types/react-router": {
|
||||
"version": "5.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.8.tgz",
|
||||
"integrity": "sha512-HzOyJb+wFmyEhyfp4D4NYrumi+LQgQL/68HvJO+q6XtuHSDvw6Aqov7sCAhjbNq3bUPgPqbdvjXC5HeB2oEAPg==",
|
||||
"requires": {
|
||||
"@types/history": "*",
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"@types/react-router-dom": {
|
||||
"version": "5.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.1.5.tgz",
|
||||
"integrity": "sha512-ArBM4B1g3BWLGbaGvwBGO75GNFbLDUthrDojV2vHLih/Tq8M+tgvY1DSwkuNrPSwdp/GUL93WSEpTZs8nVyJLw==",
|
||||
"requires": {
|
||||
"@types/history": "*",
|
||||
"@types/react": "*",
|
||||
"@types/react-router": "*"
|
||||
}
|
||||
},
|
||||
"@types/react-select": {
|
||||
"version": "3.0.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-select/-/react-select-3.0.12.tgz",
|
||||
|
||||
@@ -102,9 +102,11 @@
|
||||
"@superset-ui/validator": "^0.14.9",
|
||||
"@types/classnames": "^2.2.9",
|
||||
"@types/enzyme": "^3.10.5",
|
||||
"@types/fetch-mock": "^7.3.2",
|
||||
"@types/react-bootstrap": "^0.32.21",
|
||||
"@types/react-gravatar": "^2.6.8",
|
||||
"@types/react-json-tree": "^0.6.11",
|
||||
"@types/react-router-dom": "^5.1.5",
|
||||
"@types/react-select": "^3.0.12",
|
||||
"@types/react-virtualized": "^9.21.10",
|
||||
"@types/react-window": "^1.8.2",
|
||||
|
||||
@@ -55,7 +55,7 @@ describe('DashboardTable', () => {
|
||||
expect(fetchMock.calls(dashboardsEndpoint)).toHaveLength(1);
|
||||
// there's a delay between response and updating state, so manually set it
|
||||
// rather than adding a timeout which could introduce flakiness
|
||||
wrapper.setState({ dashaboards: mockDashboards });
|
||||
wrapper.setState({ dashboards: mockDashboards });
|
||||
expect(wrapper.find(ListView)).toHaveLength(1);
|
||||
done();
|
||||
});
|
||||
@@ -23,11 +23,21 @@ import { shallow } from 'enzyme';
|
||||
import Welcome from 'src/welcome/Welcome';
|
||||
|
||||
describe('Welcome', () => {
|
||||
const mockedProps = {};
|
||||
const mockedProps = {
|
||||
user: {
|
||||
username: 'alpha',
|
||||
firstName: 'alpha',
|
||||
lastName: 'alpha',
|
||||
createdOn: '2016-11-11T12:34:17',
|
||||
userId: 5,
|
||||
email: 'alpha@alpha.com',
|
||||
isActive: true,
|
||||
},
|
||||
};
|
||||
it('is valid', () => {
|
||||
expect(React.isValidElement(<Welcome {...mockedProps} />)).toBe(true);
|
||||
});
|
||||
it('renders 4 Tab, Panel, and Row components', () => {
|
||||
it('renders 3 Tab, Panel, and Row components', () => {
|
||||
const wrapper = shallow(<Welcome {...mockedProps} />);
|
||||
expect(wrapper.find(Tab)).toHaveLength(3);
|
||||
expect(wrapper.find(Panel)).toHaveLength(3);
|
||||
@@ -25,10 +25,10 @@ import UserInfo from './UserInfo';
|
||||
import Security from './Security';
|
||||
import RecentActivity from './RecentActivity';
|
||||
import CreatedContent from './CreatedContent';
|
||||
import { User } from '../types';
|
||||
import { UserWithPermissionsAndRoles } from '../../types/bootstrapTypes';
|
||||
|
||||
interface AppProps {
|
||||
user: User;
|
||||
user: UserWithPermissionsAndRoles;
|
||||
}
|
||||
|
||||
export default function App({ user }: AppProps) {
|
||||
|
||||
@@ -21,7 +21,8 @@ import moment from 'moment';
|
||||
import { t } from '@superset-ui/translation';
|
||||
|
||||
import TableLoader from '../../components/TableLoader';
|
||||
import { User, Dashboard, Slice } from '../types';
|
||||
import { Slice } from '../types';
|
||||
import { User, Dashboard } from '../../types/bootstrapTypes';
|
||||
|
||||
interface CreatedContentProps {
|
||||
user: User;
|
||||
|
||||
@@ -21,7 +21,8 @@ import moment from 'moment';
|
||||
import { t } from '@superset-ui/translation';
|
||||
|
||||
import TableLoader from '../../components/TableLoader';
|
||||
import { User, Dashboard, Slice } from '../types';
|
||||
import { Slice } from '../types';
|
||||
import { User, Dashboard } from '../../types/bootstrapTypes';
|
||||
|
||||
interface FavoritesProps {
|
||||
user: User;
|
||||
|
||||
@@ -20,7 +20,8 @@ import React from 'react';
|
||||
import moment from 'moment';
|
||||
|
||||
import TableLoader from '../../components/TableLoader';
|
||||
import { User, Activity } from '../types';
|
||||
import { Activity } from '../types';
|
||||
import { User } from '../../types/bootstrapTypes';
|
||||
|
||||
interface RecentActivityProps {
|
||||
user: User;
|
||||
|
||||
@@ -19,10 +19,10 @@
|
||||
import React from 'react';
|
||||
import { Badge, Label } from 'react-bootstrap';
|
||||
import { t } from '@superset-ui/translation';
|
||||
import { User } from '../types';
|
||||
import { UserWithPermissionsAndRoles } from '../../types/bootstrapTypes';
|
||||
|
||||
interface SecurityProps {
|
||||
user: User;
|
||||
user: UserWithPermissionsAndRoles;
|
||||
}
|
||||
|
||||
export default function Security({ user }: SecurityProps) {
|
||||
|
||||
@@ -21,10 +21,10 @@ import Gravatar from 'react-gravatar';
|
||||
import moment from 'moment';
|
||||
import { Panel } from 'react-bootstrap';
|
||||
import { t } from '@superset-ui/translation';
|
||||
import { User } from '../types';
|
||||
import { UserWithPermissionsAndRoles } from '../../types/bootstrapTypes';
|
||||
|
||||
interface UserInfoProps {
|
||||
user: User;
|
||||
user: UserWithPermissionsAndRoles;
|
||||
}
|
||||
|
||||
export default function UserInfo({ user }: UserInfoProps) {
|
||||
|
||||
@@ -16,21 +16,6 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
export type User = {
|
||||
createdOn: string;
|
||||
email: string;
|
||||
firstName: string;
|
||||
isActive: boolean;
|
||||
lastName: string;
|
||||
permissions: {
|
||||
database_access?: string[];
|
||||
datasource_access?: string[];
|
||||
};
|
||||
roles: Record<string, any>;
|
||||
userId: number;
|
||||
username: string;
|
||||
};
|
||||
|
||||
export type Slice = {
|
||||
dttm: number;
|
||||
id: number;
|
||||
@@ -41,15 +26,6 @@ export type Slice = {
|
||||
viz_type: string;
|
||||
};
|
||||
|
||||
export type Dashboard = {
|
||||
dttm: number;
|
||||
id: number;
|
||||
url: string;
|
||||
title: string;
|
||||
creator?: string;
|
||||
creator_url?: string;
|
||||
};
|
||||
|
||||
export type Activity = {
|
||||
action: string;
|
||||
item_title: string;
|
||||
|
||||
44
superset-frontend/src/types/bootstrapTypes.ts
Normal file
44
superset-frontend/src/types/bootstrapTypes.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
export type User = {
|
||||
createdOn: string;
|
||||
email: string;
|
||||
firstName: string;
|
||||
isActive: boolean;
|
||||
lastName: string;
|
||||
userId: number;
|
||||
username: string;
|
||||
};
|
||||
|
||||
export interface UserWithPermissionsAndRoles extends User {
|
||||
permissions: {
|
||||
database_access?: string[];
|
||||
datasource_access?: string[];
|
||||
};
|
||||
roles: Record<string, string[][]>;
|
||||
}
|
||||
|
||||
export type Dashboard = {
|
||||
dttm: number;
|
||||
id: number;
|
||||
url: string;
|
||||
title: string;
|
||||
creator?: string;
|
||||
creator_url?: string;
|
||||
};
|
||||
@@ -44,7 +44,7 @@ setupApp();
|
||||
setupPlugins();
|
||||
|
||||
const container = document.getElementById('app');
|
||||
const bootstrap = JSON.parse(container.getAttribute('data-bootstrap'));
|
||||
const bootstrap = JSON.parse(container?.getAttribute('data-bootstrap') ?? '{}');
|
||||
const user = { ...bootstrap.user };
|
||||
const menu = { ...bootstrap.common.menu_data };
|
||||
const common = { ...bootstrap.common };
|
||||
@@ -17,35 +17,45 @@
|
||||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { t } from '@superset-ui/translation';
|
||||
import { SupersetClient } from '@superset-ui/connection';
|
||||
import moment from 'moment';
|
||||
import { debounce } from 'lodash';
|
||||
import ListView from 'src/components/ListView/ListView';
|
||||
import withToasts from 'src/messageToasts/enhancers/withToasts';
|
||||
import { Dashboard } from 'src/types/bootstrapTypes';
|
||||
import { FetchDataConfig } from 'src/components/ListView/types';
|
||||
|
||||
const PAGE_SIZE = 25;
|
||||
|
||||
class DashboardTable extends React.PureComponent {
|
||||
static propTypes = {
|
||||
addDangerToast: PropTypes.func.isRequired,
|
||||
search: PropTypes.string,
|
||||
};
|
||||
interface DashboardTableProps {
|
||||
addDangerToast: (message: string) => void;
|
||||
search?: string;
|
||||
}
|
||||
|
||||
interface DashboardTableState {
|
||||
dashboards: Dashboard[];
|
||||
dashboard_count: number;
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
class DashboardTable extends React.PureComponent<
|
||||
DashboardTableProps,
|
||||
DashboardTableState
|
||||
> {
|
||||
state = {
|
||||
dashboards: [],
|
||||
dashboard_count: 0,
|
||||
loading: false,
|
||||
};
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
componentDidUpdate(prevProps: DashboardTableProps) {
|
||||
if (prevProps.search !== this.props.search) {
|
||||
this.fetchDataDebounced({
|
||||
pageSize: PAGE_SIZE,
|
||||
pageIndex: 0,
|
||||
sortBy: this.initialSort,
|
||||
filters: {},
|
||||
filters: [],
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -58,6 +68,13 @@ class DashboardTable extends React.PureComponent {
|
||||
row: {
|
||||
original: { url, dashboard_title: dashboardTitle },
|
||||
},
|
||||
}: {
|
||||
row: {
|
||||
original: {
|
||||
url: string;
|
||||
dashboard_title: string;
|
||||
};
|
||||
};
|
||||
}) => <a href={url}>{dashboardTitle}</a>,
|
||||
},
|
||||
{
|
||||
@@ -67,6 +84,13 @@ class DashboardTable extends React.PureComponent {
|
||||
row: {
|
||||
original: { changed_by_name: changedByName, changedByUrl },
|
||||
},
|
||||
}: {
|
||||
row: {
|
||||
original: {
|
||||
changed_by_name: string;
|
||||
changedByUrl: string;
|
||||
};
|
||||
};
|
||||
}) => <a href={changedByUrl}>{changedByName}</a>,
|
||||
},
|
||||
{
|
||||
@@ -76,13 +100,19 @@ class DashboardTable extends React.PureComponent {
|
||||
row: {
|
||||
original: { changed_on: changedOn },
|
||||
},
|
||||
}: {
|
||||
row: {
|
||||
original: {
|
||||
changed_on: string;
|
||||
};
|
||||
};
|
||||
}) => <span className="no-wrap">{moment(changedOn).fromNow()}</span>,
|
||||
},
|
||||
];
|
||||
|
||||
initialSort = [{ id: 'changed_on', desc: true }];
|
||||
|
||||
fetchData = ({ pageIndex, pageSize, sortBy, filters }) => {
|
||||
fetchData = ({ pageIndex, pageSize, sortBy, filters }: FetchDataConfig) => {
|
||||
this.setState({ loading: true });
|
||||
const filterExps = Object.keys(filters)
|
||||
.map(fk => ({
|
||||
@@ -17,23 +17,30 @@
|
||||
* under the License.
|
||||
*/
|
||||
import React, { useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Panel, Row, Col, Tabs, Tab, FormControl } from 'react-bootstrap';
|
||||
import { t } from '@superset-ui/translation';
|
||||
import { useQueryParam, StringParam } from 'use-query-params';
|
||||
import { useQueryParam, StringParam, QueryParamConfig } from 'use-query-params';
|
||||
import { User } from 'src/types/bootstrapTypes';
|
||||
import RecentActivity from '../profile/components/RecentActivity';
|
||||
import Favorites from '../profile/components/Favorites';
|
||||
import DashboardTable from './DashboardTable';
|
||||
|
||||
const propTypes = {
|
||||
user: PropTypes.object.isRequired,
|
||||
};
|
||||
interface WelcomeProps {
|
||||
user: User;
|
||||
}
|
||||
|
||||
function useSyncQueryState(queryParam, queryParamType, defaultState) {
|
||||
function useSyncQueryState(
|
||||
queryParam: string,
|
||||
queryParamType: QueryParamConfig<
|
||||
string | null | undefined,
|
||||
string | undefined
|
||||
>,
|
||||
defaultState: string,
|
||||
): [string, (val: string) => void] {
|
||||
const [queryState, setQueryState] = useQueryParam(queryParam, queryParamType);
|
||||
const [state, setState] = useState(queryState || defaultState);
|
||||
|
||||
const setQueryStateAndState = val => {
|
||||
const setQueryStateAndState = (val: string) => {
|
||||
setQueryState(val);
|
||||
setState(val);
|
||||
};
|
||||
@@ -41,7 +48,7 @@ function useSyncQueryState(queryParam, queryParamType, defaultState) {
|
||||
return [state, setQueryStateAndState];
|
||||
}
|
||||
|
||||
export default function Welcome({ user }) {
|
||||
export default function Welcome({ user }: WelcomeProps) {
|
||||
const [activeTab, setActiveTab] = useSyncQueryState(
|
||||
'activeTab',
|
||||
StringParam,
|
||||
@@ -58,6 +65,7 @@ export default function Welcome({ user }) {
|
||||
<div className="container welcome">
|
||||
<Tabs
|
||||
activeKey={activeTab}
|
||||
// @ts-ignore React bootstrap types aren't quite right here
|
||||
onSelect={setActiveTab}
|
||||
id="uncontrolled-tab-example"
|
||||
>
|
||||
@@ -75,6 +83,7 @@ export default function Welcome({ user }) {
|
||||
style={{ marginTop: '25px' }}
|
||||
placeholder="Search"
|
||||
value={searchQuery}
|
||||
// @ts-ignore React bootstrap types aren't quite right here
|
||||
onChange={e => setSearchQuery(e.currentTarget.value)}
|
||||
/>
|
||||
</Col>
|
||||
@@ -114,5 +123,3 @@ export default function Welcome({ user }) {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Welcome.propTypes = propTypes;
|
||||
@@ -181,7 +181,7 @@ const config = {
|
||||
explore: addPreamble('/src/explore/index.jsx'),
|
||||
dashboard: addPreamble('/src/dashboard/index.jsx'),
|
||||
sqllab: addPreamble('/src/SqlLab/index.tsx'),
|
||||
welcome: addPreamble('/src/welcome/index.jsx'),
|
||||
welcome: addPreamble('/src/welcome/index.tsx'),
|
||||
profile: addPreamble('/src/profile/index.tsx'),
|
||||
showSavedQuery: [path.join(APP_DIR, '/src/showSavedQuery/index.jsx')],
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user