Chore: Convert TeamPages from class component to function component (#87602)

This commit is contained in:
kay delaney
2024-05-13 17:58:27 +01:00
committed by GitHub
parent 18f8f2324a
commit da0f7a5036
3 changed files with 63 additions and 132 deletions
+15 -38
View File
@@ -1,14 +1,11 @@
import { render, screen } from '@testing-library/react';
import { screen } from '@testing-library/react';
import React from 'react';
import { match } from 'react-router-dom';
import { TestProvider } from 'test/helpers/TestProvider';
import { Route, Router } from 'react-router-dom';
import { render } from 'test/test-utils';
import { createTheme } from '@grafana/data';
import { getRouteComponentProps } from 'app/core/navigation/__mocks__/routeProps';
import { locationService } from '@grafana/runtime';
import { Team } from '../../types';
import { Props, TeamPages } from './TeamPages';
import TeamPages from './TeamPages';
import { getMockTeam } from './__mocks__/teamMocks';
jest.mock('app/core/components/Select/UserPicker', () => {
@@ -26,7 +23,7 @@ jest.mock('app/core/services/context_srv', () => ({
jest.mock('@grafana/runtime', () => ({
...jest.requireActual('@grafana/runtime'),
getBackendSrv: () => ({
get: jest.fn().mockResolvedValue([{ userId: 1, login: 'Test' }]),
get: jest.fn().mockResolvedValue(getMockTeam()),
}),
config: {
...jest.requireActual('@grafana/runtime').config,
@@ -62,43 +59,24 @@ jest.mock('./TeamGroupSync', () => {
return () => <div>Team group sync</div>;
});
const setup = (propOverrides?: object) => {
const props: Props = {
...getRouteComponentProps({
match: {
params: {
id: '1',
page: null,
},
} as unknown as match,
}),
pageNav: { text: 'Cool team ' },
teamId: 1,
loadTeam: jest.fn(),
pageName: 'members',
team: {} as Team,
theme: createTheme(),
};
Object.assign(props, propOverrides);
const setup = (propOverrides: { teamId?: number; pageName?: string } = {}) => {
const pageName = propOverrides.pageName ?? 'members';
const teamId = propOverrides.teamId ?? 1;
locationService.push({ pathname: `/org/teams/edit/${teamId}/${pageName}` });
render(
<TestProvider>
<TeamPages {...props} />
</TestProvider>
<Router history={locationService.getHistory()}>
<Route path="/org/teams/edit/:id/:page?">
<TeamPages />
</Route>
</Router>
);
};
describe('TeamPages', () => {
it('should render settings and preferences page', async () => {
setup({
team: getMockTeam(),
pageName: 'settings',
preferences: {
homeDashboardUID: 'home-dashboard',
theme: 'Default',
timezone: 'Default',
},
});
expect(await screen.findByText('Team settings')).toBeInTheDocument();
@@ -106,7 +84,6 @@ describe('TeamPages', () => {
it('should render group sync page', async () => {
setup({
team: getMockTeam(),
pageName: 'groupsync',
});
+47 -93
View File
@@ -1,16 +1,15 @@
import { includes } from 'lodash';
import React, { PureComponent } from 'react';
import { connect, ConnectedProps } from 'react-redux';
import { createSelector } from '@reduxjs/toolkit';
import React, { useMemo, useRef } from 'react';
import { useParams } from 'react-router';
import { useAsync } from 'react-use';
import { featureEnabled } from '@grafana/runtime';
import { Themeable2, withTheme2 } from '@grafana/ui';
import { Page } from 'app/core/components/Page/Page';
import { UpgradeBox } from 'app/core/components/Upgrade/UpgradeBox';
import config from 'app/core/config';
import { GrafanaRouteComponentProps } from 'app/core/navigation/types';
import { getNavModel } from 'app/core/selectors/navModel';
import { contextSrv } from 'app/core/services/context_srv';
import { AccessControlAction, StoreState } from 'app/types';
import { AccessControlAction, StoreState, useDispatch, useSelector } from 'app/types';
import TeamGroupSync, { TeamSyncUpgradeContent } from './TeamGroupSync';
import TeamPermissions from './TeamPermissions';
@@ -21,14 +20,7 @@ import { getTeam } from './state/selectors';
interface TeamPageRouteParams {
id: string;
page: string | null;
}
export interface OwnProps extends GrafanaRouteComponentProps<TeamPageRouteParams>, Themeable2 {}
interface State {
isSyncEnabled: boolean;
isLoading: boolean;
page?: string;
}
enum PageTypes {
@@ -37,78 +29,44 @@ enum PageTypes {
GroupSync = 'groupsync',
}
function mapStateToProps(state: StoreState, props: OwnProps) {
const teamId = parseInt(props.match.params.id, 10);
const team = getTeam(state.team, teamId);
const PAGES = ['members', 'settings', 'groupsync'];
const teamSelector = createSelector(
[(state: StoreState) => state.team, (_: StoreState, teamId: number) => teamId],
(team, teamId) => getTeam(team, teamId)
);
const pageNavSelector = createSelector(
[
(state: StoreState) => state.navIndex,
(_state: StoreState, pageName: string) => pageName,
(_state: StoreState, _pageName: string, teamId: number) => teamId,
],
(navIndex, pageName, teamId) => {
const teamLoadingNav = getTeamLoadingNav(pageName);
return getNavModel(navIndex, `team-${pageName}-${teamId}`, teamLoadingNav).main;
}
);
const TeamPages = React.memo(() => {
const isSyncEnabled = useRef(featureEnabled('teamsync'));
const params = useParams<TeamPageRouteParams>();
const teamId = useMemo(() => parseInt(params.id, 10), [params]);
const team = useSelector((state) => teamSelector(state, teamId));
let defaultPage = 'members';
// With RBAC the settings page will always be available
if (!team || !contextSrv.hasPermissionInMetadata(AccessControlAction.ActionTeamsPermissionsRead, team)) {
defaultPage = 'settings';
}
const pageName = props.match.params.page ?? defaultPage;
const teamLoadingNav = getTeamLoadingNav(pageName);
const pageNav = getNavModel(state.navIndex, `team-${pageName}-${teamId}`, teamLoadingNav).main;
const pageName = params.page ?? defaultPage;
const pageNav = useSelector((state) => pageNavSelector(state, pageName, teamId));
return {
pageNav,
teamId: teamId,
pageName: pageName,
team,
};
}
const dispatch = useDispatch();
const { loading: isLoading } = useAsync(async () => dispatch(loadTeam(teamId)), [teamId]);
const mapDispatchToProps = {
loadTeam,
};
const connector = connect(mapStateToProps, mapDispatchToProps);
export type Props = OwnProps & ConnectedProps<typeof connector>;
export class TeamPages extends PureComponent<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
isLoading: false,
isSyncEnabled: featureEnabled('teamsync'),
};
}
async componentDidMount() {
await this.fetchTeam();
}
async fetchTeam() {
const { loadTeam, teamId } = this.props;
this.setState({ isLoading: true });
const team = await loadTeam(teamId);
this.setState({ isLoading: false });
return team;
}
getCurrentPage() {
const pages = ['members', 'settings', 'groupsync'];
const currentPage = this.props.pageName;
return includes(pages, currentPage) ? currentPage : pages[0];
}
textsAreEqual = (text1: string, text2: string) => {
if (!text1 && !text2) {
return true;
}
if (!text1 || !text2) {
return false;
}
return text1.toLocaleLowerCase() === text2.toLocaleLowerCase();
};
renderPage(): React.ReactNode {
const { isSyncEnabled } = this.state;
const { team } = this.props;
const currentPage = this.getCurrentPage();
const renderPage = () => {
const currentPage = PAGES.includes(pageName) ? pageName : PAGES[0];
const canReadTeam = contextSrv.hasPermissionInMetadata(AccessControlAction.ActionTeamsRead, team!);
const canReadTeamPermissions = contextSrv.hasPermissionInMetadata(
@@ -128,7 +86,7 @@ export class TeamPages extends PureComponent<Props, State> {
case PageTypes.Settings:
return canReadTeam && <TeamSettings team={team!} />;
case PageTypes.GroupSync:
if (isSyncEnabled) {
if (isSyncEnabled.current) {
if (canReadTeamPermissions) {
return <TeamGroupSync isReadOnly={!canWriteTeamPermissions} />;
}
@@ -143,19 +101,15 @@ export class TeamPages extends PureComponent<Props, State> {
}
return null;
}
};
render() {
const { team, pageNav } = this.props;
return (
<Page navId="teams" pageNav={pageNav}>
<Page.Contents isLoading={isLoading}>{team && Object.keys(team).length !== 0 && renderPage()}</Page.Contents>
</Page>
);
});
return (
<Page navId="teams" pageNav={pageNav}>
<Page.Contents isLoading={this.state.isLoading}>
{team && Object.keys(team).length !== 0 && this.renderPage()}
</Page.Contents>
</Page>
);
}
}
TeamPages.displayName = 'TeamPages';
export default connector(withTheme2(TeamPages));
export default TeamPages;
+1 -1
View File
@@ -60,7 +60,7 @@ export function loadTeams(initial = false): ThunkResult<void> {
const loadTeamsWithDebounce = debounce((dispatch) => dispatch(loadTeams()), 500);
export function loadTeam(id: number): ThunkResult<void> {
export function loadTeam(id: number): ThunkResult<Promise<void>> {
return async (dispatch) => {
const response = await getBackendSrv().get(`/api/teams/${id}`, accessControlQueryParam());
dispatch(teamLoaded(response));