diff --git a/CHANGELOG.md b/CHANGELOG.md index bc18b177..4368d9f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Changed HTTP Endpoint to Email for inbound email integrations ([#2816](https://github.com/grafana/oncall/issues/2816)) - Enable inbound email feature flag by default by @vadimkerr ([#2846](https://github.com/grafana/oncall/pull/2846)) +- Fixed initial search on Users page ([#2842](https://github.com/grafana/oncall/issues/2842)) ## v1.3.25 (2023-08-18) diff --git a/grafana-plugin/e2e-tests/users/usersActions.test.ts b/grafana-plugin/e2e-tests/users/usersActions.test.ts index 41b55aa4..fe524372 100644 --- a/grafana-plugin/e2e-tests/users/usersActions.test.ts +++ b/grafana-plugin/e2e-tests/users/usersActions.test.ts @@ -57,6 +57,20 @@ test.describe('Users screen actions', () => { await _testButtons(editorRolePage.page, 'button.edit-other-profile-button:not([disabled])'); }); + test('Search updates the table view', async ({ adminRolePage }) => { + const { page } = adminRolePage; + await goToOnCallPage(page, 'users'); + + const searchInput = page.locator(`[data-testid="search-users"]`); + + await searchInput.fill('oncall'); + await page.waitForTimeout(5000); + + const result = page.locator(`[data-testid="users-username"]`); + + expect(await result.count()).toBe(1); + }); + /* * Helper methods */ diff --git a/grafana-plugin/src/components/UsersFilters/UsersFilters.tsx b/grafana-plugin/src/components/UsersFilters/UsersFilters.tsx index e7834dcb..85e1cf83 100644 --- a/grafana-plugin/src/components/UsersFilters/UsersFilters.tsx +++ b/grafana-plugin/src/components/UsersFilters/UsersFilters.tsx @@ -11,10 +11,11 @@ interface UsersFiltersProps { value: any; onChange: (filters: any) => void; className?: string; + isLoading?: boolean; } const UsersFilters = (props: UsersFiltersProps) => { - const { value = { searchTerm: '' }, onChange, className } = props; + const { value = { searchTerm: '' }, onChange, className, isLoading } = props; const onSearchTermChangeCallback = useCallback( (e: ChangeEvent) => { @@ -31,11 +32,13 @@ const UsersFilters = (props: UsersFiltersProps) => { return (
} className={cx('search', 'control')} placeholder="Search users..." value={value.searchTerm} onChange={onSearchTermChangeCallback} + data-testid="search-users" />
); diff --git a/grafana-plugin/src/pages/schedule/Schedule.tsx b/grafana-plugin/src/pages/schedule/Schedule.tsx index b8c56e83..876de552 100644 --- a/grafana-plugin/src/pages/schedule/Schedule.tsx +++ b/grafana-plugin/src/pages/schedule/Schedule.tsx @@ -1,15 +1,6 @@ import React from 'react'; -import { - Button, - HorizontalGroup, - VerticalGroup, - IconButton, - ToolbarButton, - Icon, - Modal, - LoadingPlaceholder, -} from '@grafana/ui'; +import { Button, HorizontalGroup, VerticalGroup, IconButton, ToolbarButton, Icon, Modal } from '@grafana/ui'; import cn from 'classnames/bind'; import dayjs from 'dayjs'; import { observer } from 'mobx-react'; @@ -48,9 +39,7 @@ import styles from './Schedule.module.css'; const cx = cn.bind(styles); -interface SchedulePageProps extends PageProps, WithStoreProps, RouteComponentProps<{ id: string }> { - basicDataLoaded: boolean; -} +interface SchedulePageProps extends PageProps, WithStoreProps, RouteComponentProps<{ id: string }> {} interface SchedulePageState extends PageBaseState { startMoment: dayjs.Dayjs; @@ -123,7 +112,6 @@ class SchedulePage extends React.Component match: { params: { id: scheduleId }, }, - basicDataLoaded, } = this.props; const { @@ -160,9 +148,7 @@ class SchedulePage extends React.Component !!shiftIdToShowOverridesForm || shiftIdToShowRotationForm; - return !basicDataLoaded ? ( - - ) : ( + return ( {() => ( <> diff --git a/grafana-plugin/src/pages/users/Users.tsx b/grafana-plugin/src/pages/users/Users.tsx index 69b41794..7a2b388e 100644 --- a/grafana-plugin/src/pages/users/Users.tsx +++ b/grafana-plugin/src/pages/users/Users.tsx @@ -47,28 +47,34 @@ interface UsersState extends PageBaseState { searchTerm: string; }; initialUsersLoaded: boolean; + queuedUpdateUsers: boolean; } @observer class Users extends React.Component { - state: UsersState = { - page: 1, - isWrongTeam: false, - userPkToEdit: undefined, - usersFilters: { - searchTerm: '', - }, + constructor(props: UsersProps) { + super(props); - errorData: initErrorDataState(), - initialUsersLoaded: false, - }; - - async componentDidMount() { const { query: { p }, - } = this.props; - this.setState({ page: p ? Number(p) : 1 }, this.updateUsers); + } = props; + this.state = { + page: p ? Number(p) : 1, + isWrongTeam: false, + userPkToEdit: undefined, + usersFilters: { + searchTerm: '', + }, + + errorData: initErrorDataState(), + initialUsersLoaded: false, + queuedUpdateUsers: false, + }; + } + + async componentDidMount() { + this.updateUsers(); this.parseParams(); } @@ -84,14 +90,15 @@ class Users extends React.Component { LocationHelper.update({ p: page }, 'partial'); await userStore.updateItems(usersFilters, page); - this.setState({ initialUsersLoaded: true }); + const { queuedUpdateUsers } = this.state; + this.setState({ initialUsersLoaded: true, queuedUpdateUsers: false }, () => { + if (queuedUpdateUsers) { + this.updateUsers(); + } + }); }; componentDidUpdate(prevProps: UsersProps) { - if (!this.state.initialUsersLoaded) { - this.updateUsers(); - } - if (prevProps.match.params.id !== this.props.match.params.id) { this.parseParams(); } @@ -176,14 +183,15 @@ class Users extends React.Component { const { store: { userStore }, } = this.props; - const { usersFilters, page, initialUsersLoaded, userPkToEdit } = this.state; + + const { usersFilters, page, initialUsersLoaded, userPkToEdit, queuedUpdateUsers } = this.state; const { count, results } = userStore.getSearchResult(); const columns = this.getTableColumns(); const handleClear = () => this.setState({ usersFilters: { searchTerm: '' } }, () => { - this.debouncedUpdateUsers(); + this.updateUsers(); }); return ( @@ -194,6 +202,7 @@ class Users extends React.Component {