From b153b797236937c0319f99b2e474773ed73cb94e Mon Sep 17 00:00:00 2001 From: Sriharsha Chintalapani Date: Thu, 16 Mar 2023 02:19:05 -0700 Subject: [PATCH] Add users.isBot flag to index and add defaults to the user spec (#10558) * Add users.isBot flag to index and add defaults to the user spec * Added bot flag in user search api * change as per comments * fix cypress * revert user.json changes --------- Co-authored-by: Ashish Gupta --- .../service/elasticsearch/UserIndex.java | 4 + .../elasticsearch/en/user_index_mapping.json | 5 +- .../ui/cypress/e2e/Pages/Users.spec.js | 21 +++ .../components/UserList/UserListV1.test.tsx | 165 ++++++++++++++++++ .../ui/src/components/UserList/UserListV1.tsx | 6 +- .../UserListPage/UserListPageV1.test.tsx | 49 +++++- .../src/pages/UserListPage/UserListPageV1.tsx | 4 +- 7 files changed, 249 insertions(+), 5 deletions(-) create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/UserList/UserListV1.test.tsx diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/elasticsearch/UserIndex.java b/openmetadata-service/src/main/java/org/openmetadata/service/elasticsearch/UserIndex.java index 35debc74767..cc4f8f3a333 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/elasticsearch/UserIndex.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/elasticsearch/UserIndex.java @@ -19,11 +19,15 @@ public class UserIndex implements ElasticSearchIndex { if (user.getDisplayName() == null) { user.setDisplayName(user.getName()); } + if (user.getIsBot() == null) { + user.setIsBot(false); + } Map doc = JsonUtils.getMap(user); ElasticSearchIndexUtils.removeNonIndexableFields(doc, excludeFields); List suggest = new ArrayList<>(); suggest.add(ElasticSearchSuggest.builder().input(user.getName()).weight(5).build()); suggest.add(ElasticSearchSuggest.builder().input(user.getDisplayName()).weight(10).build()); + doc.put("suggest", suggest); doc.put("entityType", Entity.USER); return doc; diff --git a/openmetadata-service/src/main/resources/elasticsearch/en/user_index_mapping.json b/openmetadata-service/src/main/resources/elasticsearch/en/user_index_mapping.json index c6d156fb4b9..773cc973a51 100644 --- a/openmetadata-service/src/main/resources/elasticsearch/en/user_index_mapping.json +++ b/openmetadata-service/src/main/resources/elasticsearch/en/user_index_mapping.json @@ -42,7 +42,10 @@ "type": "text" }, "isAdmin": { - "type": "text" + "type": "boolean" + }, + "isBot": { + "type": "boolean" }, "teams": { "properties": { diff --git a/openmetadata-ui/src/main/resources/ui/cypress/e2e/Pages/Users.spec.js b/openmetadata-ui/src/main/resources/ui/cypress/e2e/Pages/Users.spec.js index ee2b9b43d9a..d9e683dac5e 100644 --- a/openmetadata-ui/src/main/resources/ui/cypress/e2e/Pages/Users.spec.js +++ b/openmetadata-ui/src/main/resources/ui/cypress/e2e/Pages/Users.spec.js @@ -27,6 +27,8 @@ const userEmail = `${userName}@gmail.com`; const adminName = `Admincttest${uuid()}`; const adminEmail = `${adminName}@gmail.com`; +const searchBotText = 'bot'; + describe('Users flow should work properly', () => { beforeEach(() => { cy.login(); @@ -75,6 +77,25 @@ describe('Users flow should work properly', () => { softDeleteUser(userName); deleteSoftDeletedUser(userName); }); + + it('Search for bot user', () => { + interceptURL( + 'GET', + `/api/v1/search/query?q=*${searchBotText}***isBot:false&from=0&size=15&index=user_search_index`, + 'searchUser' + ); + cy.get('[data-testid="searchbar"]') + .should('exist') + .should('be.visible') + .type(searchBotText); + + verifyResponseStatusCode('@searchUser', 200); + + cy.get('.ant-table-placeholder > .ant-table-cell').should( + 'not.contain', + searchBotText + ); + }); }); describe('Admin flow should work properly', () => { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/UserList/UserListV1.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/UserList/UserListV1.test.tsx new file mode 100644 index 00000000000..c35ca40d5af --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/UserList/UserListV1.test.tsx @@ -0,0 +1,165 @@ +/* + * Copyright 2023 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 { cleanup, render, screen } from '@testing-library/react'; +import { MOCK_USER_DATA } from 'pages/UserListPage/mockUserData'; +import React from 'react'; +import { MemoryRouter } from 'react-router-dom'; +import { act } from 'react-test-renderer'; +import UserListV1 from './UserListV1'; + +jest.mock('rest/userAPI', () => ({ + updateUser: jest.fn().mockImplementation(() => Promise.resolve()), +})); + +jest.mock('../../generated/api/teams/createUser', () => ({ + CreateUser: jest.fn().mockImplementation(() => Promise.resolve()), +})); + +jest.mock('../../utils/ToastUtils', () => ({ + showErrorToast: jest.fn(), + showSuccessToast: jest.fn(), +})); + +jest.mock('../common/DeleteWidget/DeleteWidgetModal', () => { + return jest.fn().mockImplementation(() =>
DeleteWidgetModal
); +}); + +jest.mock('../common/error-with-placeholder/ErrorPlaceHolder', () => { + return jest.fn().mockImplementation(() =>
ErrorPlaceHolder
); +}); + +jest.mock('../common/next-previous/NextPrevious', () => { + return jest.fn().mockImplementation(() =>
NextPrevious
); +}); + +jest.mock('../header/PageHeader.component', () => { + return jest.fn().mockImplementation(() =>
PageHeader
); +}); + +jest.mock('../Loader/Loader', () => { + return jest.fn().mockImplementation(() =>
Loader
); +}); + +jest.mock('../common/searchbar/Searchbar', () => { + return jest + .fn() + .mockImplementation((prop) => ( + prop.onSearch(e.target.value)} + /> + )); +}); + +const mockFunction = jest.fn(); + +const MOCK_PROPS_DATA = { + data: MOCK_USER_DATA.data, + paging: MOCK_USER_DATA.paging, + searchTerm: '', + currentPage: 1, + isDataLoading: false, + showDeletedUser: false, + onSearch: mockFunction, + onShowDeletedUserChange: mockFunction, + onPagingChange: mockFunction, + afterDeleteAction: mockFunction, + isAdminPage: false, +}; + +describe('Test UserListV1 component', () => { + beforeEach(() => { + cleanup(); + }); + + it('Should render component', async () => { + await act(async () => { + render(, { + wrapper: MemoryRouter, + }); + }); + + const userListComponent = await screen.findByTestId( + 'user-list-v1-component' + ); + const pageHeader = await screen.findByText('PageHeader'); + + expect(userListComponent).toBeInTheDocument(); + expect(pageHeader).toBeInTheDocument(); + }); + + it('Should render ErrorPlaceHolder', async () => { + await act(async () => { + render(, { + wrapper: MemoryRouter, + }); + }); + + const emptyComponent = await screen.findByText('ErrorPlaceHolder'); + + expect(emptyComponent).toBeInTheDocument(); + }); + + it('Should render Users table', async () => { + await act(async () => { + render(, { + wrapper: MemoryRouter, + }); + }); + + const userListComponent = await screen.findByTestId( + 'user-list-v1-component' + ); + + expect(userListComponent).toBeInTheDocument(); + + const table = await screen.findByTestId('user-list-table'); + + expect(table).toBeInTheDocument(); + + const userName = await screen.findByText('label.username'); + const teams = await screen.findByText('label.team-plural'); + const role = await screen.findByText('label.role-plural'); + + expect(userName).toBeInTheDocument(); + expect(teams).toBeInTheDocument(); + expect(role).toBeInTheDocument(); + + const rows = await screen.findAllByRole('row'); + + expect(rows).toHaveLength(MOCK_PROPS_DATA.data.length + 1); + }); + + it('Should not render data when bot is search', async () => { + await act(async () => { + render(, { + wrapper: MemoryRouter, + }); + }); + + const userListComponent = await screen.findByTestId( + 'user-list-v1-component' + ); + + expect(userListComponent).toBeInTheDocument(); + + const table = await screen.findByTestId('user-list-table'); + + const noDataTable = await screen.findByText('No data'); + + expect(table).toBeInTheDocument(); + expect(noDataTable).toBeInTheDocument(); + }); +}); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/UserList/UserListV1.tsx b/openmetadata-ui/src/main/resources/ui/src/components/UserList/UserListV1.tsx index 19392796694..8fe921ddbc0 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/UserList/UserListV1.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/UserList/UserListV1.tsx @@ -223,7 +223,10 @@ const UserListV1: FC = ({ } return ( - + = ({ bordered className="user-list-table" columns={columns} + data-testid="user-list-table" dataSource={data} loading={{ spinning: isDataLoading, diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/UserListPage/UserListPageV1.test.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/UserListPage/UserListPageV1.test.tsx index dfce6d9003f..59db8cc1a6d 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/UserListPage/UserListPageV1.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/UserListPage/UserListPageV1.test.tsx @@ -138,7 +138,6 @@ describe('Test UserListPage component', () => { }); it('handleSearch function should work properly', async () => { - mockParam.tab = GlobalSettingOptions.ADMINS; const userAPI = getUsers as jest.Mock; const searchAPI = searchData as jest.Mock; render(); @@ -151,6 +150,17 @@ describe('Test UserListPage component', () => { fireEvent.change(searchBox, { target: { value: 'test' } }); }); + expect(searchAPI.mock.calls[0]).toEqual([ + 'test', + 1, + 15, + 'isBot:false', + '', + '', + 'user_search_index', + false, + ]); + await waitForElement(async () => { const userSearchTerm = new URLSearchParams(window.location.search).get( 'user' @@ -173,6 +183,43 @@ describe('Test UserListPage component', () => { expect(userlist).toBeInTheDocument(); }); + it('handleSearch function should work properly for Admin', async () => { + mockParam.tab = GlobalSettingOptions.ADMINS; + const searchAPI = searchData as jest.Mock; + + render(); + + const searchBox = await screen.findByTestId('search-input'); + + expect(searchBox).toBeInTheDocument(); + + await act(async () => { + fireEvent.change(searchBox, { target: { value: 'test' } }); + }); + + expect(searchAPI.mock.calls[0]).toEqual([ + 'test', + 1, + 15, + 'isAdmin:true isBot:false', + '', + '', + 'user_search_index', + false, + ]); + + await waitForElement(async () => { + const userSearchTerm = new URLSearchParams(window.location.search).get( + 'user' + ); + + return userSearchTerm === 'test'; + }); + + expect(searchBox).toHaveValue('test'); + expect(searchAPI).toHaveBeenCalled(); + }); + it('handleShowDeletedUserChange function should work properly', async () => { const userAPI = getUsers as jest.Mock; render(); diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/UserListPage/UserListPageV1.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/UserListPage/UserListPageV1.tsx index c90034c81dd..fc313e27f51 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/UserListPage/UserListPageV1.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/UserListPage/UserListPageV1.tsx @@ -102,9 +102,9 @@ const UserListPageV1 = () => { isAdmin = false, isDeleted = false ) => { - let filters = ''; + let filters = 'isBot:false'; if (isAdmin) { - filters = '(isAdmin:true)'; + filters = 'isAdmin:true isBot:false'; } return new Promise>((resolve) => {