mirror of
https://github.com/mattermost/mattermost.git
synced 2025-02-25 18:55:24 -06:00
MM-58529 Fix for AdditionalValues in SystemConsole (#27614)
* Reimplement display Timelimit modal * update permission_description, add unit tests * Update permission_group.tsx * update tests, remove logging * Update permission_description.tsx * fix issue with link opening new window * Update permission_description.tsx --------- Co-authored-by: Mattermost Build <build@mattermost.com>
This commit is contained in:
parent
2e004bb7bc
commit
5f8f1f8142
@ -0,0 +1,167 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`components/admin_console/permission_schemes_settings/permission_description should allow select with link 1`] = `
|
||||
<Provider
|
||||
store={
|
||||
Object {
|
||||
"clearActions": [Function],
|
||||
"dispatch": [Function],
|
||||
"getActions": [Function],
|
||||
"getState": [Function],
|
||||
"replaceReducer": [Function],
|
||||
"subscribe": [Function],
|
||||
}
|
||||
}
|
||||
>
|
||||
<PermissionDescription
|
||||
description="This is the description"
|
||||
id="defaultID"
|
||||
inherited={
|
||||
Object {
|
||||
"name": "all_users",
|
||||
}
|
||||
}
|
||||
selectRow={[MockFunction]}
|
||||
>
|
||||
<span
|
||||
className="permission-description"
|
||||
onClick={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
>
|
||||
<span
|
||||
className="inherit-link-wrapper"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Inherited from <link>{name}</link>."
|
||||
id="admin.permissions.inherited_from"
|
||||
values={
|
||||
Object {
|
||||
"link": [Function],
|
||||
"name": "All Members",
|
||||
}
|
||||
}
|
||||
>
|
||||
<span>
|
||||
Inherited from
|
||||
<a
|
||||
key=".$.1"
|
||||
>
|
||||
All Members
|
||||
</a>
|
||||
.
|
||||
</span>
|
||||
</FormattedMessage>
|
||||
</span>
|
||||
<Overlay
|
||||
animation={[Function]}
|
||||
placement="top"
|
||||
rootClose={false}
|
||||
show={false}
|
||||
target={null}
|
||||
>
|
||||
<Overlay
|
||||
placement="top"
|
||||
rootClose={false}
|
||||
show={false}
|
||||
target={null}
|
||||
transition={[Function]}
|
||||
/>
|
||||
</Overlay>
|
||||
</span>
|
||||
</PermissionDescription>
|
||||
</Provider>
|
||||
`;
|
||||
|
||||
exports[`components/admin_console/permission_schemes_settings/permission_description should match snapshot if inherited 1`] = `
|
||||
<ContextProvider
|
||||
value={
|
||||
Object {
|
||||
"store": Object {
|
||||
"clearActions": [Function],
|
||||
"dispatch": [Function],
|
||||
"getActions": [Function],
|
||||
"getState": [Function],
|
||||
"replaceReducer": [Function],
|
||||
"subscribe": [Function],
|
||||
},
|
||||
"subscription": Subscription {
|
||||
"handleChangeWrapper": [Function],
|
||||
"listeners": Object {
|
||||
"notify": [Function],
|
||||
},
|
||||
"onStateChange": [Function],
|
||||
"parentSub": undefined,
|
||||
"store": Object {
|
||||
"clearActions": [Function],
|
||||
"dispatch": [Function],
|
||||
"getActions": [Function],
|
||||
"getState": [Function],
|
||||
"replaceReducer": [Function],
|
||||
"subscribe": [Function],
|
||||
},
|
||||
"unsubscribe": null,
|
||||
},
|
||||
}
|
||||
}
|
||||
>
|
||||
<PermissionDescription
|
||||
description="This is the description"
|
||||
id="defaultID"
|
||||
inherited={
|
||||
Object {
|
||||
"name": "all_users",
|
||||
}
|
||||
}
|
||||
selectRow={[MockFunction]}
|
||||
/>
|
||||
</ContextProvider>
|
||||
`;
|
||||
|
||||
exports[`components/admin_console/permission_schemes_settings/permission_description should match snapshot with clickable link 1`] = `
|
||||
<span
|
||||
className="permission-description"
|
||||
onClick={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
>
|
||||
<span>
|
||||
This is a clickable description
|
||||
</span>
|
||||
<Overlay
|
||||
animation={[Function]}
|
||||
placement="top"
|
||||
rootClose={false}
|
||||
show={false}
|
||||
target={null}
|
||||
>
|
||||
<Tooltip>
|
||||
<span>
|
||||
This is a clickable description
|
||||
</span>
|
||||
</Tooltip>
|
||||
</Overlay>
|
||||
</span>
|
||||
`;
|
||||
|
||||
exports[`components/admin_console/permission_schemes_settings/permission_description should match snapshot with default Props 1`] = `
|
||||
<span
|
||||
className="permission-description"
|
||||
onClick={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
>
|
||||
This is the description
|
||||
<Overlay
|
||||
animation={[Function]}
|
||||
placement="top"
|
||||
rootClose={false}
|
||||
show={false}
|
||||
target={null}
|
||||
>
|
||||
<Tooltip>
|
||||
This is the description
|
||||
</Tooltip>
|
||||
</Overlay>
|
||||
</span>
|
||||
`;
|
@ -668,3 +668,56 @@ exports[`components/admin_console/permission_schemes_settings/permission_group s
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`components/admin_console/permission_schemes_settings/permission_group should match snapshot with additional values 1`] = `
|
||||
<div
|
||||
className="permission-group"
|
||||
>
|
||||
<div
|
||||
className="permission-group-row "
|
||||
id="uniqId"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<div
|
||||
className="fa fa-caret-right permission-arrow open"
|
||||
onClick={[Function]}
|
||||
/>
|
||||
<PermissionCheckbox
|
||||
id="uniqId-checkbox"
|
||||
value=""
|
||||
/>
|
||||
<span
|
||||
className="permission-name"
|
||||
>
|
||||
name
|
||||
</span>
|
||||
<PermissionDescription
|
||||
description=""
|
||||
id="name"
|
||||
selectRow={[MockFunction]}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="permission-group-permissions open"
|
||||
>
|
||||
<PermissionRow
|
||||
additionalValues={Object {}}
|
||||
id="invite_user"
|
||||
key="invite_user"
|
||||
onChange={[Function]}
|
||||
selectRow={[MockFunction]}
|
||||
uniqId="uniqId-invite_user"
|
||||
value=""
|
||||
/>
|
||||
<PermissionRow
|
||||
additionalValues={Object {}}
|
||||
id="add_user_to_team"
|
||||
key="add_user_to_team"
|
||||
onChange={[Function]}
|
||||
selectRow={[MockFunction]}
|
||||
uniqId="uniqId-add_user_to_team"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
@ -100,3 +100,40 @@ exports[`components/admin_console/permission_schemes_settings/permission_row sho
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`components/admin_console/permission_schemes_settings/permission_row should match snapshot with additional values 1`] = `
|
||||
<div
|
||||
className="permission-row"
|
||||
id="uniqId"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<PermissionCheckbox
|
||||
id="uniqId-checkbox"
|
||||
value="checked"
|
||||
/>
|
||||
<span
|
||||
className="permission-name"
|
||||
>
|
||||
id
|
||||
</span>
|
||||
<PermissionDescription
|
||||
additionalValues={
|
||||
Object {
|
||||
"edit_post": Object {
|
||||
"editTimeLimitButton": <Button
|
||||
active={false}
|
||||
block={false}
|
||||
bsClass="btn"
|
||||
bsStyle="default"
|
||||
disabled={false}
|
||||
onClick={[MockFunction]}
|
||||
/>,
|
||||
},
|
||||
}
|
||||
}
|
||||
description=""
|
||||
id="id"
|
||||
selectRow={[MockFunction]}
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
@ -0,0 +1,90 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
import * as reactRedux from 'react-redux';
|
||||
|
||||
import {mountWithIntl} from 'tests/helpers/intl-test-helper';
|
||||
import mockStore from 'tests/test_store';
|
||||
|
||||
import PermissionDescription from './permission_description';
|
||||
|
||||
describe('components/admin_console/permission_schemes_settings/permission_description', () => {
|
||||
const defaultProps = {
|
||||
id: 'defaultID',
|
||||
selectRow: jest.fn(),
|
||||
description: 'This is the description',
|
||||
};
|
||||
|
||||
let store = mockStore();
|
||||
beforeEach(() => {
|
||||
const initialState = {
|
||||
entities: {
|
||||
general: {
|
||||
config: {},
|
||||
},
|
||||
users: {
|
||||
currentUserId: 'currentUserId',
|
||||
},
|
||||
},
|
||||
};
|
||||
store = mockStore(initialState);
|
||||
});
|
||||
|
||||
test('should match snapshot with default Props', () => {
|
||||
const wrapper = shallow(
|
||||
<PermissionDescription
|
||||
{...defaultProps}
|
||||
/>,
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot if inherited', () => {
|
||||
const wrapper = shallow(
|
||||
<reactRedux.Provider store={store}>
|
||||
<PermissionDescription
|
||||
{...defaultProps}
|
||||
inherited={{
|
||||
name: 'all_users',
|
||||
}}
|
||||
/>
|
||||
</reactRedux.Provider>,
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot with clickable link', () => {
|
||||
const description = (
|
||||
<span>{'This is a clickable description'}</span>
|
||||
);
|
||||
const wrapper = shallow(
|
||||
<PermissionDescription
|
||||
{...defaultProps}
|
||||
description={description}
|
||||
/>,
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should allow select with link', () => {
|
||||
const selectRow = jest.fn();
|
||||
|
||||
const wrapper = mountWithIntl(
|
||||
<reactRedux.Provider store={store}>
|
||||
<PermissionDescription
|
||||
{...defaultProps}
|
||||
inherited={{
|
||||
name: 'all_users',
|
||||
}}
|
||||
selectRow={selectRow}
|
||||
/>
|
||||
</reactRedux.Provider>,
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
|
||||
wrapper.find('a').simulate('click');
|
||||
expect(selectRow).toBeCalled();
|
||||
});
|
||||
});
|
@ -4,15 +4,12 @@
|
||||
import React, {useState, useRef} from 'react';
|
||||
import type {MouseEvent} from 'react';
|
||||
import {Overlay} from 'react-bootstrap';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {FormattedMessage, useIntl} from 'react-intl';
|
||||
|
||||
import type {Role} from '@mattermost/types/roles';
|
||||
|
||||
import FormattedMarkdownMessage from 'components/formatted_markdown_message';
|
||||
import Tooltip from 'components/tooltip';
|
||||
|
||||
import {generateId} from 'utils/utils';
|
||||
|
||||
import type {AdditionalValues} from './permissions_tree/types';
|
||||
import {rolesRolesStrings} from './strings/roles';
|
||||
|
||||
@ -32,7 +29,6 @@ const PermissionDescription = ({
|
||||
inherited,
|
||||
}: Props): JSX.Element => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const randomId = generateId();
|
||||
const contentRef = useRef<HTMLSpanElement>(null);
|
||||
const intl = useIntl();
|
||||
|
||||
@ -57,12 +53,18 @@ const PermissionDescription = ({
|
||||
|
||||
let content: string | JSX.Element = '';
|
||||
if (inherited && inherited.name) {
|
||||
const formattedName = intl.formatMessage(rolesRolesStrings[inherited.name]);
|
||||
content = (
|
||||
<span className='inherit-link-wrapper'>
|
||||
<FormattedMarkdownMessage
|
||||
<FormattedMessage
|
||||
id='admin.permissions.inherited_from'
|
||||
defaultMessage='Inherited from [{name}]().'
|
||||
values={{name: intl.formatMessage(rolesRolesStrings[inherited.name])}}
|
||||
defaultMessage='Inherited from <link>{name}</link>.'
|
||||
values={{
|
||||
name: formattedName,
|
||||
link: (text: string) => (
|
||||
<a>{text}</a>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
@ -75,7 +77,7 @@ const PermissionDescription = ({
|
||||
placement='top'
|
||||
target={(contentRef.current as HTMLSpanElement)}
|
||||
>
|
||||
<Tooltip id={randomId}>
|
||||
<Tooltip>
|
||||
{content}
|
||||
</Tooltip>
|
||||
</Overlay>
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
import {shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
import {Button} from 'react-bootstrap';
|
||||
|
||||
import PermissionGroup from 'components/admin_console/permission_schemes_settings/permission_group';
|
||||
|
||||
@ -111,6 +112,26 @@ describe('components/admin_console/permission_schemes_settings/permission_group'
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot with additional values', () => {
|
||||
const ADDITIONAL_VALUES = {
|
||||
edit_post: {
|
||||
editTimeLimitButton: (
|
||||
<Button
|
||||
onClick={jest.fn()}
|
||||
/>
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
const wrapper = shallow(
|
||||
<PermissionGroup
|
||||
{...defaultProps}
|
||||
additionalValues={ADDITIONAL_VALUES}
|
||||
/>,
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should expand and collapse correctly, expanded by default, collapsed and then expanded again', () => {
|
||||
const wrapper = shallow(
|
||||
<PermissionGroup {...defaultProps}/>,
|
||||
|
@ -280,9 +280,17 @@ export default class PermissionGroup extends React.PureComponent<Props, State> {
|
||||
classes += ' combined';
|
||||
}
|
||||
const additionalValuesProp = additionalValues?.[id] ? additionalValues[id] : undefined;
|
||||
|
||||
const name = groupRolesStrings[id] ? <FormattedMessage {...groupRolesStrings[id].name}/> : id;
|
||||
const description = groupRolesStrings[id] ? <FormattedMessage {...groupRolesStrings[id].description}/> : '';
|
||||
let description: React.JSX.Element | string = '';
|
||||
if (groupRolesStrings[id]) {
|
||||
description = (
|
||||
<FormattedMessage
|
||||
id={groupRolesStrings[id].description.id}
|
||||
defaultMessage={groupRolesStrings[id].description.defaultMessage}
|
||||
values={additionalValuesProp}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='permission-group'>
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
import {shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
import {Button} from 'react-bootstrap';
|
||||
|
||||
import PermissionRow from 'components/admin_console/permission_schemes_settings/permission_row';
|
||||
|
||||
@ -55,6 +56,26 @@ describe('components/admin_console/permission_schemes_settings/permission_row',
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot with additional values', () => {
|
||||
const ADDITIONAL_VALUES = {
|
||||
edit_post: {
|
||||
editTimeLimitButton: (
|
||||
<Button
|
||||
onClick={jest.fn()}
|
||||
/>
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
const wrapper = shallow(
|
||||
<PermissionRow
|
||||
{...defaultProps}
|
||||
additionalValues={ADDITIONAL_VALUES}
|
||||
/>,
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should call onChange function on click', () => {
|
||||
const onChange = jest.fn();
|
||||
const wrapper = shallow(
|
||||
|
@ -43,7 +43,15 @@ const PermissionRow = ({
|
||||
}, [readOnly, onChange, id]);
|
||||
|
||||
const name = permissionRolesStrings[id] ? <FormattedMessage {...permissionRolesStrings[id].name}/> : id;
|
||||
const description = permissionRolesStrings[id] ? <FormattedMessage {...permissionRolesStrings[id].description}/> : '';
|
||||
let description: React.JSX.Element | string = '';
|
||||
if (permissionRolesStrings[id]) {
|
||||
description = (
|
||||
<FormattedMessage
|
||||
id={permissionRolesStrings[id].description.id}
|
||||
values={additionalValues}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
|
@ -58,12 +58,12 @@ exports[`components/admin_console/permission_schemes_settings/permission_team_sc
|
||||
>
|
||||
<span>
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="<linkTeamOverride>Team Override Schemes</linkTeamOverride> set the permissions for Team Admins, Channel Admins and other members in specific teams. Use a Team Override Scheme when specific teams need permission exceptions to the <linkSystemScheme>System Scheme</linkSystemScheme>."
|
||||
defaultMessage="<linkOverrideTeam>Team Override Schemes</linkOverrideTeam> set the permissions for Team Admins, Channel Admins and other members in specific teams. Use a Team Override Scheme when specific teams need permission exceptions to the <linkSystemScheme>System Scheme</linkSystemScheme>."
|
||||
id="admin.permissions.teamScheme.introBanner"
|
||||
values={
|
||||
Object {
|
||||
"linkOverrideTeam": [Function],
|
||||
"linkSystemScheme": [Function],
|
||||
"linkTeamOverride": [Function],
|
||||
}
|
||||
}
|
||||
/>
|
||||
@ -433,12 +433,12 @@ exports[`components/admin_console/permission_schemes_settings/permission_team_sc
|
||||
>
|
||||
<span>
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="<linkTeamOverride>Team Override Schemes</linkTeamOverride> set the permissions for Team Admins, Channel Admins and other members in specific teams. Use a Team Override Scheme when specific teams need permission exceptions to the <linkSystemScheme>System Scheme</linkSystemScheme>."
|
||||
defaultMessage="<linkOverrideTeam>Team Override Schemes</linkOverrideTeam> set the permissions for Team Admins, Channel Admins and other members in specific teams. Use a Team Override Scheme when specific teams need permission exceptions to the <linkSystemScheme>System Scheme</linkSystemScheme>."
|
||||
id="admin.permissions.teamScheme.introBanner"
|
||||
values={
|
||||
Object {
|
||||
"linkOverrideTeam": [Function],
|
||||
"linkSystemScheme": [Function],
|
||||
"linkTeamOverride": [Function],
|
||||
}
|
||||
}
|
||||
/>
|
||||
@ -838,12 +838,12 @@ exports[`components/admin_console/permission_schemes_settings/permission_team_sc
|
||||
>
|
||||
<span>
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="<linkTeamOverride>Team Override Schemes</linkTeamOverride> set the permissions for Team Admins, Channel Admins and other members in specific teams. Use a Team Override Scheme when specific teams need permission exceptions to the <linkSystemScheme>System Scheme</linkSystemScheme>."
|
||||
defaultMessage="<linkOverrideTeam>Team Override Schemes</linkOverrideTeam> set the permissions for Team Admins, Channel Admins and other members in specific teams. Use a Team Override Scheme when specific teams need permission exceptions to the <linkSystemScheme>System Scheme</linkSystemScheme>."
|
||||
id="admin.permissions.teamScheme.introBanner"
|
||||
values={
|
||||
Object {
|
||||
"linkOverrideTeam": [Function],
|
||||
"linkSystemScheme": [Function],
|
||||
"linkTeamOverride": [Function],
|
||||
}
|
||||
}
|
||||
/>
|
||||
@ -1311,12 +1311,12 @@ exports[`components/admin_console/permission_schemes_settings/permission_team_sc
|
||||
>
|
||||
<span>
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="<linkTeamOverride>Team Override Schemes</linkTeamOverride> set the permissions for Team Admins, Channel Admins and other members in specific teams. Use a Team Override Scheme when specific teams need permission exceptions to the <linkSystemScheme>System Scheme</linkSystemScheme>."
|
||||
defaultMessage="<linkOverrideTeam>Team Override Schemes</linkOverrideTeam> set the permissions for Team Admins, Channel Admins and other members in specific teams. Use a Team Override Scheme when specific teams need permission exceptions to the <linkSystemScheme>System Scheme</linkSystemScheme>."
|
||||
id="admin.permissions.teamScheme.introBanner"
|
||||
values={
|
||||
Object {
|
||||
"linkOverrideTeam": [Function],
|
||||
"linkSystemScheme": [Function],
|
||||
"linkTeamOverride": [Function],
|
||||
}
|
||||
}
|
||||
/>
|
||||
|
@ -606,9 +606,9 @@ class PermissionTeamSchemeSettings extends React.PureComponent<Props & RouteComp
|
||||
<span>
|
||||
<FormattedMessage
|
||||
id='admin.permissions.teamScheme.introBanner'
|
||||
defaultMessage='<linkTeamOverride>Team Override Schemes</linkTeamOverride> set the permissions for Team Admins, Channel Admins and other members in specific teams. Use a Team Override Scheme when specific teams need permission exceptions to the <linkSystemScheme>System Scheme</linkSystemScheme>.'
|
||||
defaultMessage='<linkOverrideTeam>Team Override Schemes</linkOverrideTeam> set the permissions for Team Admins, Channel Admins and other members in specific teams. Use a Team Override Scheme when specific teams need permission exceptions to the <linkSystemScheme>System Scheme</linkSystemScheme>.'
|
||||
values={{
|
||||
linkTeamOverride: (msg: React.ReactNode) => (
|
||||
linkOverrideTeam: (msg: React.ReactNode) => (
|
||||
<ExternalLink
|
||||
href={DocLinks.ONBOARD_ADVANCED_PERMISSIONS}
|
||||
location='permission_team_scheme_settings'
|
||||
|
@ -1619,7 +1619,7 @@
|
||||
"admin.permissions.group.teams_team_scope.name": "Teams",
|
||||
"admin.permissions.group.teams.description": "Create teams and manage members.",
|
||||
"admin.permissions.group.teams.name": "Teams",
|
||||
"admin.permissions.inherited_from": "Inherited from [{name}]().",
|
||||
"admin.permissions.inherited_from": "Inherited from <link>{name}</link>.",
|
||||
"admin.permissions.introBanner": "Permission Schemes set the default permissions for Team Admins, Channel Admins and everyone else. Learn more about permission schemes in our <link>documentation</link>.",
|
||||
"admin.permissions.loadingMoreSchemes": "Loading...",
|
||||
"admin.permissions.loadMoreSchemes": "Load more schemes",
|
||||
|
Loading…
Reference in New Issue
Block a user