chore: add typing to profile (#10282)

This commit is contained in:
Erik Ritter 2020-07-14 16:39:37 -07:00 committed by GitHub
parent 11ae48062f
commit 80902bca50
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 246 additions and 201 deletions

View File

@ -7678,22 +7678,6 @@
"@types/node": "*"
}
},
"@types/hoist-non-react-statics": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
"integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
"dev": true,
"requires": {
"@types/react": "*",
"hoist-non-react-statics": "^3.3.0"
},
"dependencies": {
"react-is": {
"version": "https://registry.npmjs.org/react-is/-/react-is-16.13.0.tgz",
"integrity": "sha512-GFMtL0vHkiBv9HluwNZTggSn/sCyEt9n02aM0dSAjGGyqyNlAyftYm4phPxdvCigG15JreC5biwxCgTAJZ7yAA=="
}
}
},
"@types/istanbul-lib-coverage": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz",
@ -7818,6 +7802,14 @@
"@types/react": "*"
}
},
"@types/react-gravatar": {
"version": "2.6.8",
"resolved": "https://registry.npmjs.org/@types/react-gravatar/-/react-gravatar-2.6.8.tgz",
"integrity": "sha512-VMk0bF0w72l+opBm+EqLs0JqUG+hPowMBWCVGrbTwUWm/oDncvwNrf7P/ImwYwkTCKiLnU8Rc+/lyhehaIE/Rw==",
"requires": {
"@types/react": "*"
}
},
"@types/react-json-tree": {
"version": "0.6.11",
"resolved": "https://registry.npmjs.org/@types/react-json-tree/-/react-json-tree-0.6.11.tgz",
@ -7836,31 +7828,13 @@
}
},
"@types/react-redux": {
"version": "7.1.7",
"resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.7.tgz",
"integrity": "sha512-U+WrzeFfI83+evZE2dkZ/oF/1vjIYgqrb5dGgedkqVV8HEfDFujNgWCwHL89TDuWKb47U0nTBT6PLGq4IIogWg==",
"version": "5.0.21",
"resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-5.0.21.tgz",
"integrity": "sha512-ewkOW4GjnyXq5L++T31utI8yRmwj8iCIahZohYi1Ef7Xkrw0V/q92ao7x20rm38FKgImDaCCsaRGWfCJmF/Ukg==",
"dev": true,
"requires": {
"@types/hoist-non-react-statics": "^3.3.0",
"@types/react": "*",
"hoist-non-react-statics": "^3.3.0",
"redux": "^4.0.0"
},
"dependencies": {
"react-is": {
"version": "https://registry.npmjs.org/react-is/-/react-is-16.13.0.tgz",
"integrity": "sha512-GFMtL0vHkiBv9HluwNZTggSn/sCyEt9n02aM0dSAjGGyqyNlAyftYm4phPxdvCigG15JreC5biwxCgTAJZ7yAA=="
},
"redux": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/redux/-/redux-4.0.5.tgz",
"integrity": "sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w==",
"dev": true,
"requires": {
"loose-envify": "^1.4.0",
"symbol-observable": "^1.2.0"
}
}
"redux": "^3.6.0"
}
},
"@types/react-select": {
@ -7923,6 +7897,25 @@
"redux": "^3.6.0"
}
},
"@types/redux-mock-store": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@types/redux-mock-store/-/redux-mock-store-1.0.2.tgz",
"integrity": "sha512-6LBtAQBN34i7SI5X+Qs4zpTEZO1tTDZ6sZ9fzFjYwTl3nLQXaBtwYdoV44CzNnyKu438xJ1lSIYyw0YMvunESw==",
"requires": {
"redux": "^4.0.5"
},
"dependencies": {
"redux": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/redux/-/redux-4.0.5.tgz",
"integrity": "sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w==",
"requires": {
"loose-envify": "^1.4.0",
"symbol-observable": "^1.2.0"
}
}
}
},
"@types/rison": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/@types/rison/-/rison-0.0.6.tgz",
@ -7933,6 +7926,19 @@
"resolved": "https://registry.npmjs.org/@types/shortid/-/shortid-0.0.29.tgz",
"integrity": "sha1-gJPuBBam4r8qpjOBCRFLP7/6Dps="
},
"@types/sinon": {
"version": "9.0.4",
"resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-9.0.4.tgz",
"integrity": "sha512-sJmb32asJZY6Z2u09bl0G2wglSxDlROlAejCjsnor+LzBMz17gu8IU7vKC/vWDnv9zEq2wqADHVXFjf4eE8Gdw==",
"requires": {
"@types/sinonjs__fake-timers": "*"
}
},
"@types/sinonjs__fake-timers": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.1.tgz",
"integrity": "sha512-yYezQwGWty8ziyYLdZjwxyMb0CZR49h8JALHGrxjQHWlqGgc8kLdHEgWrgL0uZ29DMvEVBDnHU2Wg36zKSIUtA=="
},
"@types/sizzle": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.2.tgz",

