diff --git a/public/app/features/teams/TeamPages.test.tsx b/public/app/features/teams/TeamPages.test.tsx
index 9aa5ca4af28..6957986c02e 100644
--- a/public/app/features/teams/TeamPages.test.tsx
+++ b/public/app/features/teams/TeamPages.test.tsx
@@ -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 () =>
Team group sync
;
});
-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(
-
-
-
+
+
+
+
+
);
};
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',
});
diff --git a/public/app/features/teams/TeamPages.tsx b/public/app/features/teams/TeamPages.tsx
index 240a113dda5..83bcaa76400 100644
--- a/public/app/features/teams/TeamPages.tsx
+++ b/public/app/features/teams/TeamPages.tsx
@@ -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, 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();
+ 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;
-
-export class TeamPages extends PureComponent {
- 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 {
case PageTypes.Settings:
return canReadTeam && ;
case PageTypes.GroupSync:
- if (isSyncEnabled) {
+ if (isSyncEnabled.current) {
if (canReadTeamPermissions) {
return ;
}
@@ -143,19 +101,15 @@ export class TeamPages extends PureComponent {
}
return null;
- }
+ };
- render() {
- const { team, pageNav } = this.props;
+ return (
+
+ {team && Object.keys(team).length !== 0 && renderPage()}
+
+ );
+});
- return (
-
-
- {team && Object.keys(team).length !== 0 && this.renderPage()}
-
-
- );
- }
-}
+TeamPages.displayName = 'TeamPages';
-export default connector(withTheme2(TeamPages));
+export default TeamPages;
diff --git a/public/app/features/teams/state/actions.ts b/public/app/features/teams/state/actions.ts
index caffdba46d3..fc745433f28 100644
--- a/public/app/features/teams/state/actions.ts
+++ b/public/app/features/teams/state/actions.ts
@@ -60,7 +60,7 @@ export function loadTeams(initial = false): ThunkResult {
const loadTeamsWithDebounce = debounce((dispatch) => dispatch(loadTeams()), 500);
-export function loadTeam(id: number): ThunkResult {
+export function loadTeam(id: number): ThunkResult> {
return async (dispatch) => {
const response = await getBackendSrv().get(`/api/teams/${id}`, accessControlQueryParam());
dispatch(teamLoaded(response));