diff --git a/openmetadata-ui/src/main/resources/ui/src/axiosAPIs/userAPI.ts b/openmetadata-ui/src/main/resources/ui/src/axiosAPIs/userAPI.ts index ee94b5c4fef..9d6f522ab04 100644 --- a/openmetadata-ui/src/main/resources/ui/src/axiosAPIs/userAPI.ts +++ b/openmetadata-ui/src/main/resources/ui/src/axiosAPIs/userAPI.ts @@ -15,6 +15,7 @@ import { AxiosResponse } from 'axios'; import { Operation } from 'fast-json-patch'; import { UserProfile } from 'Models'; import { SearchIndex } from '../enums/search.enum'; +import { CreateUser } from '../generated/api/teams/createUser'; import { User } from '../generated/entity/teams/user'; import { getURLWithQueryFields } from '../utils/APIUtils'; import APIClient from './index'; @@ -88,9 +89,9 @@ export const getUserById: Function = (id: string): Promise => { return APIClient.get(`/users/${id}`); }; -export const createUser = (userDetails: { - [name: string]: string | Array | UserProfile; -}): Promise => { +export const createUser = ( + userDetails: Record | UserProfile> | CreateUser +): Promise => { return APIClient.post(`/users`, userDetails); }; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/CreateUser/CreateUser.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/CreateUser/CreateUser.component.tsx new file mode 100644 index 00000000000..201e95dc7bf --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/CreateUser/CreateUser.component.tsx @@ -0,0 +1,325 @@ +/* + * Copyright 2021 Collate + * Licensed 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 { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import classNames from 'classnames'; +import { cloneDeep, isEmpty, isUndefined } from 'lodash'; +import { EditorContentRef } from 'Models'; +import React, { useRef, useState } from 'react'; +import { validEmailRegEx } from '../../constants/regex.constants'; +import { CreateUser as CreateUserSchema } from '../../generated/api/teams/createUser'; +import { Role } from '../../generated/entity/teams/role'; +import { EntityReference as UserTeams } from '../../generated/entity/teams/user'; +import jsonData from '../../jsons/en'; +import { errorMsg, requiredField } from '../../utils/CommonUtils'; +import { Button } from '../buttons/Button/Button'; +import MarkdownWithPreview from '../common/editor/MarkdownWithPreview'; +import PageLayout from '../containers/PageLayout'; +import DropDown from '../dropdown/DropDown'; +import { DropDownListItem } from '../dropdown/types'; +import { Field } from '../Field/Field'; +import Loader from '../Loader/Loader'; +import { CreateUserProps } from './CreateUser.interface'; + +const CreateUser = ({ + allowAccess, + roles, + teams, + saveState = 'initial', + onCancel, + onSave, +}: CreateUserProps) => { + const markdownRef = useRef(); + const [description] = useState(''); + const [email, setEmail] = useState(''); + const [isAdmin, setIsAdmin] = useState(false); + const [isBot, setIsBot] = useState(false); + const [selectedRoles, setSelectedRoles] = useState>( + [] + ); + const [selectedTeams, setSelectedTeams] = useState>( + [] + ); + const [showErrorMsg, setShowErrorMsg] = useState({ + email: false, + validEmail: false, + }); + + /** + * common function to update user input in to the state + * @param event change event for input/selection field + * @returns if user dont have access to the page it will not update data. + */ + const handleValidation = ( + event: React.ChangeEvent + ) => { + if (!allowAccess) { + return; + } + + const value = event.target.value; + const eleName = event.target.name; + + switch (eleName) { + case 'email': + setEmail(value); + setShowErrorMsg({ ...showErrorMsg, email: false }); + + break; + + default: + break; + } + }; + + /** + * Generate DropdownListItem + * @param data Array containing object which must have name and id + * @returns DropdownListItem[] + */ + const getDropdownOptions = ( + data: Array | Array + ): DropDownListItem[] => { + return [ + ...data.map((option) => { + return { + name: option.displayName || '', + value: option.id, + }; + }), + ]; + }; + + /** + * Dropdown option selector + * @param id of selected option from dropdown + */ + const selectedRolesHandler = (id?: string) => { + setSelectedRoles((prevState: Array) => { + if (prevState.includes(id as string)) { + const selectedRole = [...prevState]; + const index = selectedRole.indexOf(id as string); + selectedRole.splice(index, 1); + + return selectedRole; + } else { + return [...prevState, id]; + } + }); + }; + + /** + * Dropdown option selector. + * @param id of selected option from dropdown. + */ + const selectedTeamsHandler = (id?: string) => { + setSelectedTeams((prevState: Array) => { + if (prevState.includes(id as string)) { + const selectedRole = [...prevState]; + const index = selectedRole.indexOf(id as string); + selectedRole.splice(index, 1); + + return selectedRole; + } else { + return [...prevState, id]; + } + }); + }; + + /** + * Validate if required value is provided or not. + * @returns boolean + */ + const validateForm = () => { + const errMsg = cloneDeep(showErrorMsg); + if (isEmpty(email)) { + errMsg.email = true; + } else { + errMsg.validEmail = !validEmailRegEx.test(email); + } + setShowErrorMsg(errMsg); + + return !Object.values(errMsg).includes(true); + }; + + /** + * Form submit handler + */ + const handleSave = () => { + const validRole = selectedRoles.filter( + (id) => !isUndefined(id) + ) as string[]; + const validTeam = selectedTeams.filter( + (id) => !isUndefined(id) + ) as string[]; + if (validateForm()) { + const userProfile: CreateUserSchema = { + description: markdownRef.current?.getEditorContent() || undefined, + name: email.split('@')[0], + roles: validRole.length ? validRole : undefined, + teams: validTeam.length ? validTeam : undefined, + email: email, + isAdmin: isAdmin, + isBot: isBot, + }; + onSave(userProfile); + } + }; + + /** + * Dynamic button provided as per its state, useful for micro interaction + * @returns Button + */ + const getSaveButton = () => { + return allowAccess ? ( + <> + {saveState === 'waiting' ? ( + + ) : saveState === 'success' ? ( + + ) : ( + + )} + + ) : null; + }; + + return ( + +
Create User
+ + + + + {showErrorMsg.email + ? errorMsg(jsonData['form-error-messages']['empty-email']) + : showErrorMsg.validEmail + ? errorMsg(jsonData['form-error-messages']['invalid-email']) + : null} + + + + + + + + } + type="checkbox" + onSelect={(_e, value) => selectedTeamsHandler(value)} + /> + + + + } + type="checkbox" + onSelect={(_e, value) => selectedRolesHandler(value)} + /> + + + +
+ +
{ + if (allowAccess) { + setIsAdmin((prev) => !prev); + setIsBot(false); + } + }}> +
+
+
+
+ +
{ + if (allowAccess) { + setIsBot((prev) => !prev); + setIsAdmin(false); + } + }}> +
+
+
+ + + + {getSaveButton()} + + + ); +}; + +export default CreateUser; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/CreateUser/CreateUser.interface.ts b/openmetadata-ui/src/main/resources/ui/src/components/CreateUser/CreateUser.interface.ts new file mode 100644 index 00000000000..3d197b71a6f --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/CreateUser/CreateUser.interface.ts @@ -0,0 +1,26 @@ +/* + * Copyright 2021 Collate + * Licensed 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 { LoadingState } from 'Models'; +import { CreateUser } from '../../generated/api/teams/createUser'; +import { Role } from '../../generated/entity/teams/role'; +import { EntityReference as UserTeams } from '../../generated/entity/teams/user'; + +export interface CreateUserProps { + allowAccess: boolean; + saveState?: LoadingState; + roles: Array; + teams: Array; + onSave: (data: CreateUser) => void; + onCancel: () => void; +} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/CreateUser/CreateUser.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/CreateUser/CreateUser.test.tsx new file mode 100644 index 00000000000..4be5b02b997 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/CreateUser/CreateUser.test.tsx @@ -0,0 +1,76 @@ +/* + * Copyright 2021 Collate + * Licensed 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 { + findAllByText, + findByTestId, + findByText, + render, +} from '@testing-library/react'; +import React from 'react'; +import { MemoryRouter } from 'react-router-dom'; +import CreateUser from './CreateUser.component'; +import { CreateUserProps } from './CreateUser.interface'; + +jest.mock( + '../containers/PageLayout', + () => + ({ children }: { children: React.ReactNode }) => +
{children}
+); + +jest.mock('../dropdown/DropDown', () => { + return jest.fn().mockReturnValue(

Dropdown component

); +}); + +jest.mock('../common/editor/MarkdownWithPreview', () => { + return jest.fn().mockReturnValue(

MarkdownWithPreview component

); +}); + +const propsValue: CreateUserProps = { + allowAccess: true, + saveState: 'initial', + roles: [], + teams: [], + onSave: jest.fn(), + onCancel: jest.fn(), +}; + +describe('Test CreateUser component', () => { + it('CreateUser component should render properly', async () => { + const { container } = render(, { + wrapper: MemoryRouter, + }); + + const PageLayout = await findByTestId(container, 'PageLayout'); + const email = await findByTestId(container, 'email'); + const admin = await findByTestId(container, 'admin'); + const bot = await findByTestId(container, 'bot'); + const cancelButton = await findByTestId(container, 'cancel-user'); + const saveButton = await findByTestId(container, 'save-user'); + const description = await findByText( + container, + /MarkdownWithPreview component/i + ); + const dropdown = await findAllByText(container, /Dropdown component/i); + + expect(PageLayout).toBeInTheDocument(); + expect(email).toBeInTheDocument(); + expect(bot).toBeInTheDocument(); + expect(admin).toBeInTheDocument(); + expect(description).toBeInTheDocument(); + expect(dropdown.length).toBe(2); + expect(cancelButton).toBeInTheDocument(); + expect(saveButton).toBeInTheDocument(); + }); +}); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/UserList/UserList.tsx b/openmetadata-ui/src/main/resources/ui/src/components/UserList/UserList.tsx index 24a10a9c745..e422298e11e 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/UserList/UserList.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/UserList/UserList.tsx @@ -11,17 +11,21 @@ * limitations under the License. */ +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { compare, Operation } from 'fast-json-patch'; import { isUndefined, toLower } from 'lodash'; -import React, { FunctionComponent, useEffect, useState } from 'react'; +import React, { Fragment, FunctionComponent, useEffect, useState } from 'react'; import PageLayout from '../../components/containers/PageLayout'; import Loader from '../../components/Loader/Loader'; +import { TITLE_FOR_NON_ADMIN_ACTION } from '../../constants/constants'; import { UserType } from '../../enums/user.enum'; import { Role } from '../../generated/entity/teams/role'; import { Team } from '../../generated/entity/teams/team'; import { User } from '../../generated/entity/teams/user'; import { getCountBadge } from '../../utils/CommonUtils'; +import { Button } from '../buttons/Button/Button'; import ErrorPlaceHolder from '../common/error-with-placeholder/ErrorPlaceHolder'; +import NonAdminAction from '../common/non-admin-action/NonAdminAction'; import Searchbar from '../common/searchbar/Searchbar'; import UserDetailsModal from '../Modals/UserDetailsModal/UserDetailsModal'; import UserDataCard from '../UserDataCard/UserDataCard'; @@ -31,6 +35,7 @@ interface Props { roles: Array; allUsers: Array; updateUser: (id: string, data: Operation[], updatedUser: User) => void; + handleAddUserClick: () => void; isLoading: boolean; } @@ -38,6 +43,7 @@ const UserList: FunctionComponent = ({ allUsers = [], isLoading, updateUser, + handleAddUserClick, teams = [], roles = [], }: Props) => { @@ -231,47 +237,64 @@ const UserList: FunctionComponent = ({ const getLeftPanel = () => { return ( -
-
{ - selectTeam(); - }}> -
-

- All Users -

-
- {getCountBadge(allUsers.length || 0, '', isTeamBadgeActive())} + +
+
Users
+ + +
- {teams && - teams.map((team: Team) => ( + +
+
{ + selectTeam(); + }}>
{ - selectTeam(team); - setSearchText(''); - }}> -
-

- {team.displayName} -

-
- {getCountBadge( - team.users?.length || 0, - '', - isTeamBadgeActive(team.name) - )} + className={`tw-group tw-text-grey-body tw-text-body tw-flex tw-justify-between ${getCurrentTeamClass()}`}> +

+ All Users +

- ))} -
+ {getCountBadge(allUsers.length || 0, '', isTeamBadgeActive())} +
+ {teams && + teams.map((team: Team) => ( +
{ + selectTeam(team); + setSearchText(''); + }}> +
+

+ {team.displayName} +

+
+ {getCountBadge( + team.users?.length || 0, + '', + isTeamBadgeActive(team.name) + )} +
+ ))} +
+ ); }; @@ -308,9 +331,11 @@ const UserList: FunctionComponent = ({ isActiveUser: !user.deleted, profilePhoto: user.profile?.images?.image || '', teamCount: - user.teams - ?.map((team) => team.displayName ?? team.name) - ?.join(', ') ?? '', + user.teams && user.teams?.length + ? user.teams + ?.map((team) => team.displayName ?? team.name) + ?.join(', ') + : 'No teams', }; return ( diff --git a/openmetadata-ui/src/main/resources/ui/src/constants/constants.ts b/openmetadata-ui/src/main/resources/ui/src/constants/constants.ts index 00cabb3bc00..3b8d57a5fb2 100644 --- a/openmetadata-ui/src/main/resources/ui/src/constants/constants.ts +++ b/openmetadata-ui/src/main/resources/ui/src/constants/constants.ts @@ -157,6 +157,7 @@ export const ROUTES = { PIPELINE_DETAILS: `/pipeline/${PLACEHOLDER_ROUTE_PIPELINE_FQN}`, PIPELINE_DETAILS_WITH_TAB: `/pipeline/${PLACEHOLDER_ROUTE_PIPELINE_FQN}/${PLACEHOLDER_ROUTE_TAB}`, USER_LIST: '/user-list', + CREATE_USER: '/create-user', USER_PROFILE: `/users/${PLACEHOLDER_USER_NAME}`, ROLES: '/roles', WEBHOOKS: '/webhooks', diff --git a/openmetadata-ui/src/main/resources/ui/src/constants/regex.constants.ts b/openmetadata-ui/src/main/resources/ui/src/constants/regex.constants.ts index 64ea401ff37..3420e3fe888 100644 --- a/openmetadata-ui/src/main/resources/ui/src/constants/regex.constants.ts +++ b/openmetadata-ui/src/main/resources/ui/src/constants/regex.constants.ts @@ -12,3 +12,4 @@ */ export const UrlEntityCharRegEx = /[#.%;?/\\]/g; +export const validEmailRegEx = /^\S+@\S+\.\S+$/; diff --git a/openmetadata-ui/src/main/resources/ui/src/jsons/en.ts b/openmetadata-ui/src/main/resources/ui/src/jsons/en.ts index b54cd5083a0..873fe9f4359 100644 --- a/openmetadata-ui/src/main/resources/ui/src/jsons/en.ts +++ b/openmetadata-ui/src/main/resources/ui/src/jsons/en.ts @@ -15,6 +15,7 @@ const jsonData = { 'api-error-messages': { 'add-glossary-error': 'Error while adding glossary!', 'add-glossary-term-error': 'Error while adding glossary term!', + 'create-user-error': 'Error while creating user!', 'delete-glossary-error': 'Error while deleting glossary!', 'delete-glossary-term-error': 'Error while deleting glossary term!', 'delete-team-error': 'Error while deleting team!', @@ -27,6 +28,10 @@ const jsonData = { 'update-glossary-term-error': 'Error while updating glossary term!', 'update-description-error': 'Error while updating description!', }, + 'form-error-messages': { + 'empty-email': 'Email is required.', + 'invalid-email': 'Email is invalid.', + }, }; export default jsonData; diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/CreateUserPage/CreateUserPage.component.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/CreateUserPage/CreateUserPage.component.tsx new file mode 100644 index 00000000000..faecc46f687 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/pages/CreateUserPage/CreateUserPage.component.tsx @@ -0,0 +1,116 @@ +/* + * Copyright 2021 Collate + * Licensed 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 { AxiosError } from 'axios'; +import { observer } from 'mobx-react'; +import { LoadingState } from 'Models'; +import React, { useEffect, useState } from 'react'; +import { useHistory } from 'react-router-dom'; +import AppState from '../../AppState'; +import { useAuthContext } from '../../auth-provider/AuthProvider'; +import { createUser } from '../../axiosAPIs/userAPI'; +import PageContainerV1 from '../../components/containers/PageContainerV1'; +import CreateUserComponent from '../../components/CreateUser/CreateUser.component'; +import { ROUTES } from '../../constants/constants'; +import { CreateUser } from '../../generated/api/teams/createUser'; +import { Role } from '../../generated/entity/teams/role'; +import { EntityReference as UserTeams } from '../../generated/entity/teams/user'; +import { useAuth } from '../../hooks/authHooks'; +import useToastContext from '../../hooks/useToastContext'; +import jsonData from '../../jsons/en'; + +const CreateUserPage = () => { + const { isAdminUser } = useAuth(); + const { isAuthDisabled } = useAuthContext(); + const showToast = useToastContext(); + const history = useHistory(); + + const [roles, setRoles] = useState>([]); + const [teams, setTeams] = useState>([]); + const [status, setStatus] = useState('initial'); + + const goToUserListPage = () => { + history.push(ROUTES.USER_LIST); + }; + + const handleCancel = () => { + goToUserListPage(); + }; + + /** + * Creates toast notification for error. + * @param errMessage Error message + */ + const handleShowErrorToast = (errMessage: string) => { + showToast({ + variant: 'error', + body: errMessage, + }); + }; + + /** + * Handles error if any, while creating new user. + * @param errorMessage Error message + */ + const handleSaveFailure = (errorMessage = '') => { + handleShowErrorToast( + errorMessage || jsonData['api-error-messages']['create-user-error'] + ); + setStatus('initial'); + }; + + /** + * Submit handler for new user form. + * @param userData Data for creating new user + */ + const handleAddUserSave = (userData: CreateUser) => { + setStatus('waiting'); + createUser(userData) + .then((res) => { + if (res.data) { + setStatus('success'); + setTimeout(() => { + setStatus('initial'); + goToUserListPage(); + }, 500); + } else { + handleSaveFailure(); + } + }) + .catch((err: AxiosError) => { + handleSaveFailure(err.response?.data?.message); + }); + }; + + useEffect(() => { + setRoles(AppState.userRoles); + }, [AppState.userRoles]); + useEffect(() => { + setTeams(AppState.userTeams); + }, [AppState.userTeams]); + + return ( + + + + ); +}; + +export default observer(CreateUserPage); diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/CreateUserPage/CreateUserPage.test.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/CreateUserPage/CreateUserPage.test.tsx new file mode 100644 index 00000000000..b898128dc00 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/pages/CreateUserPage/CreateUserPage.test.tsx @@ -0,0 +1,61 @@ +/* + * Copyright 2021 Collate + * Licensed 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 { findByTestId, findByText, render } from '@testing-library/react'; +import React, { ReactNode } from 'react'; +import { MemoryRouter } from 'react-router-dom'; +import AddUserPageComponent from './CreateUserPage.component'; + +jest.mock('../../components/containers/PageContainerV1', () => { + return jest + .fn() + .mockImplementation(({ children }: { children: ReactNode }) => ( +
{children}
+ )); +}); + +jest.mock('../../auth-provider/AuthProvider', () => ({ + useAuthContext: jest.fn().mockReturnValue({ isAuthDisabled: true }), +})); + +jest.mock('../../hooks/authHooks', () => ({ + useAuth: jest.fn().mockReturnValue({ isAdminUser: true }), +})); + +jest.mock('../../components/CreateUser/CreateUser.component', () => { + return jest.fn().mockReturnValue(
CreateUser component
); +}); + +jest.mock('../../AppState', () => + jest.fn().mockReturnValue({ + userRoles: [], + userTeams: [], + }) +); + +describe('Test AddUserPage component', () => { + it('AddUserPage component should render properly', async () => { + const { container } = render(, { + wrapper: MemoryRouter, + }); + + const pageContainerV1 = await findByTestId(container, 'PageContainerV1'); + const createUserComponent = await findByText( + container, + /CreateUser component/i + ); + + expect(pageContainerV1).toBeInTheDocument(); + expect(createUserComponent).toBeInTheDocument(); + }); +}); diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/UserListPage/UserListPage.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/UserListPage/UserListPage.tsx index 0e8b6707178..fd6c6d2d70b 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/UserListPage/UserListPage.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/UserListPage/UserListPage.tsx @@ -15,11 +15,13 @@ import { AxiosError, AxiosResponse } from 'axios'; import { Operation } from 'fast-json-patch'; import { observer } from 'mobx-react'; import React, { useEffect, useState } from 'react'; +import { useHistory } from 'react-router-dom'; import AppState from '../../AppState'; import { getTeams } from '../../axiosAPIs/teamsAPI'; import { updateUserDetail } from '../../axiosAPIs/userAPI'; import PageContainerV1 from '../../components/containers/PageContainerV1'; import UserList from '../../components/UserList/UserList'; +import { ROUTES } from '../../constants/constants'; import { Role } from '../../generated/entity/teams/role'; import { Team } from '../../generated/entity/teams/team'; import { User } from '../../generated/entity/teams/user'; @@ -27,6 +29,7 @@ import useToastContext from '../../hooks/useToastContext'; const UserListPage = () => { const showToast = useToastContext(); + const history = useHistory(); const [teams, setTeams] = useState>([]); const [roles, setRoles] = useState>([]); @@ -50,6 +53,13 @@ const UserListPage = () => { }); }; + /** + * Redirect user to add-user route for adding new user. + */ + const handleAddUserClick = () => { + history.push(ROUTES.CREATE_USER); + }; + const updateUser = (id: string, data: Operation[], updatedUser: User) => { setIsLoading(true); updateUserDetail(id, data) @@ -84,6 +94,7 @@ const UserListPage = () => { { /> + ) : null}