View File

@ -85,13 +85,13 @@
"@superset-ui/legacy-plugin-chart-sankey": "^0.14.9",
"@superset-ui/legacy-plugin-chart-sankey-loop": "^0.14.9",
"@superset-ui/legacy-plugin-chart-sunburst": "^0.14.9",
"@superset-ui/plugin-chart-table": "^0.14.9",
"@superset-ui/legacy-plugin-chart-treemap": "^0.14.9",
"@superset-ui/legacy-plugin-chart-world-map": "^0.14.9",
"@superset-ui/legacy-preset-chart-big-number": "^0.14.9",
"@superset-ui/legacy-preset-chart-deckgl": "^0.2.4",
"@superset-ui/legacy-preset-chart-nvd3": "^0.14.9",
"@superset-ui/number-format": "^0.14.9",
"@superset-ui/plugin-chart-table": "^0.14.9",
"@superset-ui/plugin-chart-word-cloud": "^0.14.9",
"@superset-ui/preset-chart-xy": "^0.14.9",
"@superset-ui/query": "^0.14.9",
@ -103,14 +103,16 @@
"@types/classnames": "^2.2.9",
"@types/enzyme": "^3.10.5",
"@types/react-bootstrap": "^0.32.21",
"@types/react-gravatar": "^2.6.8",
"@types/react-json-tree": "^0.6.11",
"@types/react-select": "^3.0.12",
"@types/react-virtualized": "^9.21.10",
"@types/react-window": "^1.8.2",
"@types/redux-localstorage": "^1.0.8",
"@types/redux-mock-store": "^1.0.2",
"@types/rison": "0.0.6",
"@types/sinon": "^9.0.4",
"@vx/responsive": "^0.0.195",
"memoize-one": "^5.1.1",
"abortcontroller-polyfill": "^1.1.9",
"aphrodite": "^2.3.1",
"array-move": "^2.2.1",
@ -134,6 +136,7 @@
"lodash": "^4.17.15",
"lodash-es": "^4.17.14",
"mathjs": "^3.20.2",
"memoize-one": "^5.1.1",
"moment": "^2.20.1",
"mousetrap": "^1.6.1",
"mustache": "^2.2.1",
@ -208,7 +211,7 @@
"@types/react": "^16.9.38",
"@types/react-dom": "^16.9.8",
"@types/react-json-tree": "^0.6.11",
"@types/react-redux": "^7.1.7",
"@types/react-redux": "^5.0.2",
"@types/react-table": "^7.0.19",
"@types/react-ultimate-pagination": "^1.2.0",
"@types/yargs": "12 - 15",

View File

