diff --git a/public/app/features/teams/TeamList.tsx b/public/app/features/teams/TeamList.tsx index 37a60643eca..337048408a5 100644 --- a/public/app/features/teams/TeamList.tsx +++ b/public/app/features/teams/TeamList.tsx @@ -1,6 +1,9 @@ +import { css } from '@emotion/css'; import React, { useEffect, useMemo, useState } from 'react'; +import Skeleton from 'react-loading-skeleton'; import { connect, ConnectedProps } from 'react-redux'; +import { GrafanaTheme2 } from '@grafana/data'; import { Avatar, CellProps, @@ -14,6 +17,7 @@ import { Pagination, Stack, Tooltip, + useStyles2, } from '@grafana/ui'; import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA'; import { Page } from 'app/core/components/Page/Page'; @@ -32,6 +36,15 @@ export interface State { roleOptions: Role[]; } +// this is dummy data to pass to the table while the real data is loading +const skeletonData: Team[] = new Array(3).fill(null).map((_, index) => ({ + id: index, + memberCount: 0, + name: '', + orgId: 0, + permission: 0, +})); + export const TeamList = ({ teams, query, @@ -47,6 +60,7 @@ export const TeamList = ({ changeSort, }: Props) => { const [roleOptions, setRoleOptions] = useState([]); + const styles = useStyles2(getStyles); useEffect(() => { loadTeams(true); @@ -66,24 +80,47 @@ export const TeamList = ({ { id: 'avatarUrl', header: '', - cell: ({ cell: { value } }: Cell<'avatarUrl'>) => value && , + disableGrow: true, + cell: ({ cell: { value } }: Cell<'avatarUrl'>) => { + if (!hasFetched) { + return ; + } + + return value && ; + }, }, { id: 'name', header: 'Name', - cell: ({ cell: { value } }: Cell<'name'>) => value, + cell: ({ cell: { value } }: Cell<'name'>) => { + if (!hasFetched) { + return ; + } + return value; + }, sortType: 'string', }, { id: 'email', header: 'Email', - cell: ({ cell: { value } }: Cell<'email'>) => value, + cell: ({ cell: { value } }: Cell<'email'>) => { + if (!hasFetched) { + return ; + } + return value; + }, sortType: 'string', }, { id: 'memberCount', header: 'Members', - cell: ({ cell: { value } }: Cell<'memberCount'>) => value, + disableGrow: true, + cell: ({ cell: { value } }: Cell<'memberCount'>) => { + if (!hasFetched) { + return ; + } + return value; + }, sortType: 'number', }, ...(displayRolePicker @@ -92,6 +129,9 @@ export const TeamList = ({ id: 'role', header: 'Role', cell: ({ cell: { value }, row: { original } }: Cell<'memberCount'>) => { + if (!hasFetched) { + return ; + } const canSeeTeamRoles = contextSrv.hasPermissionInMetadata( AccessControlAction.ActionTeamsRolesList, original @@ -112,42 +152,54 @@ export const TeamList = ({ ] : []), { - id: 'edit', + id: 'actions', header: '', + disableGrow: true, cell: ({ row: { original } }: Cell) => { - const canReadTeam = contextSrv.hasPermissionInMetadata(AccessControlAction.ActionTeamsRead, original); - return canReadTeam ? ( - - - - - - ) : null; - }, - }, - { - id: 'delete', - header: '', - cell: ({ row: { original } }: Cell) => { - const canDelete = contextSrv.hasPermissionInMetadata(AccessControlAction.ActionTeamsDelete, original); + if (!hasFetched) { + return ( + + + + + ); + } + const canReadTeam = contextSrv.hasPermissionInMetadata(AccessControlAction.ActionTeamsRead, original); + const canDelete = contextSrv.hasPermissionInMetadata(AccessControlAction.ActionTeamsDelete, original); return ( - deleteTeam(original.id)} - /> + + {canReadTeam && ( + + + + + + )} + deleteTeam(original.id)} + /> + ); }, }, ], - [displayRolePicker, rolesLoading, roleOptions, deleteTeam] + [displayRolePicker, hasFetched, rolesLoading, roleOptions, deleteTeam, styles] ); return ( - - + + New Team + + } + > + {noTeams ? ( - - - New Team - String(team.id)} fetchData={changeSort} /> @@ -221,3 +269,11 @@ const mapDispatchToProps = { const connector = connect(mapStateToProps, mapDispatchToProps); export type Props = OwnProps & ConnectedProps; export default connector(TeamList); + +const getStyles = (theme: GrafanaTheme2) => ({ + blockSkeleton: css({ + lineHeight: 1, + // needed for things to align properly in the table + display: 'flex', + }), +});