diff options
author | Stas Vilchik <stas.vilchik@sonarsource.com> | 2018-02-06 16:11:41 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-02-06 16:11:41 +0100 |
commit | cf9eb2f6a2bc594e3007eff1494817a42a6f2d35 (patch) | |
tree | 177cb35f5bc07f3811462a95c4137fc243c7ba48 /server/sonar-web/src/main/js/apps/groups | |
parent | 3c42d5d2e6b362d389c0058e069897bf26ef65f7 (diff) | |
download | sonarqube-cf9eb2f6a2bc594e3007eff1494817a42a6f2d35.tar.gz sonarqube-cf9eb2f6a2bc594e3007eff1494817a42a6f2d35.zip |
rewrite groups app with react (#3017)
Diffstat (limited to 'server/sonar-web/src/main/js/apps/groups')
41 files changed, 1479 insertions, 924 deletions
diff --git a/server/sonar-web/src/main/js/apps/groups/components/App.tsx b/server/sonar-web/src/main/js/apps/groups/components/App.tsx new file mode 100644 index 00000000000..5aebe166132 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/groups/components/App.tsx @@ -0,0 +1,182 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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'; +import Header from './Header'; +import List from './List'; +import { searchUsersGroups, deleteGroup, updateGroup, createGroup } from '../../../api/user_groups'; +import { Group, Paging } from '../../../app/types'; +import ListFooter from '../../../components/controls/ListFooter'; +import SearchBox from '../../../components/controls/SearchBox'; +import { translate } from '../../../helpers/l10n'; + +interface Props { + organization?: { key: string }; +} + +interface State { + groups?: Group[]; + loading: boolean; + paging?: Paging; + query: string; +} + +export default class App extends React.PureComponent<Props, State> { + mounted: boolean; + state: State = { loading: true, query: '' }; + + componentDidMount() { + this.mounted = true; + this.fetchGroups(); + } + + componentWillUnmount() { + this.mounted = false; + } + + get organization() { + return this.props.organization && this.props.organization.key; + } + + makeFetchGroupsRequest = (data?: { p?: number; q?: string }) => { + this.setState({ loading: true }); + return searchUsersGroups({ + organization: this.organization, + q: this.state.query, + ...data + }); + }; + + stopLoading = () => { + if (this.mounted) { + this.setState({ loading: false }); + } + }; + + fetchGroups = (data?: { p?: number; q?: string }) => { + this.makeFetchGroupsRequest(data).then(({ groups, paging }) => { + if (this.mounted) { + this.setState({ groups, loading: false, paging }); + } + }, this.stopLoading); + }; + + fetchMoreGroups = () => { + const { paging } = this.state; + if (paging && paging.total > paging.pageIndex * paging.pageSize) { + this.makeFetchGroupsRequest({ p: paging.pageIndex + 1 }).then(({ groups, paging }) => { + if (this.mounted) { + this.setState(({ groups: existingGroups = [] }) => ({ + groups: [...existingGroups, ...groups], + loading: false, + paging + })); + } + }, this.stopLoading); + } + }; + + search = (query: string) => { + this.fetchGroups({ q: query }); + this.setState({ query }); + }; + + refresh = () => { + this.fetchGroups({ q: this.state.query }); + }; + + handleCreate = (data: { description: string; name: string }) => { + return createGroup({ ...data, organization: this.organization }).then(group => { + if (this.mounted) { + this.setState(({ groups = [] }: State) => ({ + groups: [...groups, group] + })); + } + }); + }; + + handleDelete = (name: string) => { + return deleteGroup({ name, organization: this.organization }).then(() => { + if (this.mounted) { + this.setState(({ groups = [] }: State) => ({ + groups: groups.filter(group => group.name !== name) + })); + } + }); + }; + + handleEdit = (data: { description?: string; id: number; name?: string }) => { + return updateGroup(data).then(() => { + if (this.mounted) { + this.setState(({ groups = [] }: State) => ({ + groups: groups.map(group => (group.id === data.id ? { ...group, ...data } : group)) + })); + } + }); + }; + + render() { + const { groups, loading, paging, query } = this.state; + + const showAnyone = + this.props.organization === undefined && 'anyone'.includes(query.toLowerCase()); + + return ( + <> + <Helmet title={translate('user_groups.page')} /> + <div className="page page-limited" id="groups-page"> + <Header loading={loading} onCreate={this.handleCreate} /> + + <SearchBox + className="big-spacer-bottom" + id="groups-search" + minLength={2} + onChange={this.search} + placeholder={translate('search.search_by_name')} + value={query} + /> + + {groups !== undefined && ( + <List + groups={groups} + onDelete={this.handleDelete} + onEdit={this.handleEdit} + onEditMembers={this.refresh} + organization={this.organization} + showAnyone={showAnyone} + /> + )} + + {groups !== undefined && + paging !== undefined && ( + <div id="groups-list-footer"> + <ListFooter + count={showAnyone ? groups.length + 1 : groups.length} + loadMore={this.fetchMoreGroups} + ready={!loading} + total={showAnyone ? paging.total + 1 : paging.total} + /> + </div> + )} + </div> + </> + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/groups/components/EditGroup.tsx b/server/sonar-web/src/main/js/apps/groups/components/EditGroup.tsx new file mode 100644 index 00000000000..8cf6bf7f8d2 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/groups/components/EditGroup.tsx @@ -0,0 +1,84 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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 Form from './Form'; +import { Group } from '../../../app/types'; +import { translate } from '../../../helpers/l10n'; +import { omitNil } from '../../../helpers/request'; + +interface Props { + children: (props: { onClick: () => void }) => React.ReactNode; + group: Group; + onEdit: (data: { description?: string; id: number; name?: string }) => Promise<void>; +} + +interface State { + modal: boolean; +} + +export default class EditGroup extends React.PureComponent<Props, State> { + mounted: boolean; + state: State = { modal: false }; + + componentDidMount() { + this.mounted = true; + } + + componentWillUnmount() { + this.mounted = false; + } + + handleClick = () => { + this.setState({ modal: true }); + }; + + handleClose = () => { + if (this.mounted) { + this.setState({ modal: false }); + } + }; + + handleSubmit = ({ name, description }: { name: string; description: string }) => { + const { group } = this.props; + return this.props.onEdit({ + description, + id: group.id, + // pass `name` only if it has changed, otherwise the WS fails + ...omitNil({ name: name !== group.name ? name : undefined }) + }); + }; + + render() { + return ( + <> + {this.props.children({ onClick: this.handleClick })} + {this.state.modal && ( + <Form + confirmButtonText={translate('update_verb')} + group={this.props.group} + header={translate('groups.update_group')} + onClose={this.handleClose} + onSubmit={this.handleSubmit} + /> + )} + </> + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/groups/components/EditMembers.tsx b/server/sonar-web/src/main/js/apps/groups/components/EditMembers.tsx new file mode 100644 index 00000000000..6ab1d85fd0a --- /dev/null +++ b/server/sonar-web/src/main/js/apps/groups/components/EditMembers.tsx @@ -0,0 +1,126 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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 * as escapeHtml from 'escape-html'; +import { Group } from '../../../app/types'; +import Modal from '../../../components/controls/Modal'; +import BulletListIcon from '../../../components/icons-components/BulletListIcon'; +import SelectList from '../../../components/SelectList'; +import { ButtonIcon } from '../../../components/ui/buttons'; +import { translate } from '../../../helpers/l10n'; +import { getBaseUrl } from '../../../helpers/urls'; + +interface Props { + group: Group; + onEdit: () => void; + organization: string | undefined; +} + +interface State { + modal: boolean; +} + +export default class EditMembers extends React.PureComponent<Props, State> { + container?: HTMLElement | null; + mounted: boolean; + state: State = { modal: false }; + + componentDidMount() { + this.mounted = true; + } + + componentWillUnmount() { + this.mounted = false; + } + + handleMembersClick = () => { + this.setState({ modal: true }, () => { + // defer rendering of the SelectList to make sure we have `ref` assigned + setTimeout(this.renderSelectList, 0); + }); + }; + + handleModalClose = () => { + if (this.mounted) { + this.setState({ modal: false }); + this.props.onEdit(); + } + }; + + handleCloseClick = (event: React.SyntheticEvent<HTMLElement>) => { + event.preventDefault(); + event.currentTarget.blur(); + this.handleModalClose(); + }; + + renderSelectList = () => { + if (this.container) { + const extra = { name: this.props.group.name, organization: this.props.organization }; + + /* eslint-disable no-new */ + new SelectList({ + el: this.container, + width: '100%', + readOnly: false, + focusSearch: false, + dangerouslyUnescapedHtmlFormat: (item: { login: string; name: string }) => + `${escapeHtml(item.name)}<br><span class="note">${escapeHtml(item.login)}</span>`, + queryParam: 'q', + searchUrl: getBaseUrl() + '/api/user_groups/users?ps=100&id=' + this.props.group.id, + selectUrl: getBaseUrl() + '/api/user_groups/add_user', + deselectUrl: getBaseUrl() + '/api/user_groups/remove_user', + extra, + selectParameter: 'login', + selectParameterValue: 'login', + parse: (r: any) => r.users + }); + /* eslint-enable no-new */ + } + }; + + render() { + const modalHeader = translate('users.update'); + + return ( + <> + <ButtonIcon className="button-small" onClick={this.handleMembersClick}> + <BulletListIcon /> + </ButtonIcon> + {this.state.modal && ( + <Modal contentLabel={modalHeader} onRequestClose={this.handleModalClose}> + <header className="modal-head"> + <h2>{modalHeader}</h2> + </header> + + <div className="modal-body"> + <div id="groups-users" ref={node => (this.container = node)} /> + </div> + + <footer className="modal-foot"> + <button className="button-link" onClick={this.handleCloseClick} type="reset"> + {translate('Done')} + </button> + </footer> + </Modal> + )} + </> + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/groups/components/Form.tsx b/server/sonar-web/src/main/js/apps/groups/components/Form.tsx new file mode 100644 index 00000000000..23452cef400 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/groups/components/Form.tsx @@ -0,0 +1,117 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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 { Group } from '../../../app/types'; +import DeferredSpinner from '../../../components/common/DeferredSpinner'; +import SimpleModal from '../../../components/controls/SimpleModal'; +import { translate } from '../../../helpers/l10n'; + +interface Props { + confirmButtonText: string; + group?: Group; + header: string; + onClose: () => void; + onSubmit: (data: { description: string; name: string }) => Promise<void>; +} + +interface State { + description: string; + name: string; +} + +export default class Form extends React.PureComponent<Props, State> { + constructor(props: Props) { + super(props); + this.state = { + description: (props.group && props.group.description) || '', + name: (props.group && props.group.name) || '' + }; + } + + handleSubmit = () => { + return this.props + .onSubmit({ description: this.state.description, name: this.state.name }) + .then(this.props.onClose); + }; + + handleDescriptionChange = (event: React.SyntheticEvent<HTMLTextAreaElement>) => { + this.setState({ description: event.currentTarget.value }); + }; + + handleNameChange = (event: React.SyntheticEvent<HTMLInputElement>) => { + this.setState({ name: event.currentTarget.value }); + }; + + render() { + return ( + <SimpleModal + header={this.props.header} + onClose={this.props.onClose} + onSubmit={this.handleSubmit}> + {({ onCloseClick, onFormSubmit, submitting }) => ( + <form onSubmit={onFormSubmit}> + <header className="modal-head"> + <h2>{this.props.header}</h2> + </header> + + <div className="modal-body"> + <div className="modal-field"> + <label htmlFor="create-group-name"> + {translate('name')} + <em className="mandatory">*</em> + </label> + <input + autoFocus={true} + id="create-group-name" + maxLength={255} + name="name" + onChange={this.handleNameChange} + required={true} + size={50} + type="text" + value={this.state.name} + /> + </div> + <div className="modal-field"> + <label htmlFor="create-group-description">{translate('description')}</label> + <textarea + id="create-group-description" + name="description" + onChange={this.handleDescriptionChange} + value={this.state.description} + /> + </div> + </div> + + <footer className="modal-foot"> + <DeferredSpinner className="spacer-right" loading={submitting} /> + <button disabled={submitting} type="submit"> + {this.props.confirmButtonText} + </button> + <button className="button-link" onClick={onCloseClick} type="reset"> + {translate('cancel')} + </button> + </footer> + </form> + )} + </SimpleModal> + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/groups/components/GroupsAppContainer.js b/server/sonar-web/src/main/js/apps/groups/components/GroupsAppContainer.js deleted file mode 100644 index 30411e9f270..00000000000 --- a/server/sonar-web/src/main/js/apps/groups/components/GroupsAppContainer.js +++ /dev/null @@ -1,39 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 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 React from 'react'; -import Helmet from 'react-helmet'; -import init from '../init'; -import { translate } from '../../../helpers/l10n'; -import '../../../components/controls/SearchBox.css'; - -export default class GroupsAppContainer extends React.PureComponent { - componentDidMount() { - init(this.refs.container); - } - - render() { - return ( - <div> - <Helmet title={translate('user_groups.page')} /> - <div ref="container" /> - </div> - ); - } -} diff --git a/server/sonar-web/src/main/js/apps/groups/components/Header.tsx b/server/sonar-web/src/main/js/apps/groups/components/Header.tsx new file mode 100644 index 00000000000..973ccb981dd --- /dev/null +++ b/server/sonar-web/src/main/js/apps/groups/components/Header.tsx @@ -0,0 +1,89 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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 Form from './Form'; +import DeferredSpinner from '../../../components/common/DeferredSpinner'; +import { translate } from '../../../helpers/l10n'; + +interface Props { + loading: boolean; + onCreate: (data: { description: string; name: string }) => Promise<void>; +} + +interface State { + createModal: boolean; +} + +export default class Header extends React.PureComponent<Props, State> { + mounted: boolean; + state: State = { createModal: false }; + + componentDidMount() { + this.mounted = true; + } + + componentWillUnmount() { + this.mounted = false; + } + + handleCreateClick = (event: React.SyntheticEvent<HTMLButtonElement>) => { + event.preventDefault(); + event.currentTarget.blur(); + this.setState({ createModal: true }); + }; + + handleClose = () => { + if (this.mounted) { + this.setState({ createModal: false }); + } + }; + + handleSubmit = (data: { name: string; description: string }) => { + return this.props.onCreate(data); + }; + + render() { + return ( + <> + <header className="page-header" id="groups-header"> + <h1 className="page-title">{translate('user_groups.page')}</h1> + + <DeferredSpinner loading={this.props.loading} /> + + <div className="page-actions"> + <button id="groups-create" onClick={this.handleCreateClick}> + {translate('groups.create_group')} + </button> + </div> + + <p className="page-description">{translate('user_groups.page.description')}</p> + </header> + {this.state.createModal && ( + <Form + confirmButtonText={translate('create')} + header={translate('groups.create_group')} + onClose={this.handleClose} + onSubmit={this.handleSubmit} + /> + )} + </> + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/groups/components/List.tsx b/server/sonar-web/src/main/js/apps/groups/components/List.tsx new file mode 100644 index 00000000000..c4207f141d5 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/groups/components/List.tsx @@ -0,0 +1,76 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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 { sortBy } from 'lodash'; +import { Group } from '../../../app/types'; +import ListItem from './ListItem'; +import { translate } from '../../../helpers/l10n'; + +interface Props { + groups: Group[]; + onDelete: (name: string) => Promise<void>; + onEdit: (data: { description?: string; id: number; name?: string }) => Promise<void>; + onEditMembers: () => void; + organization: string | undefined; + showAnyone: boolean; +} + +export default function List(props: Props) { + return ( + <div className="boxed-group boxed-group-inner"> + <table id="groups-list" className="data zebra zebra-hover"> + <thead> + <tr> + <th /> + <th className="nowrap">{translate('members')}</th> + <th className="nowrap">{translate('description')}</th> + <th /> + </tr> + </thead> + <tbody> + {props.showAnyone && ( + <tr className="js-anyone" key="anyone"> + <td className="width-20"> + <strong className="js-group-name">{translate('groups.anyone')}</strong> + </td> + <td className="width-10" /> + <td className="width-40" colSpan={2}> + <span className="js-group-description"> + {translate('user_groups.anyone.description')} + </span> + </td> + </tr> + )} + + {sortBy(props.groups, group => group.name.toLowerCase()).map(group => ( + <ListItem + group={group} + key={group.id} + onDelete={props.onDelete} + onEdit={props.onEdit} + onEditMembers={props.onEditMembers} + organization={props.organization} + /> + ))} + </tbody> + </table> + </div> + ); +} diff --git a/server/sonar-web/src/main/js/apps/groups/components/ListItem.tsx b/server/sonar-web/src/main/js/apps/groups/components/ListItem.tsx new file mode 100644 index 00000000000..651066f64b0 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/groups/components/ListItem.tsx @@ -0,0 +1,103 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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 EditGroup from './EditGroup'; +import EditMembers from './EditMembers'; +import { Group } from '../../../app/types'; +import ActionsDropdown, { + ActionsDropdownItem, + ActionsDropdownDivider +} from '../../../components/controls/ActionsDropdown'; +import ConfirmButton from '../../../components/controls/ConfirmButton'; +import { translate, translateWithParameters } from '../../../helpers/l10n'; + +interface Props { + group: Group; + onDelete: (name: string) => Promise<void>; + onEdit: (data: { description?: string; id: number; name?: string }) => Promise<void>; + onEditMembers: () => void; + organization: string | undefined; +} + +export default class ListItem extends React.PureComponent<Props> { + handleDelete = () => { + return this.props.onDelete(this.props.group.name); + }; + + render() { + const { group } = this.props; + + return ( + <tr data-id={group.id}> + <td className=" width-20"> + <strong className="js-group-name">{group.name}</strong> + {group.default && <span className="little-spacer-left">({translate('default')})</span>} + </td> + + <td className="width-10"> + <div className="display-flex-center"> + <span className="spacer-right">{group.membersCount}</span> + {!group.default && ( + <EditMembers + group={group} + onEdit={this.props.onEditMembers} + organization={this.props.organization} + /> + )} + </div> + </td> + + <td className="width-40"> + <span className="js-group-description">{group.description}</span> + </td> + + <td className="thin nowrap text-right"> + {!group.default && ( + <ActionsDropdown> + <EditGroup group={group} onEdit={this.props.onEdit}> + {({ onClick }) => ( + <ActionsDropdownItem className="js-group-update" onClick={onClick}> + {translate('update_details')} + </ActionsDropdownItem> + )} + </EditGroup> + <ActionsDropdownDivider /> + <ConfirmButton + confirmButtonText={translate('delete')} + isDestructive={true} + modalBody={translateWithParameters('groups.delete_group.confirmation', group.name)} + modalHeader={translate('groups.delete_group')} + onConfirm={this.handleDelete}> + {({ onClick }) => ( + <ActionsDropdownItem + className="js-group-delete" + destructive={true} + onClick={onClick}> + {translate('delete')} + </ActionsDropdownItem> + )} + </ConfirmButton> + </ActionsDropdown> + )} + </td> + </tr> + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/groups/components/__tests__/EditGroup-test.tsx b/server/sonar-web/src/main/js/apps/groups/components/__tests__/EditGroup-test.tsx new file mode 100644 index 00000000000..578b79d356d --- /dev/null +++ b/server/sonar-web/src/main/js/apps/groups/components/__tests__/EditGroup-test.tsx @@ -0,0 +1,58 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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 { shallow } from 'enzyme'; +import EditGroup from '../EditGroup'; + +it('should edit group', () => { + const group = { id: 3, name: 'Foo', membersCount: 5 }; + const onEdit = jest.fn(); + const newDescription = 'bla bla'; + let onClick: any; + + const wrapper = shallow( + <EditGroup group={group} onEdit={onEdit}> + {props => { + ({ onClick } = props); + return <button />; + }} + </EditGroup> + ); + expect(wrapper).toMatchSnapshot(); + + onClick(); + wrapper.update(); + expect(wrapper).toMatchSnapshot(); + + // change name + wrapper.find('Form').prop<Function>('onSubmit')({ name: 'Bar', description: newDescription }); + expect(onEdit).lastCalledWith({ description: newDescription, id: 3, name: 'Bar' }); + + // change description + wrapper.find('Form').prop<Function>('onSubmit')({ + name: group.name, + description: newDescription + }); + expect(onEdit).lastCalledWith({ description: newDescription, id: group.id }); + + wrapper.find('Form').prop<Function>('onClose')(); + wrapper.update(); + expect(wrapper).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/groups/list-footer-view.js b/server/sonar-web/src/main/js/apps/groups/components/__tests__/EditMembers-test.tsx index faa8fab224c..a4a9297d329 100644 --- a/server/sonar-web/src/main/js/apps/groups/list-footer-view.js +++ b/server/sonar-web/src/main/js/apps/groups/components/__tests__/EditMembers-test.tsx @@ -17,35 +17,23 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import Marionette from 'backbone.marionette'; -import Template from './templates/groups-list-footer.hbs'; +import * as React from 'react'; +import { shallow } from 'enzyme'; +import EditMembers from '../EditMembers'; +import { click } from '../../../../helpers/testUtils'; -export default Marionette.ItemView.extend({ - template: Template, +it('should edit members', () => { + const group = { id: 3, name: 'Foo', membersCount: 5 }; + const onEdit = jest.fn(); - collectionEvents: { - all: 'render' - }, + const wrapper = shallow(<EditMembers group={group} onEdit={onEdit} organization="org" />); + expect(wrapper).toMatchSnapshot(); - events: { - 'click #groups-fetch-more': 'onMoreClick' - }, + wrapper.find('ButtonIcon').prop<Function>('onClick')(); + wrapper.update(); + expect(wrapper).toMatchSnapshot(); - onMoreClick(e) { - e.preventDefault(); - this.fetchMore(); - }, - - fetchMore() { - this.collection.fetchMore(); - }, - - serializeData() { - return { - ...Marionette.ItemView.prototype.serializeData.apply(this, arguments), - total: this.collection.total, - count: this.collection.length, - more: this.collection.hasMore() - }; - } + click(wrapper.find('button[type="reset"]')); + expect(onEdit).toBeCalled(); + expect(wrapper).toMatchSnapshot(); }); diff --git a/server/sonar-web/src/main/js/apps/groups/components/__tests__/Form-test.tsx b/server/sonar-web/src/main/js/apps/groups/components/__tests__/Form-test.tsx new file mode 100644 index 00000000000..b89f7249231 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/groups/components/__tests__/Form-test.tsx @@ -0,0 +1,49 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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 { shallow } from 'enzyme'; +import Form from '../Form'; +import { change, submit, click } from '../../../../helpers/testUtils'; + +it('should render form', async () => { + const onClose = jest.fn(); + const onSubmit = jest.fn(() => Promise.resolve()); + const wrapper = shallow( + <Form + confirmButtonText="confirmButtonText" + header="header" + onClose={onClose} + onSubmit={onSubmit} + /> + ).dive(); + expect(wrapper).toMatchSnapshot(); + + change(wrapper.find('[name="name"]'), 'foo'); + change(wrapper.find('[name="description"]'), 'bar'); + submit(wrapper.find('form')); + expect(onSubmit).toBeCalledWith({ description: 'bar', name: 'foo' }); + + await new Promise(setImmediate); + expect(onClose).toBeCalled(); + + onClose.mockClear(); + click(wrapper.find('button[type="reset"]')); + expect(onClose).toBeCalled(); +}); diff --git a/server/sonar-web/src/main/js/apps/groups/form-view.js b/server/sonar-web/src/main/js/apps/groups/components/__tests__/Header-test.tsx index a9493bcac8d..a237e8bafec 100644 --- a/server/sonar-web/src/main/js/apps/groups/form-view.js +++ b/server/sonar-web/src/main/js/apps/groups/components/__tests__/Header-test.tsx @@ -17,24 +17,19 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import ModalForm from '../../components/common/modal-form'; -import Template from './templates/groups-form.hbs'; +import * as React from 'react'; +import { shallow } from 'enzyme'; +import Header from '../Header'; +import { click } from '../../../../helpers/testUtils'; -export default ModalForm.extend({ - template: Template, +it('should create new group', () => { + const onCreate = jest.fn(() => Promise.resolve()); + const wrapper = shallow(<Header loading={false} onCreate={onCreate} />); + expect(wrapper).toMatchSnapshot(); - onRender() { - ModalForm.prototype.onRender.apply(this, arguments); - this.$('[data-toggle="tooltip"]').tooltip({ container: 'body', placement: 'bottom' }); - }, + click(wrapper.find('#groups-create')); + expect(wrapper).toMatchSnapshot(); - onDestroy() { - ModalForm.prototype.onDestroy.apply(this, arguments); - this.$('[data-toggle="tooltip"]').tooltip('destroy'); - }, - - onFormSubmit() { - ModalForm.prototype.onFormSubmit.apply(this, arguments); - this.sendRequest(); - } + wrapper.find('Form').prop<Function>('onSubmit')({ name: 'foo', description: 'bar' }); + expect(onCreate).toBeCalledWith({ name: 'foo', description: 'bar' }); }); diff --git a/server/sonar-web/src/main/js/apps/groups/header-view.js b/server/sonar-web/src/main/js/apps/groups/components/__tests__/List-test.tsx index 42a317140f5..30afe06e472 100644 --- a/server/sonar-web/src/main/js/apps/groups/header-view.js +++ b/server/sonar-web/src/main/js/apps/groups/components/__tests__/List-test.tsx @@ -17,38 +17,36 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import Marionette from 'backbone.marionette'; -import CreateView from './create-view'; -import Template from './templates/groups-header.hbs'; +import * as React from 'react'; +import { shallow } from 'enzyme'; +import List from '../List'; -export default Marionette.ItemView.extend({ - template: Template, - - collectionEvents: { - request: 'showSpinner', - sync: 'hideSpinner' - }, - - events: { - 'click #groups-create': 'onCreateClick' - }, - - showSpinner() { - this.$('.spinner').removeClass('hidden'); - }, - - hideSpinner() { - this.$('.spinner').addClass('hidden'); - }, - - onCreateClick(e) { - e.preventDefault(); - this.createGroup(); - }, +it('should render', () => { + expect(shallowRender()).toMatchSnapshot(); +}); - createGroup() { - new CreateView({ - collection: this.collection - }).render(); - } +it('should not render "Anyone"', () => { + expect( + shallowRender(false) + .find('.js-anyone') + .exists() + ).toBeFalsy(); }); + +function shallowRender(showAnyone = true) { + const groups = [ + { id: 1, name: 'sonar-users', description: '', membersCount: 55, default: true }, + { id: 2, name: 'foo', description: 'foobar', membersCount: 0, default: false }, + { id: 3, name: 'bar', description: 'barbar', membersCount: 1, default: false } + ]; + return shallow( + <List + groups={groups} + onDelete={jest.fn()} + onEdit={jest.fn()} + onEditMembers={jest.fn()} + organization="org" + showAnyone={showAnyone} + /> + ); +} diff --git a/server/sonar-web/src/main/js/apps/groups/components/__tests__/ListItem-test.tsx b/server/sonar-web/src/main/js/apps/groups/components/__tests__/ListItem-test.tsx new file mode 100644 index 00000000000..28c9bb1617a --- /dev/null +++ b/server/sonar-web/src/main/js/apps/groups/components/__tests__/ListItem-test.tsx @@ -0,0 +1,54 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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 { shallow } from 'enzyme'; +import ListItem from '../ListItem'; + +it('should delete group', () => { + const group = { id: 3, name: 'Foo', membersCount: 5 }; + const onDelete = jest.fn(); + const wrapper = shallow( + <ListItem + group={group} + onDelete={onDelete} + onEdit={jest.fn()} + onEditMembers={jest.fn()} + organization="org" + /> + ); + expect(wrapper).toMatchSnapshot(); + + wrapper.find('ConfirmButton').prop<Function>('onConfirm')(); + expect(onDelete).toBeCalledWith('Foo'); +}); + +it('should render default group', () => { + const group = { default: true, id: 3, name: 'Foo', membersCount: 5 }; + const wrapper = shallow( + <ListItem + group={group} + onDelete={jest.fn()} + onEdit={jest.fn()} + onEditMembers={jest.fn()} + organization="org" + /> + ); + expect(wrapper).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/EditGroup-test.tsx.snap b/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/EditGroup-test.tsx.snap new file mode 100644 index 00000000000..8b54df832b3 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/EditGroup-test.tsx.snap @@ -0,0 +1,32 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should edit group 1`] = ` +<React.Fragment> + <button /> +</React.Fragment> +`; + +exports[`should edit group 2`] = ` +<React.Fragment> + <button /> + <Form + confirmButtonText="update_verb" + group={ + Object { + "id": 3, + "membersCount": 5, + "name": "Foo", + } + } + header="groups.update_group" + onClose={[Function]} + onSubmit={[Function]} + /> +</React.Fragment> +`; + +exports[`should edit group 3`] = ` +<React.Fragment> + <button /> +</React.Fragment> +`; diff --git a/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/EditMembers-test.tsx.snap b/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/EditMembers-test.tsx.snap new file mode 100644 index 00000000000..8c846140be8 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/EditMembers-test.tsx.snap @@ -0,0 +1,64 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should edit members 1`] = ` +<React.Fragment> + <ButtonIcon + className="button-small" + onClick={[Function]} + > + <BulletListIcon /> + </ButtonIcon> +</React.Fragment> +`; + +exports[`should edit members 2`] = ` +<React.Fragment> + <ButtonIcon + className="button-small" + onClick={[Function]} + > + <BulletListIcon /> + </ButtonIcon> + <Modal + contentLabel="users.update" + onRequestClose={[Function]} + > + <header + className="modal-head" + > + <h2> + users.update + </h2> + </header> + <div + className="modal-body" + > + <div + id="groups-users" + /> + </div> + <footer + className="modal-foot" + > + <button + className="button-link" + onClick={[Function]} + type="reset" + > + Done + </button> + </footer> + </Modal> +</React.Fragment> +`; + +exports[`should edit members 3`] = ` +<React.Fragment> + <ButtonIcon + className="button-small" + onClick={[Function]} + > + <BulletListIcon /> + </ButtonIcon> +</React.Fragment> +`; diff --git a/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/Form-test.tsx.snap b/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/Form-test.tsx.snap new file mode 100644 index 00000000000..e43777b754e --- /dev/null +++ b/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/Form-test.tsx.snap @@ -0,0 +1,86 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render form 1`] = ` +<Modal + contentLabel="header" + onRequestClose={[MockFunction]} +> + <form + onSubmit={[Function]} + > + <header + className="modal-head" + > + <h2> + header + </h2> + </header> + <div + className="modal-body" + > + <div + className="modal-field" + > + <label + htmlFor="create-group-name" + > + name + <em + className="mandatory" + > + * + </em> + </label> + <input + autoFocus={true} + id="create-group-name" + maxLength={255} + name="name" + onChange={[Function]} + required={true} + size={50} + type="text" + value="" + /> + </div> + <div + className="modal-field" + > + <label + htmlFor="create-group-description" + > + description + </label> + <textarea + id="create-group-description" + name="description" + onChange={[Function]} + value="" + /> + </div> + </div> + <footer + className="modal-foot" + > + <DeferredSpinner + className="spacer-right" + loading={false} + timeout={100} + /> + <button + disabled={false} + type="submit" + > + confirmButtonText + </button> + <button + className="button-link" + onClick={[Function]} + type="reset" + > + cancel + </button> + </footer> + </form> +</Modal> +`; diff --git a/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/Header-test.tsx.snap b/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/Header-test.tsx.snap new file mode 100644 index 00000000000..6578cf61fe8 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/Header-test.tsx.snap @@ -0,0 +1,75 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should create new group 1`] = ` +<React.Fragment> + <header + className="page-header" + id="groups-header" + > + <h1 + className="page-title" + > + user_groups.page + </h1> + <DeferredSpinner + loading={false} + timeout={100} + /> + <div + className="page-actions" + > + <button + id="groups-create" + onClick={[Function]} + > + groups.create_group + </button> + </div> + <p + className="page-description" + > + user_groups.page.description + </p> + </header> +</React.Fragment> +`; + +exports[`should create new group 2`] = ` +<React.Fragment> + <header + className="page-header" + id="groups-header" + > + <h1 + className="page-title" + > + user_groups.page + </h1> + <DeferredSpinner + loading={false} + timeout={100} + /> + <div + className="page-actions" + > + <button + id="groups-create" + onClick={[Function]} + > + groups.create_group + </button> + </div> + <p + className="page-description" + > + user_groups.page.description + </p> + </header> + <Form + confirmButtonText="create" + header="groups.create_group" + onClose={[Function]} + onSubmit={[Function]} + /> +</React.Fragment> +`; diff --git a/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/List-test.tsx.snap b/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/List-test.tsx.snap new file mode 100644 index 00000000000..a600ab849b1 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/List-test.tsx.snap @@ -0,0 +1,106 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render 1`] = ` +<div + className="boxed-group boxed-group-inner" +> + <table + className="data zebra zebra-hover" + id="groups-list" + > + <thead> + <tr> + <th /> + <th + className="nowrap" + > + members + </th> + <th + className="nowrap" + > + description + </th> + <th /> + </tr> + </thead> + <tbody> + <tr + className="js-anyone" + key="anyone" + > + <td + className="width-20" + > + <strong + className="js-group-name" + > + groups.anyone + </strong> + </td> + <td + className="width-10" + /> + <td + className="width-40" + colSpan={2} + > + <span + className="js-group-description" + > + user_groups.anyone.description + </span> + </td> + </tr> + <ListItem + group={ + Object { + "default": false, + "description": "barbar", + "id": 3, + "membersCount": 1, + "name": "bar", + } + } + key="3" + onDelete={[MockFunction]} + onEdit={[MockFunction]} + onEditMembers={[MockFunction]} + organization="org" + /> + <ListItem + group={ + Object { + "default": false, + "description": "foobar", + "id": 2, + "membersCount": 0, + "name": "foo", + } + } + key="2" + onDelete={[MockFunction]} + onEdit={[MockFunction]} + onEditMembers={[MockFunction]} + organization="org" + /> + <ListItem + group={ + Object { + "default": true, + "description": "", + "id": 1, + "membersCount": 55, + "name": "sonar-users", + } + } + key="1" + onDelete={[MockFunction]} + onEdit={[MockFunction]} + onEditMembers={[MockFunction]} + organization="org" + /> + </tbody> + </table> +</div> +`; diff --git a/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/ListItem-test.tsx.snap b/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/ListItem-test.tsx.snap new file mode 100644 index 00000000000..2146de899da --- /dev/null +++ b/server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/ListItem-test.tsx.snap @@ -0,0 +1,118 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should delete group 1`] = ` +<tr + data-id={3} +> + <td + className=" width-20" + > + <strong + className="js-group-name" + > + Foo + </strong> + </td> + <td + className="width-10" + > + <div + className="display-flex-center" + > + <span + className="spacer-right" + > + 5 + </span> + <EditMembers + group={ + Object { + "id": 3, + "membersCount": 5, + "name": "Foo", + } + } + onEdit={[MockFunction]} + organization="org" + /> + </div> + </td> + <td + className="width-40" + > + <span + className="js-group-description" + /> + </td> + <td + className="thin nowrap text-right" + > + <ActionsDropdown> + <EditGroup + group={ + Object { + "id": 3, + "membersCount": 5, + "name": "Foo", + } + } + onEdit={[MockFunction]} + /> + <ActionsDropdownDivider /> + <ConfirmButton + confirmButtonText="delete" + isDestructive={true} + modalBody="groups.delete_group.confirmation.Foo" + modalHeader="groups.delete_group" + onConfirm={[Function]} + /> + </ActionsDropdown> + </td> +</tr> +`; + +exports[`should render default group 1`] = ` +<tr + data-id={3} +> + <td + className=" width-20" + > + <strong + className="js-group-name" + > + Foo + </strong> + <span + className="little-spacer-left" + > + ( + default + ) + </span> + </td> + <td + className="width-10" + > + <div + className="display-flex-center" + > + <span + className="spacer-right" + > + 5 + </span> + </div> + </td> + <td + className="width-40" + > + <span + className="js-group-description" + /> + </td> + <td + className="thin nowrap text-right" + /> +</tr> +`; diff --git a/server/sonar-web/src/main/js/apps/groups/create-view.js b/server/sonar-web/src/main/js/apps/groups/create-view.js deleted file mode 100644 index dbd5978633b..00000000000 --- a/server/sonar-web/src/main/js/apps/groups/create-view.js +++ /dev/null @@ -1,48 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 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 Group from './group'; -import FormView from './form-view'; - -export default FormView.extend({ - sendRequest() { - const that = this; - const group = new Group({ - name: this.$('#create-group-name').val(), - description: this.$('#create-group-description').val() - }); - this.disableForm(); - return group - .save(null, { - organization: this.collection.organization, - statusCode: { - // do not show global error - 400: null - } - }) - .done(() => { - that.collection.refresh(); - that.destroy(); - }) - .fail(jqXHR => { - that.enableForm(); - that.showErrors(jqXHR.responseJSON.errors, jqXHR.responseJSON.warnings); - }); - } -}); diff --git a/server/sonar-web/src/main/js/apps/groups/delete-view.js b/server/sonar-web/src/main/js/apps/groups/delete-view.js deleted file mode 100644 index 61d1031fa1c..00000000000 --- a/server/sonar-web/src/main/js/apps/groups/delete-view.js +++ /dev/null @@ -1,57 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 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 ModalForm from '../../components/common/modal-form'; -import Template from './templates/groups-delete.hbs'; - -export default ModalForm.extend({ - template: Template, - - onFormSubmit() { - ModalForm.prototype.onFormSubmit.apply(this, arguments); - this.sendRequest(); - }, - - sendRequest() { - const that = this; - const collection = this.model.collection; - return this.model - .destroy({ - organization: collection.organization, - wait: true, - statusCode: { - // do not show global error - 400: null - } - }) - .done(() => { - collection.total--; - that.destroy(); - }) - .fail(jqXHR => { - that.showErrors(jqXHR.responseJSON.errors, jqXHR.responseJSON.warnings); - }); - }, - - showErrors(errors, warnings) { - this.$('.js-modal-text').addClass('hidden'); - this.disableForm(); - ModalForm.prototype.showErrors.call(this, errors, warnings); - } -}); diff --git a/server/sonar-web/src/main/js/apps/groups/group.js b/server/sonar-web/src/main/js/apps/groups/group.js deleted file mode 100644 index 9aa93b59deb..00000000000 --- a/server/sonar-web/src/main/js/apps/groups/group.js +++ /dev/null @@ -1,62 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 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 { defaults, pick } from 'lodash'; -import Backbone from 'backbone'; - -export default Backbone.Model.extend({ - urlRoot() { - return window.baseUrl + '/api/user_groups'; - }, - - sync(method, model, options) { - const opts = options || {}; - if (method === 'create') { - const data = pick(model.toJSON(), 'name', 'description'); - if (options.organization) { - Object.assign(data, { organization: options.organization.key }); - } - defaults(opts, { - url: this.urlRoot() + '/create', - type: 'POST', - data - }); - } - if (method === 'update') { - const data = { - ...pick(model.changed, 'name', 'description'), - id: model.id - }; - defaults(opts, { - url: this.urlRoot() + '/update', - type: 'POST', - data - }); - } - if (method === 'delete') { - const data = { id: this.id }; - defaults(opts, { - url: this.urlRoot() + '/delete', - type: 'POST', - data - }); - } - return Backbone.ajax(opts); - } -}); diff --git a/server/sonar-web/src/main/js/apps/groups/groups.js b/server/sonar-web/src/main/js/apps/groups/groups.js deleted file mode 100644 index ccdb24e2ae4..00000000000 --- a/server/sonar-web/src/main/js/apps/groups/groups.js +++ /dev/null @@ -1,65 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 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 Backbone from 'backbone'; -import Group from './group'; - -export default Backbone.Collection.extend({ - model: Group, - - initialize({ organization }) { - this.organization = organization; - }, - - url() { - return window.baseUrl + '/api/user_groups/search'; - }, - - parse(r) { - this.total = +r.paging.total; - this.p = +r.paging.pageIndex; - this.ps = +r.paging.pageSize; - return r.groups; - }, - - fetch(options) { - const data = (options && options.data) || {}; - this.q = data.q; - const finalOptions = this.organization - ? { - ...options, - data: { ...data, organization: this.organization.key } - } - : options; - return Backbone.Collection.prototype.fetch.call(this, finalOptions); - }, - - fetchMore() { - const p = this.p + 1; - return this.fetch({ add: true, remove: false, data: { p, ps: this.ps, q: this.q } }); - }, - - refresh() { - return this.fetch({ reset: true, data: { q: this.q } }); - }, - - hasMore() { - return this.total > this.p * this.ps; - } -}); diff --git a/server/sonar-web/src/main/js/apps/groups/init.js b/server/sonar-web/src/main/js/apps/groups/init.js deleted file mode 100644 index 26321f42656..00000000000 --- a/server/sonar-web/src/main/js/apps/groups/init.js +++ /dev/null @@ -1,63 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 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 Marionette from 'backbone.marionette'; -import Layout from './layout'; -import Groups from './groups'; -import HeaderView from './header-view'; -import SearchView from './search-view'; -import ListView from './list-view'; -import ListFooterView from './list-footer-view'; - -const App = new Marionette.Application(); -const init = function({ el, organization }) { - // Layout - this.layout = new Layout({ el }); - this.layout.render(); - - // Collection - this.groups = new Groups({ organization }); - - // Header View - this.headerView = new HeaderView({ collection: this.groups }); - this.layout.headerRegion.show(this.headerView); - - // Search View - this.searchView = new SearchView({ collection: this.groups }); - this.layout.searchRegion.show(this.searchView); - - // List View - this.listView = new ListView({ collection: this.groups }); - this.layout.listRegion.show(this.listView); - - // List Footer View - this.listFooterView = new ListFooterView({ collection: this.groups }); - this.layout.listFooterRegion.show(this.listFooterView); - - // Go! - this.groups.fetch(); -}; - -App.on('start', options => { - init.call(App, options); -}); - -export default function(el, organization) { - App.start({ el, organization }); -} diff --git a/server/sonar-web/src/main/js/apps/groups/layout.js b/server/sonar-web/src/main/js/apps/groups/layout.js deleted file mode 100644 index a6fe01225c2..00000000000 --- a/server/sonar-web/src/main/js/apps/groups/layout.js +++ /dev/null @@ -1,32 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 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 Marionette from 'backbone.marionette'; -import Template from './templates/groups-layout.hbs'; - -export default Marionette.LayoutView.extend({ - template: Template, - - regions: { - headerRegion: '#groups-header', - searchRegion: '#groups-search', - listRegion: '#groups-list', - listFooterRegion: '#groups-list-footer' - } -}); diff --git a/server/sonar-web/src/main/js/apps/groups/list-item-view.js b/server/sonar-web/src/main/js/apps/groups/list-item-view.js deleted file mode 100644 index 55a76e50de8..00000000000 --- a/server/sonar-web/src/main/js/apps/groups/list-item-view.js +++ /dev/null @@ -1,83 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 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 $ from 'jquery'; -import Marionette from 'backbone.marionette'; -import UpdateView from './update-view'; -import DeleteView from './delete-view'; -import UsersView from './users-view'; -import Template from './templates/groups-list-item.hbs'; - -export default Marionette.ItemView.extend({ - tagName: 'li', - className: 'panel panel-vertical', - template: Template, - - events: { - 'click .js-group-update': 'onUpdateClick', - 'click .js-group-delete': 'onDeleteClick', - 'click .js-group-users': 'onUsersClick' - }, - - onRender() { - this.$el.attr('data-id', this.model.id); - this.$('[data-toggle="tooltip"]').tooltip({ container: 'body', placement: 'bottom' }); - }, - - onDestroy() { - this.$('[data-toggle="tooltip"]').tooltip('destroy'); - }, - - onUpdateClick(e) { - e.preventDefault(); - if (!this.model.get('default')) { - this.updateGroup(); - } - }, - - onDeleteClick(e) { - e.preventDefault(); - if (!this.model.get('default')) { - this.deleteGroup(); - } - }, - - onUsersClick(e) { - e.preventDefault(); - $('.tooltip').remove(); - if (!this.model.get('default')) { - this.showUsers(); - } - }, - - updateGroup() { - new UpdateView({ - model: this.model, - collection: this.model.collection - }).render(); - }, - - deleteGroup() { - new DeleteView({ model: this.model }).render(); - }, - - showUsers() { - new UsersView({ model: this.model, organization: this.model.collection.organization }).render(); - } -}); diff --git a/server/sonar-web/src/main/js/apps/groups/list-view.js b/server/sonar-web/src/main/js/apps/groups/list-view.js deleted file mode 100644 index c5c7a99360c..00000000000 --- a/server/sonar-web/src/main/js/apps/groups/list-view.js +++ /dev/null @@ -1,53 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 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 Marionette from 'backbone.marionette'; -import ListItemView from './list-item-view'; -import Template from './templates/groups-list.hbs'; - -export default Marionette.CompositeView.extend({ - childView: ListItemView, - childViewContainer: '.js-list', - template: Template, - - collectionEvents: { - request: 'showLoading', - sync: 'hideLoading' - }, - - showLoading() { - this.$el.addClass('new-loading'); - }, - - hideLoading() { - this.$el.removeClass('new-loading'); - - const query = this.collection.q || ''; - const shouldHideAnyone = - this.collection.organization || !'anyone'.includes(query.toLowerCase()); - this.$('.js-anyone').toggleClass('hidden', shouldHideAnyone); - }, - - serializeData() { - return { - ...Marionette.CompositeView.prototype.serializeData.apply(this, arguments), - organization: this.collection.organization - }; - } -}); diff --git a/server/sonar-web/src/main/js/apps/groups/routes.ts b/server/sonar-web/src/main/js/apps/groups/routes.ts index 6222c04d6e0..88584bbe9df 100644 --- a/server/sonar-web/src/main/js/apps/groups/routes.ts +++ b/server/sonar-web/src/main/js/apps/groups/routes.ts @@ -23,10 +23,10 @@ const routes = [ { getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) { Promise.all([ - import('./components/GroupsAppContainer').then(i => i.default), + import('./components/App').then(i => i.default), import('../organizations/forSingleOrganization').then(i => i.default) - ]).then(([GroupsAppContainer, forSingleOrganization]) => - callback(null, { component: forSingleOrganization(GroupsAppContainer) }) + ]).then(([App, forSingleOrganization]) => + callback(null, { component: forSingleOrganization(App) }) ); } } diff --git a/server/sonar-web/src/main/js/apps/groups/search-view.js b/server/sonar-web/src/main/js/apps/groups/search-view.js deleted file mode 100644 index acd2338f6e9..00000000000 --- a/server/sonar-web/src/main/js/apps/groups/search-view.js +++ /dev/null @@ -1,86 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 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 { debounce } from 'lodash'; -import Marionette from 'backbone.marionette'; -import Template from './templates/groups-search.hbs'; - -export default Marionette.ItemView.extend({ - template: Template, - - ui: { - reset: '.js-reset' - }, - - events: { - 'submit #groups-search-form': 'onFormSubmit', - 'search #groups-search-query': 'initialOnKeyUp', - 'keyup #groups-search-query': 'initialOnKeyUp', - 'click .js-reset': 'onResetClick' - }, - - initialize() { - this._bufferedValue = null; - this.debouncedOnKeyUp = debounce(this.onKeyUp, 400); - }, - - onRender() { - this.delegateEvents(); - }, - - onFormSubmit(e) { - e.preventDefault(); - this.debouncedOnKeyUp(); - }, - - initialOnKeyUp() { - const q = this.getQuery(); - this.ui.reset.toggleClass('hidden', q.length === 0); - this.debouncedOnKeyUp(); - }, - - onKeyUp() { - const q = this.getQuery(); - if (q === this._bufferedValue) { - return; - } - this._bufferedValue = this.getQuery(); - if (this.searchRequest != null) { - this.searchRequest.abort(); - } - this.searchRequest = this.search(q); - }, - - getQuery() { - return this.$('#groups-search-query').val(); - }, - - search(q) { - return this.collection.fetch({ reset: true, data: { q } }); - }, - - onResetClick(e) { - e.preventDefault(); - e.currentTarget.blur(); - this.$('#groups-search-query') - .val('') - .focus(); - this.onKeyUp(); - } -}); diff --git a/server/sonar-web/src/main/js/apps/groups/templates/groups-delete.hbs b/server/sonar-web/src/main/js/apps/groups/templates/groups-delete.hbs deleted file mode 100644 index 00d92c0a983..00000000000 --- a/server/sonar-web/src/main/js/apps/groups/templates/groups-delete.hbs +++ /dev/null @@ -1,13 +0,0 @@ -<form id="delete-group-form" autocomplete="off"> - <div class="modal-head"> - <h2>{{t 'groups.delete_group'}}</h2> - </div> - <div class="modal-body"> - <div class="js-modal-messages"></div> - <div class="js-modal-text">{{tp 'groups.delete_group.confirmation' name}}</div> - </div> - <div class="modal-foot"> - <button id="delete-group-submit">{{t 'delete'}}</button> - <a href="#" class="js-modal-close" id="delete-group-cancel">{{t 'cancel'}}</a> - </div> -</form> diff --git a/server/sonar-web/src/main/js/apps/groups/templates/groups-form.hbs b/server/sonar-web/src/main/js/apps/groups/templates/groups-form.hbs deleted file mode 100644 index c31e8598cd7..00000000000 --- a/server/sonar-web/src/main/js/apps/groups/templates/groups-form.hbs +++ /dev/null @@ -1,24 +0,0 @@ -<form id="create-group-form" autocomplete="off"> - <div class="modal-head"> - <h2>{{#if id}}{{t 'groups.update_group'}}{{else}}{{t 'groups.create_group'}}{{/if}}</h2> - </div> - <div class="modal-body"> - <div class="js-modal-messages"></div> - <div class="modal-field"> - <label for="create-group-name">{{t 'name'}}<em class="mandatory">*</em></label> - {{! keep this fake field to hack browser autofill }} - <input id="create-group-name-fake" name="name-fake" type="text" class="hidden"> - <input id="create-group-name" name="name" type="text" size="50" maxlength="255" required value="{{name}}"> - </div> - <div class="modal-field"> - <label for="create-group-description">{{t 'description'}}</label> - {{! keep this fake field to hack browser autofill }} - <textarea id="create-group-description-fake" name="description-fake" class="hidden"></textarea> - <textarea id="create-group-description" name="description">{{description}}</textarea> - </div> - </div> - <div class="modal-foot"> - <button id="create-group-submit">{{#if id}}{{t 'update_verb'}}{{else}}{{t 'create'}}{{/if}}</button> - <a href="#" class="js-modal-close" id="create-group-cancel">{{t 'cancel'}}</a> - </div> -</form> diff --git a/server/sonar-web/src/main/js/apps/groups/templates/groups-header.hbs b/server/sonar-web/src/main/js/apps/groups/templates/groups-header.hbs deleted file mode 100644 index 95ba488355d..00000000000 --- a/server/sonar-web/src/main/js/apps/groups/templates/groups-header.hbs +++ /dev/null @@ -1,8 +0,0 @@ -<header class="page-header"> - <h1 class="page-title">{{t 'user_groups.page'}}</h1> - <i class="spinner hidden"></i> - <div class="page-actions"> - <button id="groups-create">{{t 'groups.create_group'}}</button> - </div> - <p class="page-description">{{t 'user_groups.page.description'}}</p> -</header> diff --git a/server/sonar-web/src/main/js/apps/groups/templates/groups-layout.hbs b/server/sonar-web/src/main/js/apps/groups/templates/groups-layout.hbs deleted file mode 100644 index 92fd355cc47..00000000000 --- a/server/sonar-web/src/main/js/apps/groups/templates/groups-layout.hbs +++ /dev/null @@ -1,6 +0,0 @@ -<div class="page page-limited"> - <div id="groups-header"></div> - <div id="groups-search"></div> - <div id="groups-list"></div> - <div id="groups-list-footer"></div> -</div> diff --git a/server/sonar-web/src/main/js/apps/groups/templates/groups-list-footer.hbs b/server/sonar-web/src/main/js/apps/groups/templates/groups-list-footer.hbs deleted file mode 100644 index 16d1bdec7f5..00000000000 --- a/server/sonar-web/src/main/js/apps/groups/templates/groups-list-footer.hbs +++ /dev/null @@ -1,6 +0,0 @@ -<footer class="spacer-top note text-center"> - {{tp 'x_of_y_shown' count total}} - {{#if more}} - <a id="groups-fetch-more" class="spacer-left" href="#">{{t 'show_more'}}</a> - {{/if}} -</footer> diff --git a/server/sonar-web/src/main/js/apps/groups/templates/groups-list-item.hbs b/server/sonar-web/src/main/js/apps/groups/templates/groups-list-item.hbs deleted file mode 100644 index 6d2600c9e41..00000000000 --- a/server/sonar-web/src/main/js/apps/groups/templates/groups-list-item.hbs +++ /dev/null @@ -1,41 +0,0 @@ -<div class="pull-right big-spacer-left nowrap"> - {{#unless default}} - <div class="dropdown"> - <button class="dropdown-toggle" data-toggle="dropdown"> - {{settingsIcon}}<i class="icon-dropdown little-spacer-left" /> - </button> - <ul class="dropdown-menu dropdown-menu-right"> - <li> - <a class="js-group-update" href="#">{{t 'update_details'}}</a> - </li> - <li class="divider" /> - <li> - <a class="js-group-delete text-danger" href="#">{{t 'delete'}}</a> - </li> - </ul> - </div> - {{/unless}} -</div> - -<div class="display-inline-block text-top width-20"> - <strong class="js-group-name">{{name}}</strong> - {{#if default}} - <span class="little-spacer-left">({{t 'default'}})</span> - {{/if}} -</div> - -<div class="display-inline-block text-top big-spacer-left width-25"> - <div class="pull-left spacer-right"> - <strong>{{t 'members'}}</strong> - </div> - <div class="overflow-hidden bordered-left"> - <span class="spacer-left spacer-right">{{membersCount}}</span> - {{#unless default}} - <a class="js-group-users icon-bullet-list" title="{{t 'users.update'}}" data-toggle="tooltip" href="#"></a> - {{/unless}} - </div> -</div> - -<div class="display-inline-block text-top big-spacer-left width-40"> - <span class="js-group-description">{{description}}</span> -</div> diff --git a/server/sonar-web/src/main/js/apps/groups/templates/groups-list.hbs b/server/sonar-web/src/main/js/apps/groups/templates/groups-list.hbs deleted file mode 100644 index ac23f3c1f65..00000000000 --- a/server/sonar-web/src/main/js/apps/groups/templates/groups-list.hbs +++ /dev/null @@ -1,19 +0,0 @@ -<div class="boxed-group boxed-group-inner"> - {{#isNull organization}} - <div class="panel panel-vertical js-anyone"> - <div class="display-inline-block text-top width-20"> - <strong class="js-group-name">{{t 'groups.anyone'}}</strong> - </div> - - <div class="display-inline-block text-top big-spacer-left width-25"> - - </div> - - <div class="display-inline-block text-top big-spacer-left width-40"> - <span class="js-group-description">{{t 'user_groups.anyone.description'}}</span> - </div> - </div> - {{/isNull}} - - <ul class="js-list"></ul> -</div> diff --git a/server/sonar-web/src/main/js/apps/groups/templates/groups-search.hbs b/server/sonar-web/src/main/js/apps/groups/templates/groups-search.hbs deleted file mode 100644 index ecf034da48e..00000000000 --- a/server/sonar-web/src/main/js/apps/groups/templates/groups-search.hbs +++ /dev/null @@ -1,15 +0,0 @@ -<div class="big-spacer-bottom"> - <form id="groups-search-form" class="search-box"> - <input id="groups-search-query" class="search-box-input" type="text" name="q" placeholder="{{t 'search.search_by_name'}}" maxlength="100"> - <svg class="search-box-magnifier" width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;"> - <g transform="matrix(0.0288462,0,0,0.0288462,2,1.07692)"> - <path d="M288,208C288,177.167 277.042,150.792 255.125,128.875C233.208,106.958 206.833,96 176,96C145.167,96 118.792,106.958 96.875,128.875C74.958,150.792 64,177.167 64,208C64,238.833 74.958,265.208 96.875,287.125C118.792,309.042 145.167,320 176,320C206.833,320 233.208,309.042 255.125,287.125C277.042,265.208 288,238.833 288,208ZM416,416C416,424.667 412.833,432.167 406.5,438.5C400.167,444.833 392.667,448 384,448C375,448 367.5,444.833 361.5,438.5L275.75,353C245.917,373.667 212.667,384 176,384C152.167,384 129.375,379.375 107.625,370.125C85.875,360.875 67.125,348.375 51.375,332.625C35.625,316.875 23.125,298.125 13.875,276.375C4.625,254.625 0,231.833 0,208C0,184.167 4.625,161.375 13.875,139.625C23.125,117.875 35.625,99.125 51.375,83.375C67.125,67.625 85.875,55.125 107.625,45.875C129.375,36.625 152.167,32 176,32C199.833,32 222.625,36.625 244.375,45.875C266.125,55.125 284.875,67.625 300.625,83.375C316.375,99.125 328.875,117.875 338.125,139.625C347.375,161.375 352,184.167 352,208C352,244.667 341.667,277.917 321,307.75L406.75,393.5C412.917,399.667 416,407.167 416,416Z" style="fill:currentColor;fill-rule:nonzero;"/> - </g> - </svg> - <button class="js-reset hidden button-tiny search-box-clear button-icon" style="color: rgb(153, 153, 153);" type="reset"> - <svg width="12" height="12" viewBox="0 0 16 16" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve"> - <path d="M14 4.242L11.758 2l-3.76 3.76L4.242 2 2 4.242l3.756 3.756L2 11.758 4.242 14l3.756-3.76 3.76 3.76L14 11.758l-3.76-3.76L14 4.242z" style="fill: currentcolor;"/> - </svg> - </button> - </form> -</div> diff --git a/server/sonar-web/src/main/js/apps/groups/templates/groups-users.hbs b/server/sonar-web/src/main/js/apps/groups/templates/groups-users.hbs deleted file mode 100644 index 7f9db589a3e..00000000000 --- a/server/sonar-web/src/main/js/apps/groups/templates/groups-users.hbs +++ /dev/null @@ -1,10 +0,0 @@ -<div class="modal-head"> - <h2>{{t 'users.update'}}</h2> -</div> -<div class="modal-body"> - <div class="js-modal-messages"></div> - <div id="groups-users"></div> -</div> -<div class="modal-foot"> - <a href="#" class="js-modal-close" id="groups-users-done">{{t 'Done'}}</a> -</div> diff --git a/server/sonar-web/src/main/js/apps/groups/update-view.js b/server/sonar-web/src/main/js/apps/groups/update-view.js deleted file mode 100644 index 76023ba0190..00000000000 --- a/server/sonar-web/src/main/js/apps/groups/update-view.js +++ /dev/null @@ -1,47 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 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 FormView from './form-view'; - -export default FormView.extend({ - sendRequest() { - const that = this; - this.model.set({ - name: this.$('#create-group-name').val(), - description: this.$('#create-group-description').val() - }); - this.disableForm(); - return this.model - .save(null, { - organization: this.collection.organization, - statusCode: { - // do not show global error - 400: null - } - }) - .done(() => { - that.collection.refresh(); - that.destroy(); - }) - .fail(jqXHR => { - that.enableForm(); - that.showErrors(jqXHR.responseJSON.errors, jqXHR.responseJSON.warnings); - }); - } -}); diff --git a/server/sonar-web/src/main/js/apps/groups/users-view.js b/server/sonar-web/src/main/js/apps/groups/users-view.js deleted file mode 100644 index 980974bb11c..00000000000 --- a/server/sonar-web/src/main/js/apps/groups/users-view.js +++ /dev/null @@ -1,68 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 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 escapeHtml from 'escape-html'; -import Modal from '../../components/common/modals'; -import SelectList from '../../components/SelectList'; -import Template from './templates/groups-users.hbs'; - -export default Modal.extend({ - template: Template, - - initialize(options) { - this.organization = options.organization; - }, - - onRender() { - Modal.prototype.onRender.apply(this, arguments); - - const extra = { - name: this.model.get('name') - }; - if (this.organization) { - extra.organization = this.organization.key; - } - - new SelectList({ - el: this.$('#groups-users'), - width: '100%', - readOnly: false, - focusSearch: false, - dangerouslyUnescapedHtmlFormat(item) { - return `${escapeHtml(item.name)}<br><span class="note">${escapeHtml(item.login)}</span>`; - }, - queryParam: 'q', - searchUrl: window.baseUrl + '/api/user_groups/users?ps=100&id=' + this.model.id, - selectUrl: window.baseUrl + '/api/user_groups/add_user', - deselectUrl: window.baseUrl + '/api/user_groups/remove_user', - extra, - selectParameter: 'login', - selectParameterValue: 'login', - parse(r) { - this.more = false; - return r.users; - } - }); - }, - - onDestroy() { - this.model.collection.refresh(); - Modal.prototype.onDestroy.apply(this, arguments); - } -}); |