TeamMembers.tsx 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. import React, { PureComponent } from 'react';
  2. import { connect, ConnectedProps } from 'react-redux';
  3. import { SelectableValue } from '@grafana/data';
  4. import { Button, FilterInput, Label } from '@grafana/ui';
  5. import { SlideDown } from 'app/core/components/Animations/SlideDown';
  6. import { CloseButton } from 'app/core/components/CloseButton/CloseButton';
  7. import { UserPicker } from 'app/core/components/Select/UserPicker';
  8. import { TagBadge } from 'app/core/components/TagFilter/TagBadge';
  9. import { WithFeatureToggle } from 'app/core/components/WithFeatureToggle';
  10. import { config } from 'app/core/config';
  11. import { contextSrv } from 'app/core/services/context_srv';
  12. import { TeamMember, OrgUser } from 'app/types';
  13. import TeamMemberRow from './TeamMemberRow';
  14. import { addTeamMember } from './state/actions';
  15. import { setSearchMemberQuery } from './state/reducers';
  16. import { getSearchMemberQuery, isSignedInUserTeamAdmin } from './state/selectors';
  17. function mapStateToProps(state: any) {
  18. return {
  19. searchMemberQuery: getSearchMemberQuery(state.team),
  20. editorsCanAdmin: config.editorsCanAdmin, // this makes the feature toggle mockable/controllable from tests,
  21. signedInUser: contextSrv.user, // this makes the feature toggle mockable/controllable from tests,
  22. };
  23. }
  24. const mapDispatchToProps = {
  25. addTeamMember,
  26. setSearchMemberQuery,
  27. };
  28. const connector = connect(mapStateToProps, mapDispatchToProps);
  29. interface OwnProps {
  30. members: TeamMember[];
  31. syncEnabled: boolean;
  32. }
  33. export type Props = ConnectedProps<typeof connector> & OwnProps;
  34. export interface State {
  35. isAdding: boolean;
  36. newTeamMember?: SelectableValue<OrgUser['userId']> | null;
  37. }
  38. export class TeamMembers extends PureComponent<Props, State> {
  39. constructor(props: Props) {
  40. super(props);
  41. this.state = { isAdding: false, newTeamMember: null };
  42. }
  43. onSearchQueryChange = (value: string) => {
  44. this.props.setSearchMemberQuery(value);
  45. };
  46. onToggleAdding = () => {
  47. this.setState({ isAdding: !this.state.isAdding });
  48. };
  49. onUserSelected = (user: SelectableValue<OrgUser['userId']>) => {
  50. this.setState({ newTeamMember: user });
  51. };
  52. onAddUserToTeam = async () => {
  53. this.props.addTeamMember(this.state.newTeamMember!.id);
  54. this.setState({ newTeamMember: null });
  55. };
  56. renderLabels(labels: string[]) {
  57. if (!labels) {
  58. return <td />;
  59. }
  60. return (
  61. <td>
  62. {labels.map((label) => (
  63. <TagBadge key={label} label={label} removeIcon={false} count={0} onClick={() => {}} />
  64. ))}
  65. </td>
  66. );
  67. }
  68. render() {
  69. const { isAdding } = this.state;
  70. const { searchMemberQuery, members, syncEnabled, editorsCanAdmin, signedInUser } = this.props;
  71. const isTeamAdmin = isSignedInUserTeamAdmin({ members, editorsCanAdmin, signedInUser });
  72. return (
  73. <div>
  74. <div className="page-action-bar">
  75. <div className="gf-form gf-form--grow">
  76. <FilterInput placeholder="Search members" value={searchMemberQuery} onChange={this.onSearchQueryChange} />
  77. </div>
  78. <Button className="pull-right" onClick={this.onToggleAdding} disabled={isAdding || !isTeamAdmin}>
  79. Add member
  80. </Button>
  81. </div>
  82. <SlideDown in={isAdding}>
  83. <div className="cta-form">
  84. <CloseButton aria-label="Close 'Add team member' dialogue" onClick={this.onToggleAdding} />
  85. <Label htmlFor="user-picker">Add team member</Label>
  86. <div className="gf-form-inline">
  87. <UserPicker inputId="user-picker" onSelected={this.onUserSelected} className="min-width-30" />
  88. {this.state.newTeamMember && (
  89. <Button type="submit" onClick={this.onAddUserToTeam}>
  90. Add to team
  91. </Button>
  92. )}
  93. </div>
  94. </div>
  95. </SlideDown>
  96. <div className="admin-list-table">
  97. <table className="filter-table filter-table--hover form-inline">
  98. <thead>
  99. <tr>
  100. <th />
  101. <th>Login</th>
  102. <th>Email</th>
  103. <th>Name</th>
  104. <WithFeatureToggle featureToggle={editorsCanAdmin}>
  105. <th>Permission</th>
  106. </WithFeatureToggle>
  107. {syncEnabled && <th />}
  108. <th style={{ width: '1%' }} />
  109. </tr>
  110. </thead>
  111. <tbody>
  112. {members &&
  113. members.map((member) => (
  114. <TeamMemberRow
  115. key={member.userId}
  116. member={member}
  117. syncEnabled={syncEnabled}
  118. editorsCanAdmin={editorsCanAdmin}
  119. signedInUserIsTeamAdmin={isTeamAdmin}
  120. />
  121. ))}
  122. </tbody>
  123. </table>
  124. </div>
  125. </div>
  126. );
  127. }
  128. }
  129. export default connector(TeamMembers);