@ -158,6 +158,7 @@ export default class ResultSet extends React.PureComponent<
this.props.database &&
this.props.database.allows_virtual_table_explore && (
<ExploreResultsButton
// @ts-ignore Redux types are difficult to work with, ignoring for now
query={this.props.query}
database={this.props.database}
actions={this.props.actions}
@ -246,6 +247,7 @@ export default class ResultSet extends React.PureComponent<
{t('Query in a new tab')}
</Button>
<ExploreCtasResultsButton
// @ts-ignore Redux types are difficult to work with, ignoring for now
table={tempTable}
schema={tempSchema}
dbId={exploreDBId}

View File

@ -29,7 +29,7 @@ import {
} from '../actions';
// To work properly the redux state must have a `messageToasts` subtree
export default function withToasts(BaseComponent: ComponentType) {
export default function withToasts(BaseComponent: ComponentType<any>) {
return connect(null, dispatch =>
bindActionCreators(
{
@ -41,6 +41,6 @@ export default function withToasts(BaseComponent: ComponentType) {
dispatch,
),
)(BaseComponent) as any;
// Rsedux has some confusing typings that cause problems for consumers of this function.
// Redux has some confusing typings that cause problems for consumers of this function.
// If someone can fix the types, great, but for now it's just any.
}

View File

@ -33,7 +33,7 @@ setupApp();
const profileViewContainer = document.getElementById('app');
const bootstrap = JSON.parse(
profileViewContainer.getAttribute('data-bootstrap'),
profileViewContainer?.getAttribute('data-bootstrap') ?? '{}',
);
const store = createStore(

View File

@ -17,7 +17,6 @@
* under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import { Col, Row, Tabs, Tab, Panel } from 'react-bootstrap';
import { t } from '@superset-ui/translation';
@ -26,17 +25,18 @@ import UserInfo from './UserInfo';
import Security from './Security';
import RecentActivity from './RecentActivity';
import CreatedContent from './CreatedContent';
import { User } from '../types';
const propTypes = {
user: PropTypes.object.isRequired,
};
interface AppProps {
user: User;
}
export default function App(props) {
export default function App({ user }: AppProps) {
return (
<div className="container app">
<Row>
<Col md={3}>
<UserInfo user={props.user} />
<UserInfo user={user} />
</Col>
<Col md={9}>
<Tabs id="options">
@ -50,7 +50,7 @@ export default function App(props) {
>
<Panel>
<Panel.Body>
<Favorites user={props.user} />
<Favorites user={user} />
</Panel.Body>
</Panel>
</Tab>
@ -64,7 +64,7 @@ export default function App(props) {
>
<Panel>
<Panel.Body>
<CreatedContent user={props.user} />
<CreatedContent user={user} />
</Panel.Body>
</Panel>
</Tab>
@ -78,7 +78,7 @@ export default function App(props) {
>
<Panel>
<Panel.Body>
<RecentActivity user={props.user} />
<RecentActivity user={user} />
</Panel.Body>
</Panel>
</Tab>
@ -92,7 +92,7 @@ export default function App(props) {
>
<Panel>
<Panel.Body>
<Security user={props.user} />
<Security user={user} />
</Panel.Body>
</Panel>
</Tab>
@ -102,4 +102,3 @@ export default function App(props) {
</div>
);
}
App.propTypes = propTypes;

View File

@ -17,28 +17,19 @@
* under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import moment from 'moment';
import { t } from '@superset-ui/translation';
import TableLoader from '../../components/TableLoader';
import { User, Dashboard, Slice } from '../types';
const propTypes = {
user: PropTypes.object.isRequired,
};
interface CreatedContentProps {
user: User;
}
class CreatedContent extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
dashboardsLoading: true,
slicesLoading: true,
dashboards: [],
slices: [],
};
}
class CreatedContent extends React.PureComponent<CreatedContentProps> {
renderSliceTable() {
const mutator = data =>
const mutator = (data: Slice[]) =>
data.map(slice => ({
slice: <a href={slice.url}>{slice.title}</a>,
favorited: moment.utc(slice.dttm).fromNow(),
@ -56,7 +47,7 @@ class CreatedContent extends React.PureComponent {
);
}
renderDashboardTable() {
const mutator = data =>
const mutator = (data: Dashboard[]) =>
data.map(dash => ({
dashboard: <a href={dash.url}>{dash.title}</a>,
favorited: moment.utc(dash.dttm).fromNow(),
@ -85,6 +76,5 @@ class CreatedContent extends React.PureComponent {
);
}
}
CreatedContent.propTypes = propTypes;
export default CreatedContent;

View File

@ -17,28 +17,19 @@
* under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import moment from 'moment';
import { t } from '@superset-ui/translation';
import TableLoader from '../../components/TableLoader';
import { User, Dashboard, Slice } from '../types';
const propTypes = {
user: PropTypes.object.isRequired,
};
interface FavoritesProps {
user: User;
}
export default class Favorites extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
dashboardsLoading: true,
slicesLoading: true,
dashboards: [],
slices: [],
};
}
export default class Favorites extends React.PureComponent<FavoritesProps> {
renderSliceTable() {
const mutator = data =>
const mutator = (data: Slice[]) =>
data.map(slice => ({
slice: <a href={slice.url}>{slice.title}</a>,
creator: <a href={slice.creator_url}>{slice.creator}</a>,
@ -57,7 +48,7 @@ export default class Favorites extends React.PureComponent {
);
}
renderDashboardTable() {
const mutator = data =>
const mutator = (data: Dashboard[]) =>
data.map(dash => ({
dashboard: <a href={dash.url}>{dash.title}</a>,
creator: <a href={dash.creator_url}>{dash.creator}</a>,
@ -86,4 +77,3 @@ export default class Favorites extends React.PureComponent {
);
}
}
Favorites.propTypes = propTypes;

View File

@ -17,39 +17,35 @@
* under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import moment from 'moment';
import TableLoader from '../../components/TableLoader';
import { User, Activity } from '../types';
const propTypes = {
user: PropTypes.object,
};
export default class RecentActivity extends React.PureComponent {
render() {
const rowLimit = 50;
const mutator = function (data) {
return data
.filter(row => row.action === 'dashboard' || row.action === 'explore')
.map(row => ({
name: <a href={row.item_url}>{row.item_title}</a>,
type: row.action,
time: moment.utc(row.time).fromNow(),
_time: row.time,
}));
};
return (
<div>
<TableLoader
className="table table-condensed"
mutator={mutator}
sortable
dataEndpoint={`/superset/recent_activity/${this.props.user.userId}/?limit=${rowLimit}`}
/>
</div>
);
}
interface RecentActivityProps {
user: User;
}
RecentActivity.propTypes = propTypes;
export default function RecentActivity({ user }: RecentActivityProps) {
const rowLimit = 50;
const mutator = function (data: Activity[]) {
return data
.filter(row => row.action === 'dashboard' || row.action === 'explore')
.map(row => ({
name: <a href={row.item_url}>{row.item_title}</a>,
type: row.action,
time: moment.utc(row.time).fromNow(),
_time: row.time,
}));
};
return (
<div>
<TableLoader
className="table table-condensed"
mutator={mutator}
sortable
dataEndpoint={`/superset/recent_activity/${user.userId}/?limit=${rowLimit}`}
/>
</div>
);
}

View File

@ -17,14 +17,15 @@
* under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import { Badge, Label } from 'react-bootstrap';
import { t } from '@superset-ui/translation';
import { User } from '../types';
const propTypes = {
user: PropTypes.object.isRequired,
};
export default function Security({ user }) {
interface SecurityProps {
user: User;
}
export default function Security({ user }: SecurityProps) {
return (
<div>
<div className="roles">
@ -66,4 +67,3 @@ export default function Security({ user }) {
</div>
);
}
Security.propTypes = propTypes;

View File

@ -1,75 +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 PropTypes from 'prop-types';
import Gravatar from 'react-gravatar';
import moment from 'moment';
import { Panel } from 'react-bootstrap';
import { t } from '@superset-ui/translation';
const propTypes = {
user: PropTypes.object.isRequired,
};
const UserInfo = ({ user }) => (
<div>
<a href="https://en.gravatar.com/">
<Gravatar
email={user.email}
width="100%"
height=""
size={220}
alt={t('Profile picture provided by Gravatar')}
className="img-rounded"
style={{ borderRadius: 15 }}
/>
</a>
<hr />
<Panel>
<Panel.Body>
<h3>
<strong>
{user.firstName} {user.lastName}
</strong>
</h3>
<h4 className="username">
<i className="fa fa-user-o" /> {user.username}
</h4>
<hr />
<p>
<i className="fa fa-clock-o" /> {t('joined')}{' '}
{moment(user.createdOn, 'YYYYMMDD').fromNow()}
</p>
<p className="email">
<i className="fa fa-envelope-o" /> {user.email}
</p>
<p className="roles">
<i className="fa fa-lock" /> {Object.keys(user.roles).join(', ')}
</p>
<p>
<i className="fa fa-key" />
&nbsp;
<span className="text-muted">{t('id:')}</span>&nbsp;
<span className="user-id">{user.userId}</span>
</p>
</Panel.Body>
</Panel>
</div>
);
UserInfo.propTypes = propTypes;
export default UserInfo;

View File

@ -0,0 +1,76 @@
/**
* 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 Gravatar from 'react-gravatar';
import moment from 'moment';
import { Panel } from 'react-bootstrap';
import { t } from '@superset-ui/translation';
import { User } from '../types';
interface UserInfoProps {
user: User;
}
export default function UserInfo({ user }: UserInfoProps) {
return (
<div>
<a href="https://en.gravatar.com/">
<Gravatar
email={user.email}
width="100%"
height=""
size={220}
alt={t('Profile picture provided by Gravatar')}
className="img-rounded"
style={{ borderRadius: 15 }}
/>
</a>
<hr />
<Panel>
<Panel.Body>
<h3>
<strong>
{user.firstName} {user.lastName}
</strong>
</h3>
<h4 className="username">
<i className="fa fa-user-o" /> {user.username}
</h4>
<hr />
<p>
<i className="fa fa-clock-o" /> {t('joined')}{' '}
{moment(user.createdOn, 'YYYYMMDD').fromNow()}
</p>
<p className="email">
<i className="fa fa-envelope-o" /> {user.email}
</p>
<p className="roles">
<i className="fa fa-lock" /> {Object.keys(user.roles).join(', ')}
</p>
<p>
<i className="fa fa-key" />
&nbsp;
<span className="text-muted">{t('id:')}</span>&nbsp;
<span className="user-id">{user.userId}</span>
</p>
</Panel.Body>
</Panel>
</div>
);
}

View File

@ -0,0 +1,58 @@
/**
* 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;
permissions: {
database_access?: string[];
datasource_access?: string[];
};
roles: Record<string, any>;
userId: number;
username: string;
};
export type Slice = {
dttm: number;
id: number;
url: string;
title: string;
creator?: string;
creator_url?: string;
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;
item_url: string;
time: number;
};

View File

@ -182,7 +182,7 @@ const config = {
dashboard: addPreamble('/src/dashboard/index.jsx'),
sqllab: addPreamble('/src/SqlLab/index.jsx'),
welcome: addPreamble('/src/welcome/index.jsx'),
profile: addPreamble('/src/profile/index.jsx'),
profile: addPreamble('/src/profile/index.tsx'),
showSavedQuery: [path.join(APP_DIR, '/src/showSavedQuery/index.jsx')],
},
output,