From be192b81917bd733d509c5b22a962c7dac180154 Mon Sep 17 00:00:00 2001 From: Marcus Andersson Date: Mon, 16 Mar 2020 09:53:20 +0100 Subject: [PATCH] Chore: migrate admin/users from angular to react + redux (#22759) * Start adding admin users list page to redux/react. * removed unused code. * added pagination. * changed so we use the new form styles. * added tooltip. * using tagbadge for authlabels. * remove unused code. * removed old code. * Fixed the last feedback on PR. --- .../src/components/Pagination/Pagination.mdx | 8 + .../Pagination/Pagination.story.tsx | 22 +++ .../src/components/Pagination/Pagination.tsx | 47 ++++++ packages/grafana-ui/src/components/index.ts | 1 + .../app/features/admin/AdminListUsersCtrl.ts | 75 --------- .../app/features/admin/UserListAdminPage.tsx | 152 ++++++++++++++++++ public/app/features/admin/index.ts | 2 - public/app/features/admin/partials/users.html | 80 --------- public/app/features/admin/state/actions.ts | 34 ++++ public/app/features/admin/state/reducers.ts | 50 ++++++ public/app/routes/routes.ts | 10 +- public/app/types/store.ts | 3 +- public/app/types/user.ts | 11 ++ 13 files changed, 333 insertions(+), 162 deletions(-) create mode 100644 packages/grafana-ui/src/components/Pagination/Pagination.mdx create mode 100644 packages/grafana-ui/src/components/Pagination/Pagination.story.tsx create mode 100644 packages/grafana-ui/src/components/Pagination/Pagination.tsx delete mode 100644 public/app/features/admin/AdminListUsersCtrl.ts create mode 100644 public/app/features/admin/UserListAdminPage.tsx delete mode 100644 public/app/features/admin/partials/users.html diff --git a/packages/grafana-ui/src/components/Pagination/Pagination.mdx b/packages/grafana-ui/src/components/Pagination/Pagination.mdx new file mode 100644 index 00000000000..726ca63a354 --- /dev/null +++ b/packages/grafana-ui/src/components/Pagination/Pagination.mdx @@ -0,0 +1,8 @@ +import { Meta, Story, Preview, Props } from '@storybook/addon-docs/blocks'; +import { Pagination } from './Pagination'; + +# Pagination + + + + \ No newline at end of file diff --git a/packages/grafana-ui/src/components/Pagination/Pagination.story.tsx b/packages/grafana-ui/src/components/Pagination/Pagination.story.tsx new file mode 100644 index 00000000000..b9d1f689be0 --- /dev/null +++ b/packages/grafana-ui/src/components/Pagination/Pagination.story.tsx @@ -0,0 +1,22 @@ +import React, { useState } from 'react'; +import { number } from '@storybook/addon-knobs'; +import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; +import { Pagination } from './Pagination'; +import mdx from './Pagination.mdx'; + +export const WithPages = () => { + const [page, setPage] = useState(1); + const numberOfPages = number('Number of pages', 5); + return ; +}; + +export default { + title: 'General/Pagination', + component: WithPages, + decorators: [withCenteredStory], + parameters: { + docs: { + page: mdx, + }, + }, +}; diff --git a/packages/grafana-ui/src/components/Pagination/Pagination.tsx b/packages/grafana-ui/src/components/Pagination/Pagination.tsx new file mode 100644 index 00000000000..50eb5106e63 --- /dev/null +++ b/packages/grafana-ui/src/components/Pagination/Pagination.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import { css } from 'emotion'; +import { stylesFactory } from '../../themes'; +import { Button, ButtonVariant } from '../Forms/Button'; + +interface Props { + currentPage: number; + numberOfPages: number; + onNavigate: (toPage: number) => void; +} + +export const Pagination: React.FC = ({ currentPage, numberOfPages, onNavigate }) => { + const styles = getStyles(); + const pages = [...new Array(numberOfPages).keys()]; + + return ( +
+
    + {pages.map(pageIndex => { + const page = pageIndex + 1; + const variant: ButtonVariant = page === currentPage ? 'primary' : 'secondary'; + + return ( +
  1. + +
  2. + ); + })} +
+
+ ); +}; + +const getStyles = stylesFactory(() => { + return { + container: css` + float: right; + `, + item: css` + display: inline-block; + padding-left: 10px; + margin-bottom: 5px; + `, + }; +}); diff --git a/packages/grafana-ui/src/components/index.ts b/packages/grafana-ui/src/components/index.ts index e069bd21ad9..2936b97e620 100644 --- a/packages/grafana-ui/src/components/index.ts +++ b/packages/grafana-ui/src/components/index.ts @@ -40,6 +40,7 @@ export { TimePicker } from './TimePicker/TimePicker'; export { TimeOfDayPicker } from './TimePicker/TimeOfDayPicker'; export { List } from './List/List'; export { TagsInput } from './TagsInput/TagsInput'; +export { Pagination } from './Pagination/Pagination'; export { ConfirmModal } from './ConfirmModal/ConfirmModal'; export { QueryField } from './QueryField/QueryField'; diff --git a/public/app/features/admin/AdminListUsersCtrl.ts b/public/app/features/admin/AdminListUsersCtrl.ts deleted file mode 100644 index 9dc750e2422..00000000000 --- a/public/app/features/admin/AdminListUsersCtrl.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { getTagColorsFromName } from '@grafana/ui'; -import { getBackendSrv } from '@grafana/runtime'; -import { NavModelSrv } from 'app/core/core'; -import { Scope } from 'app/types/angular'; -import { promiseToDigest } from 'app/core/utils/promiseToDigest'; - -export default class AdminListUsersCtrl { - users: any; - pages: any[] = []; - perPage = 50; - page = 1; - totalPages: number; - showPaging = false; - query: any; - navModel: any; - - /** @ngInject */ - constructor(private $scope: Scope, navModelSrv: NavModelSrv) { - this.navModel = navModelSrv.getNav('admin', 'global-users', 0); - this.query = ''; - this.getUsers(); - } - - getUsers() { - promiseToDigest(this.$scope)( - getBackendSrv() - .get(`/api/users/search?perpage=${this.perPage}&page=${this.page}&query=${this.query}`) - .then((result: any) => { - this.users = result.users; - this.page = result.page; - this.perPage = result.perPage; - this.totalPages = Math.ceil(result.totalCount / result.perPage); - this.showPaging = this.totalPages > 1; - this.pages = []; - - for (let i = 1; i < this.totalPages + 1; i++) { - this.pages.push({ page: i, current: i === this.page }); - } - - this.addUsersAuthLabels(); - }) - ); - } - - navigateToPage(page: any) { - this.page = page.page; - this.getUsers(); - } - - addUsersAuthLabels() { - for (const user of this.users) { - user.authLabel = getAuthLabel(user); - user.authLabelStyle = getAuthLabelStyle(user.authLabel); - } - } -} - -function getAuthLabel(user: any) { - if (user.authLabels && user.authLabels.length) { - return user.authLabels[0]; - } - return ''; -} - -function getAuthLabelStyle(label: string) { - if (label === 'LDAP' || !label) { - return {}; - } - - const { color, borderColor } = getTagColorsFromName(label); - return { - 'background-color': color, - 'border-color': borderColor, - }; -} diff --git a/public/app/features/admin/UserListAdminPage.tsx b/public/app/features/admin/UserListAdminPage.tsx new file mode 100644 index 00000000000..54821535f36 --- /dev/null +++ b/public/app/features/admin/UserListAdminPage.tsx @@ -0,0 +1,152 @@ +import React, { useEffect } from 'react'; +import { css, cx } from 'emotion'; +import { hot } from 'react-hot-loader'; +import { connect, MapDispatchToProps, MapStateToProps } from 'react-redux'; +import { NavModel } from '@grafana/data'; +import { Pagination, Forms, Tooltip, HorizontalGroup, stylesFactory } from '@grafana/ui'; +import { StoreState, UserDTO } from '../../types'; +import Page from 'app/core/components/Page/Page'; +import { getNavModel } from '../../core/selectors/navModel'; +import { fetchUsers, changeQuery, changePage } from './state/actions'; +import { TagBadge } from 'app/core/components/TagFilter/TagBadge'; + +interface OwnProps {} + +interface ConnectedProps { + navModel: NavModel; + users: UserDTO[]; + query: string; + showPaging: boolean; + totalPages: number; + page: number; +} + +interface DispatchProps { + fetchUsers: typeof fetchUsers; + changeQuery: typeof changeQuery; + changePage: typeof changePage; +} + +type Props = OwnProps & ConnectedProps & DispatchProps; + +const UserListAdminPageUnConnected: React.FC = props => { + const styles = getStyles(); + + useEffect(() => { + props.fetchUsers(); + }, []); + + return ( + + + <> +
+ + props.changeQuery(event.currentTarget.value)} + prefix={} + /> + + New user + + +
+ +
+ + + + + + + + + + + + {props.users.map(renderUser)} +
LoginEmail + Seen  + + + +
+
+ {props.showPaging && ( + + )} + +
+
+ ); +}; + +const renderUser = (user: UserDTO) => { + const editUrl = `admin/users/edit/${user.id}`; + + return ( + + + + + + + + {user.login} + + + {user.email} + + {user.lastSeenAtAge && {user.lastSeenAtAge}} + + {user.isAdmin && ( + + + + + + )} + + + {Array.isArray(user.authLabels) && user.authLabels.length > 0 && ( + + )} + + + {user.isDisabled && Disabled} + + + ); +}; + +const getStyles = stylesFactory(() => { + return { + table: css` + margin-top: 28px; + `, + }; +}); + +const mapDispatchToProps: MapDispatchToProps = { + fetchUsers, + changeQuery, + changePage, +}; + +const mapStateToProps: MapStateToProps = state => ({ + navModel: getNavModel(state.navIndex, 'global-users'), + users: state.userListAdmin.users, + query: state.userListAdmin.query, + showPaging: state.userListAdmin.showPaging, + totalPages: state.userListAdmin.totalPages, + page: state.userListAdmin.page, +}); + +export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(UserListAdminPageUnConnected)); diff --git a/public/app/features/admin/index.ts b/public/app/features/admin/index.ts index b9b60229b50..c43f16fb3fa 100644 --- a/public/app/features/admin/index.ts +++ b/public/app/features/admin/index.ts @@ -1,4 +1,3 @@ -import AdminListUsersCtrl from './AdminListUsersCtrl'; import AdminEditUserCtrl from './AdminEditUserCtrl'; import AdminListOrgsCtrl from './AdminListOrgsCtrl'; import AdminEditOrgCtrl from './AdminEditOrgCtrl'; @@ -15,7 +14,6 @@ class AdminHomeCtrl { } } -coreModule.controller('AdminListUsersCtrl', AdminListUsersCtrl); coreModule.controller('AdminEditUserCtrl', AdminEditUserCtrl); coreModule.controller('AdminListOrgsCtrl', AdminListOrgsCtrl); coreModule.controller('AdminEditOrgCtrl', AdminEditOrgCtrl); diff --git a/public/app/features/admin/partials/users.html b/public/app/features/admin/partials/users.html deleted file mode 100644 index 7463de8bbf3..00000000000 --- a/public/app/features/admin/partials/users.html +++ /dev/null @@ -1,80 +0,0 @@ - - -
-
- -
- - New user - -
- -
- - - - - - - - - - - - - - - - - - - - - - - -
LoginEmail - Seen - Time since user was seen using Grafana -
- - {{user.authLabel}} - - - Disabled -
-
- -
-
    -
  1. - -
  2. -
-
-
- -