/*
* SonarQube
* Copyright (C) 2009-2022 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
import { Helmet } from 'react-helmet-async';
import { createGroup, deleteGroup, searchUsersGroups, updateGroup } from '../../../api/user_groups';
import ListFooter from '../../../components/controls/ListFooter';
import SearchBox from '../../../components/controls/SearchBox';
import Suggestions from '../../../components/embed-docs-modal/Suggestions';
import { translate } from '../../../helpers/l10n';
import { omitNil } from '../../../helpers/request';
import { Group, Paging } from '../../../types/types';
import DeleteForm from './DeleteForm';
import Form from './Form';
import Header from './Header';
import List from './List';
interface State {
groups?: Group[];
editedGroup?: Group;
groupToBeDeleted?: Group;
loading: boolean;
paging?: Paging;
query: string;
}
export default class App extends React.PureComponent<{}, State> {
mounted = false;
state: State = { loading: true, query: '' };
componentDidMount() {
this.mounted = true;
this.fetchGroups();
}
componentWillUnmount() {
this.mounted = false;
}
makeFetchGroupsRequest = (data?: { p?: number; q?: string }) => {
this.setState({ loading: true });
return searchUsersGroups({
q: this.state.query,
...data
});
};
stopLoading = () => {
if (this.mounted) {
this.setState({ loading: false });
}
};
fetchGroups = async (data?: { p?: number; q?: string }) => {
try {
const { groups, paging } = await this.makeFetchGroupsRequest(data);
if (this.mounted) {
this.setState({ groups, loading: false, paging });
}
} catch {
this.stopLoading();
}
};
fetchMoreGroups = async () => {
const { paging: currentPaging } = this.state;
if (currentPaging && currentPaging.total > currentPaging.pageIndex * currentPaging.pageSize) {
try {
const { groups, paging } = await this.makeFetchGroupsRequest({
p: currentPaging.pageIndex + 1
});
if (this.mounted) {
this.setState(({ groups: existingGroups = [] }) => ({
groups: [...existingGroups, ...groups],
loading: false,
paging
}));
}
} catch {
this.stopLoading();
}
}
};
search = (query: string) => {
this.fetchGroups({ q: query });
this.setState({ query });
};
refresh = async () => {
const { paging, query } = this.state;
await this.fetchGroups({ q: query });
// reload all pages in order
if (paging && paging.pageIndex > 1) {
for (let p = 1; p < paging.pageIndex; p++) {
// eslint-disable-next-line no-await-in-loop
await this.fetchMoreGroups(); // This is a intentional promise chain
}
}
};
closeDeleteForm = () => {
this.setState({ groupToBeDeleted: undefined });
};
closeEditForm = () => {
this.setState({ editedGroup: undefined });
};
openDeleteForm = (group: Group) => {
this.setState({ groupToBeDeleted: group });
};
openEditForm = (group: Group) => {
this.setState({ editedGroup: group });
};
handleCreate = async (data: { description: string; name: string }) => {
await createGroup({ ...data });
await this.refresh();
};
handleDelete = async () => {
const { groupToBeDeleted } = this.state;
if (!groupToBeDeleted) {
return;
}
await deleteGroup({ name: groupToBeDeleted.name });
await this.refresh();
if (this.mounted) {
this.setState({ groupToBeDeleted: undefined });
}
};
handleEdit = async ({ name, description }: { name?: string; description: string }) => {
const { editedGroup } = this.state;
if (!editedGroup) {
return;
}
const data = {
description,
id: editedGroup.id,
// pass `name` only if it has changed, otherwise the WS fails
...omitNil({ name: name !== editedGroup.name ? name : undefined })
};
await updateGroup(data);
if (this.mounted) {
this.setState(({ groups = [] }: State) => ({
editedGroup: undefined,
groups: groups.map(group =>
group.name === editedGroup.name ? { ...group, ...data } : group
)
}));
}
};
render() {
const { editedGroup, groupToBeDeleted, groups, loading, paging, query } = this.state;
const showAnyone = 'anyone'.includes(query.toLowerCase());
return (
<>