mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
flattened team state, tests for TeamMembers
This commit is contained in:
parent
59b3bfd342
commit
d494ebc730
79
public/app/features/teams/TeamMembers.test.tsx
Normal file
79
public/app/features/teams/TeamMembers.test.tsx
Normal file
@ -0,0 +1,79 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { TeamMembers, Props } from './TeamMembers';
|
||||
import { TeamMember } from '../../types';
|
||||
import { getMockTeamMember, getMockTeamMembers } from './__mocks__/teamMocks';
|
||||
|
||||
const setup = (propOverrides?: object) => {
|
||||
const props: Props = {
|
||||
members: [] as TeamMember[],
|
||||
searchMemberQuery: '',
|
||||
setSearchMemberQuery: jest.fn(),
|
||||
loadTeamMembers: jest.fn(),
|
||||
addTeamMember: jest.fn(),
|
||||
removeTeamMember: jest.fn(),
|
||||
};
|
||||
|
||||
Object.assign(props, propOverrides);
|
||||
|
||||
const wrapper = shallow(<TeamMembers {...props} />);
|
||||
const instance = wrapper.instance() as TeamMembers;
|
||||
|
||||
return {
|
||||
wrapper,
|
||||
instance,
|
||||
};
|
||||
};
|
||||
|
||||
describe('Render', () => {
|
||||
it('should render component', () => {
|
||||
const { wrapper } = setup();
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should render team members', () => {
|
||||
const { wrapper } = setup({
|
||||
members: getMockTeamMembers(5),
|
||||
});
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Functions', () => {
|
||||
describe('on search member query change', () => {
|
||||
it('it should call setSearchMemberQuery', () => {
|
||||
const { instance } = setup();
|
||||
const mockEvent = { target: { value: 'member' } };
|
||||
|
||||
instance.onSearchQueryChange(mockEvent);
|
||||
|
||||
expect(instance.props.setSearchMemberQuery).toHaveBeenCalledWith('member');
|
||||
});
|
||||
});
|
||||
|
||||
describe('on remove member', () => {
|
||||
const { instance } = setup();
|
||||
const mockTeamMember = getMockTeamMember();
|
||||
|
||||
instance.onRemoveMember(mockTeamMember);
|
||||
|
||||
expect(instance.props.removeTeamMember).toHaveBeenCalledWith(1);
|
||||
});
|
||||
|
||||
describe('on add user to team', () => {
|
||||
const { wrapper, instance } = setup();
|
||||
|
||||
wrapper.state().newTeamMember = {
|
||||
id: 1,
|
||||
label: '',
|
||||
avatarUrl: '',
|
||||
login: '',
|
||||
};
|
||||
|
||||
instance.onAddUserToTeam();
|
||||
|
||||
expect(instance.props.addTeamMember).toHaveBeenCalledWith(1);
|
||||
});
|
||||
});
|
@ -1,16 +1,14 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { hot } from 'react-hot-loader';
|
||||
import SlideDown from 'app/core/components/Animations/SlideDown';
|
||||
import { UserPicker, User } from 'app/core/components/Picker/UserPicker';
|
||||
import DeleteButton from 'app/core/components/DeleteButton/DeleteButton';
|
||||
import { Team, TeamMember } from '../../types';
|
||||
import { TeamMember } from '../../types';
|
||||
import { loadTeamMembers, addTeamMember, removeTeamMember, setSearchMemberQuery } from './state/actions';
|
||||
import { getSearchMemberQuery, getTeam } from './state/selectors';
|
||||
import { getRouteParamsId } from '../../core/selectors/location';
|
||||
import { getSearchMemberQuery, getTeamMembers } from './state/selectors';
|
||||
|
||||
interface Props {
|
||||
team: Team;
|
||||
export interface Props {
|
||||
members: TeamMember[];
|
||||
searchMemberQuery: string;
|
||||
loadTeamMembers: typeof loadTeamMembers;
|
||||
addTeamMember: typeof addTeamMember;
|
||||
@ -37,7 +35,7 @@ export class TeamMembers extends PureComponent<Props, State> {
|
||||
this.props.setSearchMemberQuery(event.target.value);
|
||||
};
|
||||
|
||||
removeMember(member: TeamMember) {
|
||||
onRemoveMember(member: TeamMember) {
|
||||
this.props.removeTeamMember(member.userId);
|
||||
}
|
||||
|
||||
@ -63,7 +61,7 @@ export class TeamMembers extends PureComponent<Props, State> {
|
||||
<td>{member.login}</td>
|
||||
<td>{member.email}</td>
|
||||
<td className="text-right">
|
||||
<DeleteButton onConfirmDelete={() => this.removeMember(member)} />
|
||||
<DeleteButton onConfirmDelete={() => this.onRemoveMember(member)} />
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
@ -71,7 +69,7 @@ export class TeamMembers extends PureComponent<Props, State> {
|
||||
|
||||
render() {
|
||||
const { newTeamMember, isAdding } = this.state;
|
||||
const { team, searchMemberQuery } = this.props;
|
||||
const { searchMemberQuery, members } = this.props;
|
||||
const newTeamMemberValue = newTeamMember && newTeamMember.id.toString();
|
||||
|
||||
return (
|
||||
@ -125,7 +123,7 @@ export class TeamMembers extends PureComponent<Props, State> {
|
||||
<th style={{ width: '1%' }} />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>{team.members && team.members.map(member => this.renderMember(member))}</tbody>
|
||||
<tbody>{members && members.map(member => this.renderMember(member))}</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@ -134,10 +132,8 @@ export class TeamMembers extends PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
function mapStateToProps(state) {
|
||||
const teamId = getRouteParamsId(state.location);
|
||||
|
||||
return {
|
||||
team: getTeam(state.team, teamId),
|
||||
members: getTeamMembers(state.team),
|
||||
searchMemberQuery: getSearchMemberQuery(state.team),
|
||||
};
|
||||
}
|
||||
@ -149,4 +145,4 @@ const mapDispatchToProps = {
|
||||
setSearchMemberQuery,
|
||||
};
|
||||
|
||||
export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(TeamMembers));
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(TeamMembers);
|
||||
|
@ -41,10 +41,10 @@ export class TeamPages extends PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.loadTeam();
|
||||
this.fetchTeam();
|
||||
}
|
||||
|
||||
async loadTeam() {
|
||||
async fetchTeam() {
|
||||
const { loadTeam, teamId } = this.props;
|
||||
|
||||
await loadTeam(teamId);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Team } from '../../../types';
|
||||
import { Team, TeamMember } from '../../../types';
|
||||
|
||||
export const getMultipleMockTeams = (numberOfTeams: number): Team[] => {
|
||||
let teams: Team[] = [];
|
||||
@ -9,9 +9,6 @@ export const getMultipleMockTeams = (numberOfTeams: number): Team[] => {
|
||||
avatarUrl: 'some/url/',
|
||||
email: `test-${i}@test.com`,
|
||||
memberCount: i,
|
||||
search: '',
|
||||
members: [],
|
||||
groups: [],
|
||||
});
|
||||
}
|
||||
|
||||
@ -25,13 +22,26 @@ export const getMockTeam = (): Team => {
|
||||
avatarUrl: 'some/url/',
|
||||
email: 'test@test.com',
|
||||
memberCount: 1,
|
||||
search: '',
|
||||
members: [],
|
||||
groups: [],
|
||||
};
|
||||
};
|
||||
|
||||
export const getMockTeamMember = () => {
|
||||
export const getMockTeamMembers = (amount: number): TeamMember[] => {
|
||||
let teamMembers: TeamMember[] = [];
|
||||
|
||||
for (let i = 1; i <= amount; i++) {
|
||||
teamMembers.push({
|
||||
userId: i,
|
||||
teamId: 1,
|
||||
avatarUrl: 'some/url/',
|
||||
email: 'test@test.com',
|
||||
login: `testUser-${i}`,
|
||||
});
|
||||
}
|
||||
|
||||
return teamMembers;
|
||||
};
|
||||
|
||||
export const getMockTeamMember = (): TeamMember => {
|
||||
return {
|
||||
userId: 1,
|
||||
teamId: 1,
|
||||
|
@ -0,0 +1,317 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Render should render component 1`] = `
|
||||
<div>
|
||||
<div
|
||||
className="page-action-bar"
|
||||
>
|
||||
<div
|
||||
className="gf-form gf-form--grow"
|
||||
>
|
||||
<label
|
||||
className="gf-form--has-input-icon gf-form--grow"
|
||||
>
|
||||
<input
|
||||
className="gf-form-input"
|
||||
onChange={[Function]}
|
||||
placeholder="Search members"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<i
|
||||
className="gf-form-input-icon fa fa-search"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
className="page-action-bar__spacer"
|
||||
/>
|
||||
<button
|
||||
className="btn btn-success pull-right"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
>
|
||||
<i
|
||||
className="fa fa-plus"
|
||||
/>
|
||||
Add a member
|
||||
</button>
|
||||
</div>
|
||||
<Component
|
||||
in={false}
|
||||
>
|
||||
<div
|
||||
className="cta-form"
|
||||
>
|
||||
<button
|
||||
className="cta-form__close btn btn-transparent"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<i
|
||||
className="fa fa-close"
|
||||
/>
|
||||
</button>
|
||||
<h5>
|
||||
Add Team Member
|
||||
</h5>
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<UserPicker
|
||||
className="width-30"
|
||||
onSelected={[Function]}
|
||||
value={null}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Component>
|
||||
<div
|
||||
className="admin-list-table"
|
||||
>
|
||||
<table
|
||||
className="filter-table filter-table--hover form-inline"
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th />
|
||||
<th>
|
||||
Name
|
||||
</th>
|
||||
<th>
|
||||
Email
|
||||
</th>
|
||||
<th
|
||||
style={
|
||||
Object {
|
||||
"width": "1%",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody />
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Render should render team members 1`] = `
|
||||
<div>
|
||||
<div
|
||||
className="page-action-bar"
|
||||
>
|
||||
<div
|
||||
className="gf-form gf-form--grow"
|
||||
>
|
||||
<label
|
||||
className="gf-form--has-input-icon gf-form--grow"
|
||||
>
|
||||
<input
|
||||
className="gf-form-input"
|
||||
onChange={[Function]}
|
||||
placeholder="Search members"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<i
|
||||
className="gf-form-input-icon fa fa-search"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
className="page-action-bar__spacer"
|
||||
/>
|
||||
<button
|
||||
className="btn btn-success pull-right"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
>
|
||||
<i
|
||||
className="fa fa-plus"
|
||||
/>
|
||||
Add a member
|
||||
</button>
|
||||
</div>
|
||||
<Component
|
||||
in={false}
|
||||
>
|
||||
<div
|
||||
className="cta-form"
|
||||
>
|
||||
<button
|
||||
className="cta-form__close btn btn-transparent"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<i
|
||||
className="fa fa-close"
|
||||
/>
|
||||
</button>
|
||||
<h5>
|
||||
Add Team Member
|
||||
</h5>
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<UserPicker
|
||||
className="width-30"
|
||||
onSelected={[Function]}
|
||||
value={null}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Component>
|
||||
<div
|
||||
className="admin-list-table"
|
||||
>
|
||||
<table
|
||||
className="filter-table filter-table--hover form-inline"
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th />
|
||||
<th>
|
||||
Name
|
||||
</th>
|
||||
<th>
|
||||
Email
|
||||
</th>
|
||||
<th
|
||||
style={
|
||||
Object {
|
||||
"width": "1%",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
key="1"
|
||||
>
|
||||
<td
|
||||
className="width-4 text-center"
|
||||
>
|
||||
<img
|
||||
className="filter-table__avatar"
|
||||
src="some/url/"
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
testUser-1
|
||||
</td>
|
||||
<td>
|
||||
test@test.com
|
||||
</td>
|
||||
<td
|
||||
className="text-right"
|
||||
>
|
||||
<DeleteButton
|
||||
onConfirmDelete={[Function]}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
key="2"
|
||||
>
|
||||
<td
|
||||
className="width-4 text-center"
|
||||
>
|
||||
<img
|
||||
className="filter-table__avatar"
|
||||
src="some/url/"
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
testUser-2
|
||||
</td>
|
||||
<td>
|
||||
test@test.com
|
||||
</td>
|
||||
<td
|
||||
className="text-right"
|
||||
>
|
||||
<DeleteButton
|
||||
onConfirmDelete={[Function]}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
key="3"
|
||||
>
|
||||
<td
|
||||
className="width-4 text-center"
|
||||
>
|
||||
<img
|
||||
className="filter-table__avatar"
|
||||
src="some/url/"
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
testUser-3
|
||||
</td>
|
||||
<td>
|
||||
test@test.com
|
||||
</td>
|
||||
<td
|
||||
className="text-right"
|
||||
>
|
||||
<DeleteButton
|
||||
onConfirmDelete={[Function]}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
key="4"
|
||||
>
|
||||
<td
|
||||
className="width-4 text-center"
|
||||
>
|
||||
<img
|
||||
className="filter-table__avatar"
|
||||
src="some/url/"
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
testUser-4
|
||||
</td>
|
||||
<td>
|
||||
test@test.com
|
||||
</td>
|
||||
<td
|
||||
className="text-right"
|
||||
>
|
||||
<DeleteButton
|
||||
onConfirmDelete={[Function]}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
key="5"
|
||||
>
|
||||
<td
|
||||
className="width-4 text-center"
|
||||
>
|
||||
<img
|
||||
className="filter-table__avatar"
|
||||
src="some/url/"
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
testUser-5
|
||||
</td>
|
||||
<td>
|
||||
test@test.com
|
||||
</td>
|
||||
<td
|
||||
className="text-right"
|
||||
>
|
||||
<DeleteButton
|
||||
onConfirmDelete={[Function]}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
@ -21,12 +21,9 @@ exports[`Render should render group sync page 1`] = `
|
||||
Object {
|
||||
"avatarUrl": "some/url/",
|
||||
"email": "test@test.com",
|
||||
"groups": Array [],
|
||||
"id": 1,
|
||||
"memberCount": 1,
|
||||
"members": Array [],
|
||||
"name": "test",
|
||||
"search": "",
|
||||
}
|
||||
}
|
||||
/>
|
||||
@ -42,20 +39,7 @@ exports[`Render should render member page if team not empty 1`] = `
|
||||
<div
|
||||
className="page-container page-body"
|
||||
>
|
||||
<TeamMembers
|
||||
team={
|
||||
Object {
|
||||
"avatarUrl": "some/url/",
|
||||
"email": "test@test.com",
|
||||
"groups": Array [],
|
||||
"id": 1,
|
||||
"memberCount": 1,
|
||||
"members": Array [],
|
||||
"name": "test",
|
||||
"search": "",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Connect(TeamMembers) />
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@ -73,12 +57,9 @@ exports[`Render should render settings page 1`] = `
|
||||
Object {
|
||||
"avatarUrl": "some/url/",
|
||||
"email": "test@test.com",
|
||||
"groups": Array [],
|
||||
"id": 1,
|
||||
"memberCount": 1,
|
||||
"members": Array [],
|
||||
"name": "test",
|
||||
"search": "",
|
||||
}
|
||||
}
|
||||
/>
|
||||
|
@ -117,6 +117,7 @@ export function loadTeam(id: number): ThunkResult<void> {
|
||||
}
|
||||
|
||||
export function loadTeamMembers(): ThunkResult<void> {
|
||||
console.log('loading team members');
|
||||
return async (dispatch, getStore) => {
|
||||
const team = getStore().team.team;
|
||||
|
||||
|
@ -31,22 +31,42 @@ describe('teams reducer', () => {
|
||||
});
|
||||
|
||||
describe('team reducer', () => {
|
||||
it('should set team', () => {
|
||||
const payload = getMockTeam();
|
||||
|
||||
const action: Action = {
|
||||
type: ActionTypes.LoadTeam,
|
||||
payload,
|
||||
};
|
||||
|
||||
const result = teamReducer(initialTeamState, action);
|
||||
|
||||
expect(result.team).toEqual(payload);
|
||||
});
|
||||
|
||||
it('should set team members', () => {
|
||||
const mockTeamMember = getMockTeamMember();
|
||||
const mockTeam = getMockTeam();
|
||||
const state = {
|
||||
...initialTeamState,
|
||||
team: mockTeam,
|
||||
};
|
||||
|
||||
const action: Action = {
|
||||
type: ActionTypes.LoadTeamMembers,
|
||||
payload: [mockTeamMember],
|
||||
};
|
||||
|
||||
const result = teamReducer(state, action);
|
||||
const expectedState = { team: { ...mockTeam, members: [mockTeamMember] }, searchQuery: '' };
|
||||
const result = teamReducer(initialTeamState, action);
|
||||
|
||||
expect(result).toEqual(expectedState);
|
||||
expect(result.members).toEqual([mockTeamMember]);
|
||||
});
|
||||
|
||||
it('should set member search query', () => {
|
||||
const payload = 'member';
|
||||
|
||||
const action: Action = {
|
||||
type: ActionTypes.SetSearchMemberQuery,
|
||||
payload,
|
||||
};
|
||||
|
||||
const result = teamReducer(initialTeamState, action);
|
||||
|
||||
expect(result.searchMemberQuery).toEqual('member');
|
||||
});
|
||||
});
|
||||
|
@ -1,8 +1,13 @@
|
||||
import { Team, TeamsState, TeamState } from '../../../types';
|
||||
import { Team, TeamGroup, TeamMember, TeamsState, TeamState } from '../../../types';
|
||||
import { Action, ActionTypes } from './actions';
|
||||
|
||||
export const initialTeamsState: TeamsState = { teams: [], searchQuery: '' };
|
||||
export const initialTeamState: TeamState = { team: {} as Team, searchMemberQuery: '' };
|
||||
export const initialTeamState: TeamState = {
|
||||
team: {} as Team,
|
||||
members: [] as TeamMember[],
|
||||
groups: [] as TeamGroup[],
|
||||
searchMemberQuery: '',
|
||||
};
|
||||
|
||||
export const teamsReducer = (state = initialTeamsState, action: Action): TeamsState => {
|
||||
switch (action.type) {
|
||||
@ -21,7 +26,7 @@ export const teamReducer = (state = initialTeamState, action: Action): TeamState
|
||||
return { ...state, team: action.payload };
|
||||
|
||||
case ActionTypes.LoadTeamMembers:
|
||||
return { ...state, team: { ...state.team, members: action.payload } };
|
||||
return { ...state, members: action.payload };
|
||||
|
||||
case ActionTypes.SetSearchMemberQuery:
|
||||
return { ...state, searchMemberQuery: action.payload };
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { getTeams } from './selectors';
|
||||
import { getMultipleMockTeams } from '../__mocks__/teamMocks';
|
||||
import { TeamsState } from '../../../types';
|
||||
import { getTeam, getTeams } from './selectors';
|
||||
import { getMockTeam, getMultipleMockTeams } from '../__mocks__/teamMocks';
|
||||
import { TeamsState, TeamState } from '../../../types';
|
||||
|
||||
describe('Team selectors', () => {
|
||||
describe('Teams selectors', () => {
|
||||
describe('Get teams', () => {
|
||||
const mockTeams = getMultipleMockTeams(5);
|
||||
|
||||
@ -23,3 +23,17 @@ describe('Team selectors', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Team selectors', () => {
|
||||
describe('Get team', () => {
|
||||
const mockTeam = getMockTeam();
|
||||
|
||||
it('should return team if matching with location team', () => {
|
||||
const mockState: TeamState = { team: mockTeam, searchMemberQuery: '' };
|
||||
|
||||
const team = getTeam(mockState, '1');
|
||||
|
||||
expect(team).toEqual(mockTeam);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -2,8 +2,7 @@ export const getSearchQuery = state => state.searchQuery;
|
||||
export const getSearchMemberQuery = state => state.searchMemberQuery;
|
||||
|
||||
export const getTeam = (state, currentTeamId) => {
|
||||
if (state.team.id === currentTeamId) {
|
||||
console.log('yes');
|
||||
if (state.team.id === parseInt(currentTeamId)) {
|
||||
return state.team;
|
||||
}
|
||||
};
|
||||
@ -15,3 +14,11 @@ export const getTeams = state => {
|
||||
return regex.test(team.name);
|
||||
});
|
||||
};
|
||||
|
||||
export const getTeamMembers = state => {
|
||||
const regex = RegExp(state.searchMemberQuery, 'i');
|
||||
|
||||
return state.members.filter(member => {
|
||||
return regex.test(member.login) || regex.test(member.email);
|
||||
});
|
||||
};
|
||||
|
@ -63,9 +63,6 @@ export interface Team {
|
||||
avatarUrl: string;
|
||||
email: string;
|
||||
memberCount: number;
|
||||
search?: string;
|
||||
members?: TeamMember[];
|
||||
groups?: TeamGroup[];
|
||||
}
|
||||
|
||||
export interface TeamMember {
|
||||
@ -124,6 +121,8 @@ export interface TeamsState {
|
||||
|
||||
export interface TeamState {
|
||||
team: Team;
|
||||
members: TeamMember[];
|
||||
groups: TeamGroup[];
|
||||
searchMemberQuery: string;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user