);
}
+export const TableSeparator = styled.tr`
+ ${tw`sw-h-4`}
+ border-top: ${themeBorder('default')};
+`;
+
export const TableRow = styled.tr`
td,
th {
return <CellComponentStyled as={containerType} {...props} />;
}
-export function ContentCell({ children, ...props }: CellComponentProps) {
+export function ContentCell({ children, className, ...props }: CellComponentProps) {
return (
<CellComponent {...props}>
- <div className="sw-text-left sw-justify-start sw-flex sw-items-center">{children}</div>
+ <div
+ className={classNames('sw-text-left sw-justify-start sw-flex sw-items-center', className)}
+ >
+ {children}
+ </div>
</CellComponent>
);
}
'/project/quality_profiles',
'/project/webhooks',
'/admin/webhooks',
+ '/project_roles',
+ '/admin/permissions',
+ '/admin/permission_templates',
];
export default function GlobalContainer() {
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { ActionsDropdown, ItemButton, ItemLink, PopupZLevel } from 'design-system';
import { difference } from 'lodash';
import * as React from 'react';
import {
setDefaultPermissionTemplate,
updatePermissionTemplate,
} from '../../../api/permissions';
-import ActionsDropdown, { ActionsDropdownItem } from '../../../components/controls/ActionsDropdown';
import { Router, withRouter } from '../../../components/hoc/withRouter';
import QualifierIcon from '../../../components/icons/QualifierIcon';
import { translate, translateWithParameters } from '../../../helpers/l10n';
renderSetDefaultLink(qualifier: string, child: React.ReactNode) {
return (
- <ActionsDropdownItem
+ <ItemButton
className="js-set-default"
data-qualifier={qualifier}
key={qualifier}
onClick={this.setDefault(qualifier)}
>
{child}
- </ActionsDropdownItem>
+ </ItemButton>
);
}
return (
<>
<ActionsDropdown
- label={translateWithParameters('permission_templates.show_actions_for_x', t.name)}
+ id={`permission-template-actions-${t.id}`}
+ zLevel={PopupZLevel.Global}
+ toggleClassName="it__permission-actions"
+ ariaLabel={translateWithParameters('permission_templates.show_actions_for_x', t.name)}
>
- {this.renderSetDefaultsControl()}
-
- {!this.props.fromDetails && (
- <ActionsDropdownItem
- to={{ pathname: PERMISSION_TEMPLATES_PATH, search: queryToSearch({ id: t.id }) }}
- >
- {translate('edit_permissions')}
- </ActionsDropdownItem>
- )}
-
- <ActionsDropdownItem className="js-update" onClick={this.handleUpdateClick}>
- {translate('update_details')}
- </ActionsDropdownItem>
-
- {t.defaultFor.length === 0 && (
- <ActionsDropdownItem className="js-delete" destructive onClick={this.handleDeleteClick}>
- {translate('delete')}
- </ActionsDropdownItem>
- )}
+ <>
+ {this.renderSetDefaultsControl()}
+
+ {!this.props.fromDetails && (
+ <ItemLink
+ to={{ pathname: PERMISSION_TEMPLATES_PATH, search: queryToSearch({ id: t.id }) }}
+ >
+ {translate('edit_permissions')}
+ </ItemLink>
+ )}
+
+ <ItemButton className="js-update" onClick={this.handleUpdateClick}>
+ {translate('update_details')}
+ </ItemButton>
+
+ {t.defaultFor.length === 0 && (
+ <ItemButton className="js-delete" onClick={this.handleDeleteClick}>
+ {translate('delete')}
+ </ItemButton>
+ )}
+ </>
</ActionsDropdown>
{this.state.updateModal && (
{permissions}
- <td className="nowrap thin text-right text-top little-padded-left little-padded-right">
+ <td className="nowrap thin text-right little-padded-left little-padded-right">
<ActionsCell
permissionTemplate={props.template}
refresh={props.refresh}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { FlagMessage, LargeCenteredLayout, PageContentFontWrapper } from 'design-system';
import { without } from 'lodash';
import * as React from 'react';
import { Helmet } from 'react-helmet-async';
import * as api from '../../../api/permissions';
import AllHoldersList from '../../../components/permissions/AllHoldersList';
import { FilterOption } from '../../../components/permissions/SearchForm';
-import { Alert } from '../../../components/ui/Alert';
+import UseQuery from '../../../helpers/UseQuery';
import { translate } from '../../../helpers/l10n';
import {
- convertToPermissionDefinitions,
PERMISSIONS_ORDER_FOR_PROJECT_TEMPLATE,
+ convertToPermissionDefinitions,
} from '../../../helpers/permissions';
-import UseQuery from '../../../helpers/UseQuery';
import { useGithubProvisioningEnabledQuery } from '../../../queries/identity-provider';
import { Paging, PermissionGroup, PermissionTemplate, PermissionUser } from '../../../types/types';
import TemplateDetails from './TemplateDetails';
}
return (
- <div className="page page-limited">
- <Helmet defer={false} title={template.name} />
-
- <TemplateHeader
- loading={loading}
- refresh={this.props.refresh}
- template={template}
- topQualifiers={topQualifiers}
- />
- <main>
- <TemplateDetails template={template} />
- <UseQuery query={useGithubProvisioningEnabledQuery}>
- {({ data: githubProvisioningStatus }) =>
- githubProvisioningStatus ? (
- <Alert variant="warning" className="sw-w-fit">
- {translate('permission_templates.github_warning')}
- </Alert>
- ) : null
- }
- </UseQuery>
-
- <AllHoldersList
- filter={filter}
- onGrantPermissionToGroup={this.grantPermissionToGroup}
- onGrantPermissionToUser={this.grantPermissionToUser}
- groups={groups}
- groupsPaging={groupsPaging}
- loading={loading}
- onFilter={this.handleFilter}
- onLoadMore={this.onLoadMore}
- onQuery={this.handleSearch}
- query={query}
- onRevokePermissionFromGroup={this.revokePermissionFromGroup}
- onRevokePermissionFromUser={this.revokePermissionFromUser}
- users={allUsers}
- usersPaging={usersPagingWithCreator}
- permissions={permissions}
- selectedPermission={selectedPermission}
- onSelectPermission={this.handleSelectPermission}
+ <LargeCenteredLayout id="permission-template">
+ <PageContentFontWrapper className="sw-my-8 sw-body-sm">
+ <Helmet defer={false} title={template.name} />
+
+ <TemplateHeader
+ refresh={this.props.refresh}
+ template={template}
+ topQualifiers={topQualifiers}
/>
- </main>
- </div>
+ <main>
+ <TemplateDetails template={template} />
+ <UseQuery query={useGithubProvisioningEnabledQuery}>
+ {({ data: githubProvisioningStatus }) =>
+ githubProvisioningStatus ? (
+ <FlagMessage variant="warning" className="sw-w-fit">
+ {translate('permission_templates.github_warning')}
+ </FlagMessage>
+ ) : null
+ }
+ </UseQuery>
+
+ <AllHoldersList
+ filter={filter}
+ onGrantPermissionToGroup={this.grantPermissionToGroup}
+ onGrantPermissionToUser={this.grantPermissionToUser}
+ groups={groups}
+ groupsPaging={groupsPaging}
+ loading={loading}
+ onFilter={this.handleFilter}
+ onLoadMore={this.onLoadMore}
+ onQuery={this.handleSearch}
+ query={query}
+ onRevokePermissionFromGroup={this.revokePermissionFromGroup}
+ onRevokePermissionFromUser={this.revokePermissionFromUser}
+ users={allUsers}
+ usersPaging={usersPagingWithCreator}
+ permissions={permissions}
+ selectedPermission={selectedPermission}
+ onSelectPermission={this.handleSelectPermission}
+ />
+ </main>
+ </PageContentFontWrapper>
+ </LargeCenteredLayout>
);
}
}
export default function TemplateDetails({ template }: Props) {
return (
- <div className="big-spacer-bottom">
+ <div className="sw-mb-4">
{template.defaultFor.length > 0 && (
- <div className="spacer-top js-defaults">
+ <div className="sw-mt-2 js-defaults">
<Defaults template={template} />
</div>
)}
{!!template.description && (
- <div className="spacer-top js-description">{template.description}</div>
+ <div className="sw-mt-2 js-description">{template.description}</div>
)}
{!!template.projectKeyPattern && (
- <div className="spacer-top js-project-key-pattern">
+ <div className="sw-mt-2 js-project-key-pattern">
Project Key Pattern: <code>{template.projectKeyPattern}</code>
</div>
)}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { Link, Title } from 'design-system';
import * as React from 'react';
-import Link from '../../../components/common/Link';
-import Spinner from '../../../components/ui/Spinner';
import { translate } from '../../../helpers/l10n';
import { PermissionTemplate } from '../../../types/types';
import { PERMISSION_TEMPLATES_PATH } from '../utils';
import ActionsCell from './ActionsCell';
interface Props {
- loading: boolean;
refresh: () => void;
template: PermissionTemplate;
topQualifiers: string[];
}
export default function TemplateHeader(props: Props) {
- const { template, loading } = props;
+ const { template } = props;
return (
- <header className="page-header" id="project-permissions-header">
- <div className="note spacer-bottom">
- <Link to={PERMISSION_TEMPLATES_PATH}>{translate('permission_templates.page')}</Link>
+ <header className="sw-mb-2 sw-flex sw-justify-between" id="project-permissions-header">
+ <div>
+ <div className="sw-mb-2">
+ <Link to={PERMISSION_TEMPLATES_PATH}>{translate('permission_templates.page')}</Link>
+ </div>
+ <div>
+ <Title>{template.name}</Title>
+ </div>
+ <div>{translate('global_permissions.page.description')}</div>
</div>
-
- <h1 className="page-title">{template.name}</h1>
-
- <Spinner loading={loading} />
-
- <div className="pull-right">
+ <div>
<ActionsCell
fromDetails
permissionTemplate={template}
await ui.openTemplateDetails('Permission Template 1');
await ui.appLoaded();
- expect(screen.getAllByRole('row').length).toBe(12);
+ expect(screen.getAllByRole('row').length).toBe(11);
await ui.toggleFilterByPermission(Permissions.Admin);
expect(screen.getAllByRole('row').length).toBe(3);
await ui.toggleFilterByPermission(Permissions.Admin);
- expect(screen.getAllByRole('row').length).toBe(12);
+ expect(screen.getAllByRole('row').length).toBe(11);
});
});
await ui.openTemplateDetails('Permission Template 1');
await ui.appLoaded();
- expect(screen.getAllByRole('row').length).toBe(14);
+ expect(screen.getAllByRole('row').length).toBe(13);
await ui.clickLoadMore();
- expect(screen.getAllByRole('row').length).toBe(24);
+ expect(screen.getAllByRole('row').length).toBe(23);
});
it.each([ComponentQualifier.Project, ComponentQualifier.Application, ComponentQualifier.Portfolio])(
name: `permission.assign_x_to_y.projects_role.${permission}.${target}`,
}),
tableHeaderFilter: (permission: Permissions) =>
- byRole('link', { name: `projects_role.${permission}` }),
- onlyUsersBtn: byRole('button', { name: 'users.page' }),
- onlyGroupsBtn: byRole('button', { name: 'user_groups.page' }),
+ byRole('button', { name: `projects_role.${permission}` }),
+ onlyUsersBtn: byRole('radio', { name: 'users.page' }),
+ onlyGroupsBtn: byRole('radio', { name: 'user_groups.page' }),
githubWarning: byText('permission_templates.github_warning'),
- showAllBtn: byRole('button', { name: 'all' }),
+ showAllBtn: byRole('radio', { name: 'all' }),
searchInput: byRole('searchbox', { name: 'search.search_for_users_or_groups' }),
loadMoreBtn: byRole('button', { name: 'show_more' }),
createNewTemplateBtn: byRole('button', { name: 'create' }),
modal: byRole('dialog'),
cogMenuBtn: (name: string) =>
byRole('button', { name: `permission_templates.show_actions_for_x.${name}` }),
- deleteBtn: byRole('button', { name: 'delete' }),
- updateDetailsBtn: byRole('button', { name: 'update_details' }),
+ deleteBtn: byRole('menuitem', { name: 'delete' }),
+ updateDetailsBtn: byRole('menuitem', { name: 'update_details' }),
setDefaultBtn: (qualifier: ComponentQualifier) =>
- byRole('button', {
+ byRole('menuitem', {
name:
qualifier === ComponentQualifier.Project
? 'permission_templates.set_default'
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { Title } from 'design-system';
import * as React from 'react';
import { translate } from '../../../../helpers/l10n';
-interface Props {
- loading?: boolean;
-}
-
-export default class PageHeader extends React.PureComponent<Props> {
- render() {
- return (
- <header className="page-header">
- <h1 className="page-title">{translate('global_permissions.page')}</h1>
-
- {this.props.loading && <i className="spinner" />}
-
- <div className="page-description">{translate('global_permissions.page.description')}</div>
- </header>
- );
- }
+export default function PageHeader() {
+ return (
+ <header className="sw-mb-2 sw-flex sw-flex-col sw-justify-between sw-mb-4">
+ <div>
+ <Title>{translate('global_permissions.page')}</Title>
+ </div>
+ <div>{translate('global_permissions.page.description')}</div>
+ </header>
+ );
}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { LargeCenteredLayout, PageContentFontWrapper } from 'design-system';
import { without } from 'lodash';
import * as React from 'react';
import { Helmet } from 'react-helmet-async';
import { FilterOption } from '../../../../components/permissions/SearchForm';
import { translate } from '../../../../helpers/l10n';
import {
+ PERMISSIONS_ORDER_GLOBAL,
convertToPermissionDefinitions,
filterPermissions,
- PERMISSIONS_ORDER_GLOBAL,
} from '../../../../helpers/permissions';
import { ComponentQualifier } from '../../../../types/component';
import { Paging, PermissionGroup, PermissionUser } from '../../../../types/types';
'global_permissions',
);
return (
- <main className="page page-limited">
- <Suggestions suggestions="global_permissions" />
- <Helmet defer={false} title={translate('global_permissions.permission')} />
- <PageHeader loading={loading} />
- <AllHoldersList
- permissions={permissions}
- filter={filter}
- onGrantPermissionToGroup={this.handleGrantPermissionToGroup}
- onGrantPermissionToUser={this.handleGrantPermissionToUser}
- groups={groups}
- groupsPaging={groupsPaging}
- loading={loading}
- onFilter={this.handleFilter}
- onLoadMore={this.handleLoadMore}
- onQuery={this.handleSearch}
- query={query}
- onRevokePermissionFromGroup={this.handleRevokePermissionFromGroup}
- onRevokePermissionFromUser={this.handleRevokePermissionFromUser}
- users={users}
- usersPaging={usersPaging}
- />
- </main>
+ <LargeCenteredLayout id="project-permissions-page">
+ <PageContentFontWrapper className="sw-my-8 sw-body-sm">
+ <Suggestions suggestions="global_permissions" />
+ <Helmet defer={false} title={translate('global_permissions.permission')} />
+ <PageHeader />
+ <AllHoldersList
+ permissions={permissions}
+ filter={filter}
+ onGrantPermissionToGroup={this.handleGrantPermissionToGroup}
+ onGrantPermissionToUser={this.handleGrantPermissionToUser}
+ groups={groups}
+ groupsPaging={groupsPaging}
+ loading={loading}
+ onFilter={this.handleFilter}
+ onLoadMore={this.handleLoadMore}
+ onQuery={this.handleSearch}
+ query={query}
+ onRevokePermissionFromGroup={this.handleRevokePermissionFromGroup}
+ onRevokePermissionFromUser={this.handleRevokePermissionFromUser}
+ users={users}
+ usersPaging={usersPaging}
+ />
+ </PageContentFontWrapper>
+ </LargeCenteredLayout>
);
}
}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import {
+ ButtonPrimary,
+ FlagMessage,
+ FormField,
+ InputSelect,
+ LabelValueSelectOption,
+ Modal,
+} from 'design-system';
import * as React from 'react';
import { applyTemplateToProject, getPermissionTemplates } from '../../../../api/permissions';
-import Select from '../../../../components/controls/Select';
-import SimpleModal from '../../../../components/controls/SimpleModal';
-import { ResetButtonLink, SubmitButton } from '../../../../components/controls/buttons';
-import { Alert } from '../../../../components/ui/Alert';
-import MandatoryFieldMarker from '../../../../components/ui/MandatoryFieldMarker';
import MandatoryFieldsExplanation from '../../../../components/ui/MandatoryFieldsExplanation';
-import Spinner from '../../../../components/ui/Spinner';
import { translate, translateWithParameters } from '../../../../helpers/l10n';
import { PermissionTemplate } from '../../../../types/types';
);
};
- handleSubmit = () => {
+ handleSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => {
+ event.preventDefault();
if (this.state.permissionTemplate) {
+ this.setState({ loading: true });
return applyTemplateToProject({
projectKey: this.props.project.key,
templateId: this.state.permissionTemplate,
if (this.props.onApply) {
this.props.onApply();
}
- this.setState({ done: true });
+ this.setState({ done: true, loading: false });
}
});
}
};
- handlePermissionTemplateChange = ({ value }: { value: string }) => {
+ handlePermissionTemplateChange = ({ value }: LabelValueSelectOption<string>) => {
this.setState({ permissionTemplate: value });
};
}))
: [];
+ const FORM_ID = 'project-permissions-apply-template-form';
+
return (
- <SimpleModal
- header={header}
+ <Modal
+ isOverflowVisible
+ headerTitle={header}
onClose={this.props.onClose}
- onSubmit={this.handleSubmit}
- size="small"
- >
- {({ onCloseClick, onFormSubmit, submitting }) => (
- <form id="project-permissions-apply-template-form" onSubmit={onFormSubmit}>
- <header className="modal-head">
- <h2>{header}</h2>
- </header>
-
- <div className="modal-body">
+ primaryButton={
+ !this.state.done && (
+ <ButtonPrimary
+ disabled={this.state.loading || !this.state.permissionTemplate}
+ type="submit"
+ form={FORM_ID}
+ >
+ {translate('apply')}
+ </ButtonPrimary>
+ )
+ }
+ secondaryButtonLabel={translate(this.state.done ? 'close' : 'cancel')}
+ body={
+ <form id={FORM_ID} onSubmit={this.handleSubmit}>
+ <div>
{this.state.done && (
- <Alert variant="success">{translate('projects_role.apply_template.success')}</Alert>
+ <FlagMessage variant="success">
+ {translate('projects_role.apply_template.success')}
+ </FlagMessage>
)}
- {this.state.loading && <i className="spinner" />}
-
{!this.state.done && !this.state.loading && (
<>
- <MandatoryFieldsExplanation className="modal-field" />
- <div className="modal-field">
- <label htmlFor="project-permissions-template-input">
- {translate('template')}
- <MandatoryFieldMarker />
- </label>
+ <MandatoryFieldsExplanation className="sw-mb-4" />
+ <FormField
+ label={translate('template')}
+ required
+ htmlFor="project-permissions-template-input"
+ >
{this.state.permissionTemplates && (
- <Select
+ <InputSelect
+ size="full"
id="project-permissions-template"
- className="Select"
inputId="project-permissions-template-input"
onChange={this.handlePermissionTemplateChange}
options={options}
value={options.filter((o) => o.value === this.state.permissionTemplate)}
/>
)}
- </div>
+ </FormField>
</>
)}
</div>
-
- <footer className="modal-foot">
- <Spinner className="spacer-right" loading={submitting} />
- {!this.state.done && (
- <SubmitButton disabled={submitting || !this.state.permissionTemplate}>
- {translate('apply')}
- </SubmitButton>
- )}
- <ResetButtonLink onClick={onCloseClick}>
- {translate(this.state.done ? 'close' : 'cancel')}
- </ResetButtonLink>
- </footer>
</form>
- )}
- </SimpleModal>
+ }
+ />
);
}
}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { ButtonPrimary, FlagMessage, Title } from 'design-system';
import * as React from 'react';
import GitHubSynchronisationWarning from '../../../../app/components/GitHubSynchronisationWarning';
-import { Button } from '../../../../components/controls/buttons';
-import { Alert } from '../../../../components/ui/Alert';
-import Spinner from '../../../../components/ui/Spinner';
import { translate } from '../../../../helpers/l10n';
import { getBaseUrl } from '../../../../helpers/system';
import { useGithubProvisioningEnabledQuery } from '../../../../queries/identity-provider';
component: Component;
isGitHubProject?: boolean;
loadHolders: () => void;
- loading: boolean;
}
export default function PageHeader(props: Props) {
const [applyTemplateModal, setApplyTemplateModal] = React.useState(false);
const { data: githubProvisioningStatus } = useGithubProvisioningEnabledQuery();
- const { component, isGitHubProject, loading } = props;
+ const { component, isGitHubProject } = props;
const { configuration } = component;
const provisionedByGitHub = isGitHubProject && !!githubProvisioningStatus;
const canApplyPermissionTemplate =
: undefined;
return (
- <header className="page-header">
- <h1 className="page-title">
- {translate('permissions.page')}
- {provisionedByGitHub && (
- <img
- alt="github"
- className="spacer-left spacer-right"
- aria-label={translate('project_permission.github_managed')}
- height={16}
- src={`${getBaseUrl()}/images/alm/github.svg`}
- />
- )}
- </h1>
-
- <Spinner className="spacer-left" loading={loading} />
+ <header className="sw-mb-2 sw-flex sw-items-center sw-justify-between">
+ <div>
+ <Title>
+ {translate('permissions.page')}
+ {provisionedByGitHub && (
+ <img
+ alt="github"
+ className="sw-mx-2"
+ aria-label={translate('project_permission.github_managed')}
+ height={16}
+ src={`${getBaseUrl()}/images/alm/github.svg`}
+ />
+ )}
+ </Title>
+ <div>
+ <p>{description}</p>
+ {visibilityDescription && <p>{visibilityDescription}</p>}
+ {provisionedByGitHub && (
+ <>
+ <p>{translate('roles.page.description.github')}</p>
+ <div className="sw-mt-2">
+ <GitHubSynchronisationWarning short />
+ </div>
+ </>
+ )}
+ {githubProvisioningStatus && !isGitHubProject && (
+ <FlagMessage variant="warning" className="sw-mt-2">
+ {translate('project_permission.local_project_with_github_provisioning')}
+ </FlagMessage>
+ )}
+ </div>
+ </div>
{canApplyPermissionTemplate && (
- <div className="page-actions">
- <Button className="js-apply-template" onClick={handleApplyTemplate}>
+ <div>
+ <ButtonPrimary className="js-apply-template" onClick={handleApplyTemplate}>
{translate('projects_role.apply_template')}
- </Button>
+ </ButtonPrimary>
{applyTemplateModal && (
<ApplyTemplate
)}
</div>
)}
-
- <div className="page-description">
- <p>{description}</p>
- {visibilityDescription && <p>{visibilityDescription}</p>}
- {provisionedByGitHub && (
- <>
- <p>{translate('roles.page.description.github')}</p>
- <div className="sw-mt-2">
- <GitHubSynchronisationWarning short />
- </div>
- </>
- )}
- {githubProvisioningStatus && !isGitHubProject && (
- <Alert variant="warning" className="sw-mt-2">
- {translate('project_permission.local_project_with_github_provisioning')}
- </Alert>
- )}
- </div>
</header>
);
}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { LargeCenteredLayout, PageContentFontWrapper } from 'design-system';
import { noop, without } from 'lodash';
import * as React from 'react';
import { Helmet } from 'react-helmet-async';
const permissions = convertToPermissionDefinitions(order, 'projects_role');
return (
- <main className="page page-limited" id="project-permissions-page">
- <Helmet defer={false} title={translate('permissions.page')} />
-
- <UseQuery query={useIsGitHubProjectQuery} args={[component.key]}>
- {({ data: isGitHubProject }) => (
- <>
- <PageHeader
- component={component}
- isGitHubProject={isGitHubProject}
- loadHolders={this.loadHolders}
- loading={loading}
- />
- <div>
- <UseQuery query={useGithubProvisioningEnabledQuery}>
- {({ data: githubProvisioningStatus, isFetching }) => (
- <VisibilitySelector
- canTurnToPrivate={canTurnToPrivate}
- className="sw-flex big-spacer-top big-spacer-bottom"
- onChange={this.handleVisibilityChange}
- loading={loading || isFetching}
- disabled={isGitHubProject && !!githubProvisioningStatus}
- visibility={component.visibility}
+ <LargeCenteredLayout id="project-permissions-page">
+ <PageContentFontWrapper className="sw-my-8 sw-body-sm">
+ <Helmet defer={false} title={translate('permissions.page')} />
+
+ <UseQuery query={useIsGitHubProjectQuery} args={[component.key]}>
+ {({ data: isGitHubProject }) => (
+ <>
+ <PageHeader
+ component={component}
+ isGitHubProject={isGitHubProject}
+ loadHolders={this.loadHolders}
+ />
+ <div>
+ <UseQuery query={useGithubProvisioningEnabledQuery}>
+ {({ data: githubProvisioningStatus, isFetching }) => (
+ <VisibilitySelector
+ canTurnToPrivate={canTurnToPrivate}
+ className="sw-flex sw-my-4"
+ onChange={this.handleVisibilityChange}
+ loading={loading || isFetching}
+ disabled={isGitHubProject && !!githubProvisioningStatus}
+ visibility={component.visibility}
+ />
+ )}
+ </UseQuery>
+
+ {disclaimer && (
+ <PublicProjectDisclaimer
+ component={component}
+ onClose={this.handleCloseDisclaimer}
+ onConfirm={this.handleTurnProjectToPublic}
/>
)}
- </UseQuery>
-
- {disclaimer && (
- <PublicProjectDisclaimer
- component={component}
- onClose={this.handleCloseDisclaimer}
- onConfirm={this.handleTurnProjectToPublic}
- />
- )}
- </div>
- </>
- )}
- </UseQuery>
-
- <AllHoldersList
- filter={filter}
- onGrantPermissionToGroup={this.handleGrantPermissionToGroup}
- onGrantPermissionToUser={this.handleGrantPermissionToUser}
- groups={groups}
- groupsPaging={groupsPaging}
- onFilter={this.handleFilterChange}
- onLoadMore={this.handleLoadMore}
- onSelectPermission={this.handlePermissionSelect}
- onQuery={this.handleQueryChange}
- query={query}
- onRevokePermissionFromGroup={this.handleRevokePermissionFromGroup}
- onRevokePermissionFromUser={this.handleRevokePermissionFromUser}
- selectedPermission={selectedPermission}
- users={users}
- usersPaging={usersPaging}
- permissions={permissions}
- />
- </main>
+ </div>
+ </>
+ )}
+ </UseQuery>
+
+ <AllHoldersList
+ loading={loading}
+ filter={filter}
+ onGrantPermissionToGroup={this.handleGrantPermissionToGroup}
+ onGrantPermissionToUser={this.handleGrantPermissionToUser}
+ groups={groups}
+ groupsPaging={groupsPaging}
+ onFilter={this.handleFilterChange}
+ onLoadMore={this.handleLoadMore}
+ onSelectPermission={this.handlePermissionSelect}
+ onQuery={this.handleQueryChange}
+ query={query}
+ onRevokePermissionFromGroup={this.handleRevokePermissionFromGroup}
+ onRevokePermissionFromUser={this.handleRevokePermissionFromUser}
+ selectedPermission={selectedPermission}
+ users={users}
+ usersPaging={usersPaging}
+ permissions={permissions}
+ />
+ </PageContentFontWrapper>
+ </LargeCenteredLayout>
);
}
}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { FlagMessage } from 'design-system/lib';
import * as React from 'react';
import ConfirmModal from '../../../../components/controls/ConfirmModal';
-import { Alert } from '../../../../components/ui/Alert';
import { translate, translateWithParameters } from '../../../../helpers/l10n';
interface Props {
onClose={onClose}
onConfirm={onConfirm}
>
- <Alert variant="warning">
+ <FlagMessage className="sw-mb-4" variant="warning">
{translate('projects_role.are_you_sure_to_turn_project_to_public.warning', qualifier)}
- </Alert>
+ </FlagMessage>
<p>{translate('projects_role.are_you_sure_to_turn_project_to_public', qualifier)}</p>
</ConfirmModal>
);
renderPermissionsProjectApp();
await ui.appLoaded();
- expect(screen.getAllByRole('row').length).toBe(11);
+ expect(screen.getAllByRole('row').length).toBe(10);
await ui.toggleFilterByPermission(Permissions.Admin);
expect(screen.getAllByRole('row').length).toBe(3);
await ui.toggleFilterByPermission(Permissions.Admin);
- expect(screen.getAllByRole('row').length).toBe(11);
+ expect(screen.getAllByRole('row').length).toBe(10);
});
});
expect(ui.githubExplanations.get()).toBeInTheDocument();
expect(ui.projectPermissionCheckbox('John', Permissions.Admin).get()).toBeChecked();
- expect(ui.projectPermissionCheckbox('John', Permissions.Admin).get()).toHaveAttribute(
- 'aria-disabled',
- 'true',
- );
+ expect(ui.projectPermissionCheckbox('John', Permissions.Admin).get()).toBeDisabled();
expect(ui.projectPermissionCheckbox('Alexa', Permissions.IssueAdmin).get()).toBeChecked();
- expect(ui.projectPermissionCheckbox('Alexa', Permissions.IssueAdmin).get()).toHaveAttribute(
- 'aria-disabled',
- 'false',
- );
+ expect(ui.projectPermissionCheckbox('Alexa', Permissions.IssueAdmin).get()).toBeEnabled();
await ui.toggleProjectPermission('Alexa', Permissions.IssueAdmin);
expect(ui.confirmRemovePermissionDialog.get()).toBeInTheDocument();
expect(ui.confirmRemovePermissionDialog.get()).toHaveTextContent(
expect(ui.projectPermissionCheckbox('Alexa', Permissions.IssueAdmin).get()).not.toBeChecked();
expect(ui.projectPermissionCheckbox('sonar-users', Permissions.Browse).get()).toBeChecked();
- expect(ui.projectPermissionCheckbox('sonar-users', Permissions.Browse).get()).toHaveAttribute(
- 'aria-disabled',
- 'false',
- );
+ expect(ui.projectPermissionCheckbox('sonar-users', Permissions.Browse).get()).toBeEnabled();
await ui.toggleProjectPermission('sonar-users', Permissions.Browse);
expect(ui.confirmRemovePermissionDialog.get()).toBeInTheDocument();
expect(ui.confirmRemovePermissionDialog.get()).toHaveTextContent(
expect(ui.projectPermissionCheckbox('sonar-users', Permissions.Browse).get()).not.toBeChecked();
expect(ui.projectPermissionCheckbox('sonar-admins', Permissions.Admin).get()).toBeChecked();
expect(ui.projectPermissionCheckbox('sonar-admins', Permissions.Admin).get()).toHaveAttribute(
- 'aria-disabled',
- 'true',
+ 'disabled',
);
const johnRow = screen.getAllByRole('row')[4];
expect(
screen
.getAllByRole('checkbox', { checked: false })
- .every((item) => item.getAttribute('aria-disabled') === 'true'),
+ .every((item) => item.getAttributeNames().includes('disabled')),
).toBe(true);
});
// no restrictions
expect(
- screen.getAllByRole('checkbox').every((item) => item.getAttribute('aria-disabled') !== 'true'),
- ).toBe(true);
+ screen.getAllByRole('checkbox').every((item) => item.getAttributeNames().includes('disabled')),
+ ).toBe(false);
});
it('should allow to change permissions for non-GH Project', async () => {
// no restrictions
expect(
- screen.getAllByRole('checkbox').every((item) => item.getAttribute('aria-disabled') !== 'true'),
- ).toBe(true);
+ screen.getAllByRole('checkbox').every((item) => item.getAttributeNames().includes('disabled')),
+ ).toBe(false);
});
function renderPermissionsProjectApp(
templateSuccessfullyApplied: byText('projects_role.apply_template.success'),
confirmApplyTemplateBtn: byRole('button', { name: 'apply' }),
tableHeaderFilter: (permission: Permissions) =>
- byRole('link', { name: `projects_role.${permission}` }),
- onlyUsersBtn: byRole('button', { name: 'users.page' }),
- onlyGroupsBtn: byRole('button', { name: 'user_groups.page' }),
- showAllBtn: byRole('button', { name: 'all' }),
+ byRole('button', { name: `projects_role.${permission}` }),
+ onlyUsersBtn: byRole('radio', { name: 'users.page' }),
+ onlyGroupsBtn: byRole('radio', { name: 'user_groups.page' }),
+ showAllBtn: byRole('radio', { name: 'all' }),
searchInput: byRole('searchbox', { name: 'search.search_for_users_or_groups' }),
loadMoreBtn: byRole('button', { name: 'show_more' }),
};
applyTemplateDialog: byRole('dialog', {
name: 'projects_role.apply_template_to_x.Project 1',
}),
- selectTemplate: byRole('combobox', { name: 'template field_required' }),
+ selectTemplate: (required: string) => byRole('combobox', { name: `template ${required}` }),
deleteDialog: byRole('dialog', { name: 'qualifiers.delete.TRK' }),
.get(),
).toBeInTheDocument();
await selectEvent.select(
- ui.bulkApplyDialog.by(ui.selectTemplate).get(),
+ ui.bulkApplyDialog.by(ui.selectTemplate('field_required')).get(),
'Permission Template 2',
);
await user.click(ui.bulkApplyDialog.by(ui.apply).get());
expect(ui.applyTemplateDialog.get()).toBeInTheDocument();
await selectEvent.select(
- ui.applyTemplateDialog.by(ui.selectTemplate).get(),
+ ui.applyTemplateDialog.by(ui.selectTemplate('required')).get(),
'Permission Template 2',
);
await user.click(ui.applyTemplateDialog.by(ui.apply).get());
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { BasicSeparator, Spinner } from 'design-system';
import * as React from 'react';
import {
Paging,
};
render() {
- const { filter, query, groups, users, permissions, selectedPermission, loading } = this.props;
+ const {
+ filter,
+ query,
+ groups,
+ users,
+ permissions,
+ selectedPermission,
+ loading = false,
+ } = this.props;
const { count, total } = this.getPaging();
return (
<>
+ <div>
+ <div className="sw-flex sw-justify-between">
+ <SearchForm
+ filter={filter}
+ onFilter={this.props.onFilter}
+ onSearch={this.props.onQuery}
+ query={query}
+ />
+ <Spinner loading={loading} />
+ </div>
+ <BasicSeparator className="sw-mt-4" />
+ </div>
<HoldersList
loading={loading}
filter={filter}
query={query}
selectedPermission={selectedPermission}
users={users}
- >
- <SearchForm
- filter={filter}
- onFilter={this.props.onFilter}
- onSearch={this.props.onQuery}
- query={query}
- />
- </HoldersList>
- <ListFooter count={count} loadMore={this.props.onLoadMore} total={total} />
+ />
+ <ListFooter count={count} loadMore={this.props.onLoadMore} total={total} useMIUIButtons />
</>
);
}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { Badge, ContentCell, TableRowInteractive } from 'design-system';
import * as React from 'react';
import { translate } from '../../helpers/l10n';
import { isPermissionDefinitionGroup } from '../../helpers/permissions';
removeOnly,
});
+ const description =
+ group.name === ANYONE ? translate('user_groups.anyone.description') : group.description;
+
return (
- <tr>
- <td className="nowrap text-middle">
- <div className="display-flex-center">
- <GroupIcon className="big-spacer-right" />
- <div className="max-width-100">
+ <TableRowInteractive>
+ <ContentCell>
+ <div className="sw-flex sw-items-center">
+ <GroupIcon className="sw-mr-4" />
+ <div className="sw-max-w-abs-800">
<div className="sw-flex sw-w-fit sw-max-w-full">
- <div className="sw-flex-1 text-ellipsis">
+ <div className="sw-flex-1 sw-text-ellipsis sw-whitespace-nowrap sw-overflow-hidden sw-min-w-0">
<strong>{group.name}</strong>
</div>
{isGitHubProject && group.managed && (
<img
alt="github"
- className="spacer-left spacer-right"
+ className="sw-my-2"
aria-label={translate('project_permission.github_managed')}
height={16}
src={`${getBaseUrl()}/images/alm/github.svg`}
/>
)}
{group.name === ANYONE && (
- <span className="spacer-left badge badge-error">{translate('deprecated')}</span>
+ <Badge className="sw-ml-2" variant="deleted">
+ {translate('deprecated')}
+ </Badge>
)}
</div>
-
- <div className="little-spacer-top" style={{ whiteSpace: 'normal' }}>
- {group.name === ANYONE
- ? translate('user_groups.anyone.description')
- : group.description}
- </div>
+ {description && <div className="sw-mt-2 sw-whitespace-normal">{description}</div>}
</div>
</div>
- </td>
+ </ContentCell>
{permissions.map((permission) => {
const isPermissionGroup = isPermissionDefinitionGroup(permission);
const permissionKey = isPermissionGroup ? permission.category : permission.key;
onCheck={handleCheck}
permission={permission}
permissionItem={group}
+ prefixID={group.name}
selectedPermission={selectedPermission}
/>
);
})}
{modal}
- </tr>
+ </TableRowInteractive>
);
}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { ContentCell, Table, TableRow, TableSeparator } from 'design-system';
import { partition } from 'lodash';
import * as React from 'react';
import UseQuery from '../../helpers/UseQuery';
}
render() {
- const { permissions, users, groups, loading, children, selectedPermission } = this.props;
+ const { permissions, users, groups, loading, selectedPermission } = this.props;
const items = [...groups, ...users];
const [itemWithPermissions, itemWithoutPermissions] = partition(items, (item) =>
this.getItemInitialPermissionsCount(item),
);
+ const HEADER_COLUMNS = permissions.length + 1;
+
+ const tableHeader = (
+ <TableRow>
+ <ContentCell />
+ {permissions.map((permission) => (
+ <PermissionHeader
+ key={isPermissionDefinitionGroup(permission) ? permission.category : permission.key}
+ onSelectPermission={this.props.onSelectPermission}
+ permission={permission}
+ selectedPermission={selectedPermission}
+ />
+ ))}
+ </TableRow>
+ );
+
return (
- <div className="boxed-group boxed-group-inner">
- <table className="data zebra permissions-table">
- <thead>
- <tr>
- <td className="nowrap bordered-bottom">{children}</td>
- {permissions.map((permission) => (
- <PermissionHeader
- key={
- isPermissionDefinitionGroup(permission) ? permission.category : permission.key
- }
- onSelectPermission={this.props.onSelectPermission}
- permission={permission}
- selectedPermission={selectedPermission}
- />
- ))}
- </tr>
- </thead>
- <tbody>
- {items.length === 0 && !loading && this.renderEmpty()}
- {itemWithPermissions.map((item) => this.renderItem(item, permissions))}
- {itemWithPermissions.length > 0 && itemWithoutPermissions.length > 0 && (
- <>
- <tr>
- <td className="divider" colSpan={20} />
- </tr>
- <tr />
- {/* Keep correct zebra colors in the table */}
- </>
- )}
- {itemWithoutPermissions.map((item) => this.renderItem(item, permissions))}
- </tbody>
- </table>
+ <div>
+ <Table
+ columnWidths={[500, ...permissions.map(() => 100)]}
+ className="it__permission-list"
+ noHeaderTopBorder
+ columnCount={HEADER_COLUMNS}
+ header={tableHeader}
+ >
+ {items.length === 0 && !loading && this.renderEmpty()}
+ {itemWithPermissions.map((item) => this.renderItem(item, permissions))}
+ {itemWithPermissions.length > 0 && itemWithoutPermissions.length > 0 && (
+ <TableSeparator />
+ )}
+ {itemWithoutPermissions.map((item) => this.renderItem(item, permissions))}
+ </Table>
</div>
);
}
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import classNames from 'classnames';
+import { Checkbox, CheckboxCell } from 'design-system';
import * as React from 'react';
import { translateWithParameters } from '../../helpers/l10n';
import { isPermissionDefinitionGroup } from '../../helpers/permissions';
PermissionGroup,
PermissionUser,
} from '../../types/types';
-import Checkbox from '../controls/Checkbox';
export interface PermissionCellProps {
disabled?: boolean;
permission: PermissionDefinition | PermissionDefinitionGroup;
permissionItem: PermissionGroup | PermissionUser;
selectedPermission?: string;
+ prefixID: string;
}
export default function PermissionCell(props: PermissionCellProps) {
- const { disabled, loading, onCheck, permission, permissionItem, selectedPermission, removeOnly } =
- props;
+ const {
+ disabled,
+ loading,
+ onCheck,
+ permission,
+ permissionItem,
+ selectedPermission,
+ removeOnly,
+ prefixID,
+ } = props;
if (isPermissionDefinitionGroup(permission)) {
return (
- <td className="text-middle">
- {permission.permissions.map((permissionDefinition) => {
- const isChecked = permissionItem.permissions.includes(permissionDefinition.key);
- const isDisabled = disabled || loading.includes(permissionDefinition.key);
+ <CheckboxCell>
+ <div className="sw-flex sw-flex-col sw-text-left">
+ {permission.permissions.map((permissionDefinition) => {
+ const isChecked = permissionItem.permissions.includes(permissionDefinition.key);
+ const isDisabled = disabled || loading.includes(permissionDefinition.key);
- return (
- <div key={permissionDefinition.key}>
- <Checkbox
- checked={isChecked}
- disabled={isDisabled || (!isChecked && removeOnly)}
- id={permissionDefinition.key}
- label={translateWithParameters(
- 'permission.assign_x_to_y',
- permissionDefinition.name,
- permissionItem.name,
- )}
- onCheck={onCheck}
- >
- <span className="little-spacer-left">{permissionDefinition.name}</span>
- </Checkbox>
- </div>
- );
- })}
- </td>
+ return (
+ <div key={permissionDefinition.key}>
+ <Checkbox
+ aria-disabled={isDisabled || (!isChecked && removeOnly)}
+ checked={isChecked}
+ disabled={isDisabled || (!isChecked && removeOnly)}
+ id={`${permissionDefinition.key}`}
+ label={translateWithParameters(
+ 'permission.assign_x_to_y',
+ permissionDefinition.name,
+ permissionItem.name,
+ )}
+ onCheck={() => onCheck(isChecked, permissionDefinition.key)}
+ >
+ <span className="sw-ml-2">{permissionDefinition.name}</span>
+ </Checkbox>
+ </div>
+ );
+ })}
+ </div>
+ </CheckboxCell>
);
}
const isDisabled = disabled || loading.includes(permission.key);
return (
- <td
- className={classNames('permission-column text-center text-middle', {
+ <CheckboxCell
+ className={classNames({
selected: permission.key === selectedPermission,
})}
>
<Checkbox
+ aria-disabled={isDisabled || (!isChecked && removeOnly)}
checked={isChecked}
disabled={isDisabled || (!isChecked && removeOnly)}
- id={permission.key}
+ id={`${prefixID}-${permission.key}`}
label={translateWithParameters(
'permission.assign_x_to_y',
permission.name,
permissionItem.name,
)}
- onCheck={onCheck}
+ onCheck={() => onCheck(isChecked, permission.key)}
/>
- </td>
+ </CheckboxCell>
);
}
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import classNames from 'classnames';
+import { BareButton, ContentCell, HelperHintIcon } from 'design-system';
import * as React from 'react';
import { translate, translateWithParameters } from '../../helpers/l10n';
import { isPermissionDefinitionGroup } from '../../helpers/permissions';
import { PermissionDefinition, PermissionDefinitionGroup } from '../../types/types';
import InstanceMessage from '../common/InstanceMessage';
+import ClickEventBoundary from '../controls/ClickEventBoundary';
import HelpTooltip from '../controls/HelpTooltip';
import Tooltip from '../controls/Tooltip';
}
export default class PermissionHeader extends React.PureComponent<Props> {
- handlePermissionClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
- event.preventDefault();
- event.currentTarget.blur();
+ handlePermissionClick = () => {
const { permission } = this.props;
if (this.props.onSelectPermission && !isPermissionDefinitionGroup(permission)) {
this.props.onSelectPermission(permission.key);
name = translate('global_permissions', permission.category);
} else {
name = onSelectPermission ? (
- <Tooltip
- overlay={translateWithParameters(
- 'global_permissions.filter_by_x_permission',
- permission.name,
- )}
- >
- <a href="#" onClick={this.handlePermissionClick}>
- {permission.name}
- </a>
- </Tooltip>
+ <ClickEventBoundary>
+ <BareButton onClick={this.handlePermissionClick}>
+ <Tooltip
+ overlay={translateWithParameters(
+ 'global_permissions.filter_by_x_permission',
+ permission.name,
+ )}
+ >
+ <span>{permission.name}</span>
+ </Tooltip>
+ </BareButton>
+ </ClickEventBoundary>
) : (
permission.name
);
}
return (
- <th
+ <ContentCell
scope="col"
- className={classNames('permission-column text-center text-middle', {
+ className={classNames('sw-justify-center', {
selected:
!isPermissionDefinitionGroup(permission) &&
permission.key === this.props.selectedPermission,
})}
>
- <div className="permission-column-inner">
- {name}
- <HelpTooltip className="spacer-left" overlay={this.getTooltipOverlay()} />
+ <div className="sw-flex sw-content-center">
+ <div className="sw-grow-1 sw-text-center">{name}</div>
+
+ <HelpTooltip className="sw-ml-2" overlay={this.getTooltipOverlay()}>
+ <HelperHintIcon aria-label="help-tooltip" />
+ </HelpTooltip>
</div>
- </th>
+ </ContentCell>
);
}
}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { InputSearch, ToggleButton } from 'design-system';
import * as React from 'react';
import { translate } from '../../helpers/l10n';
-import ButtonToggle from '../controls/ButtonToggle';
-import SearchBox from '../controls/SearchBox';
export type FilterOption = 'all' | 'users' | 'groups';
interface Props {
];
return (
- <div className="display-flex-row">
- <ButtonToggle onCheck={props.onFilter} options={filterOptions} value={props.filter} />
+ <div className="sw-flex sw-flex-row">
+ <ToggleButton onChange={props.onFilter} options={filterOptions} value={props.filter} />
- <div className="flex-1 spacer-left">
- <SearchBox
+ <div className="sw-flex-1 sw-ml-2">
+ <InputSearch
minLength={3}
onChange={props.onSearch}
placeholder={translate('search.search_for_users_or_groups')}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { Avatar, ContentCell, Note, TableRowInteractive } from 'design-system';
import * as React from 'react';
import { translate } from '../../helpers/l10n';
import { isPermissionDefinitionGroup } from '../../helpers/permissions';
import { getBaseUrl } from '../../helpers/system';
import { PermissionDefinitions, PermissionUser } from '../../types/types';
-import LegacyAvatar from '../ui/LegacyAvatar';
import PermissionCell from './PermissionCell';
import usePermissionChange from './usePermissionChange';
disabled={disabled}
removeOnly={removeOnly}
permissionItem={user}
+ prefixID={user.login}
selectedPermission={selectedPermission}
/>
));
if (user.login === '<creator>') {
return (
- <tr>
- <td className="nowrap text-middle">
- <div>
- <strong>{user.name}</strong>
- </div>
- <div className="little-spacer-top" style={{ whiteSpace: 'normal' }}>
- {translate('permission_templates.project_creators.explanation')}
+ <TableRowInteractive>
+ <ContentCell>
+ <div className="sw-max-w-abs-800">
+ <div className="sw-flex sw-flex-col sw-w-fit sw-max-w-full">
+ <strong className="sw-text-ellipsis sw-whitespace-nowrap sw-overflow-hidden">
+ {user.name}
+ </strong>
+ <p className="sw-mt-2">
+ {translate('permission_templates.project_creators.explanation')}
+ </p>
+ </div>
</div>
- </td>
+ </ContentCell>
{permissionCells}
- </tr>
+ </TableRowInteractive>
);
}
return (
- <tr>
- <td className="nowrap text-middle">
- <div className="display-flex-center">
- <LegacyAvatar
- className="text-middle big-spacer-right flex-0"
- hash={user.avatar}
- name={user.name}
- size={36}
- />
- <div className="max-width-100">
+ <TableRowInteractive>
+ <ContentCell>
+ <div className="sw-flex sw-items-center">
+ <Avatar className="sw-mr-4" hash={user.avatar} name={user.name} size="md" />
+ <div className="sw-max-w-abs-800">
<div className="sw-flex sw-w-fit sw-max-w-full">
- <div className="sw-flex-1 text-ellipsis">
+ <div className="sw-flex-1 sw-text-ellipsis sw-whitespace-nowrap sw-overflow-hidden">
<strong>{user.name}</strong>
- <span className="note spacer-left">{user.login}</span>
+ <Note className="sw-ml-2">{user.login}</Note>
</div>
{isGitHubProject && user.managed && (
<img
alt="github"
- className="spacer-left spacer-right"
+ className="sw-my-2"
height={16}
aria-label={translate('project_permission.github_managed')}
src={`${getBaseUrl()}/images/alm/github.svg`}
/>
)}
</div>
-
- <div className="little-spacer-top max-width-100 text-ellipsis">{user.email}</div>
+ {user.email && (
+ <div className="sw-mt-2 sw-max-w-100 sw-text-ellipsis sw-whitespace-nowrap sw-overflow-hidden">
+ {user.email}
+ </div>
+ )}
</div>
</div>
- </td>
+ </ContentCell>
{permissionCells}
{modal}
- </tr>
+ </TableRowInteractive>
);
}