mirror of
https://github.com/ansible/awx.git
synced 2026-05-07 01:17:37 -02:30
Refactors Lookup
This commit is contained in:
@@ -36,7 +36,7 @@ function CredentialLookup({
|
|||||||
name="credential"
|
name="credential"
|
||||||
value={value}
|
value={value}
|
||||||
onBlur={onBlur}
|
onBlur={onBlur}
|
||||||
onLookupSave={onChange}
|
onChange={onChange}
|
||||||
getItems={getCredentials}
|
getItems={getCredentials}
|
||||||
required={required}
|
required={required}
|
||||||
sortedColumnKey="name"
|
sortedColumnKey="name"
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ class InstanceGroupsLookup extends React.Component {
|
|||||||
lookupHeader={i18n._(t`Instance Groups`)}
|
lookupHeader={i18n._(t`Instance Groups`)}
|
||||||
name="instanceGroups"
|
name="instanceGroups"
|
||||||
value={value}
|
value={value}
|
||||||
onLookupSave={onChange}
|
onChange={onChange}
|
||||||
getItems={getInstanceGroups}
|
getItems={getInstanceGroups}
|
||||||
qsNamespace="instance-group"
|
qsNamespace="instance-group"
|
||||||
multiple
|
multiple
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ class InventoryLookup extends React.Component {
|
|||||||
lookupHeader={i18n._(t`Inventory`)}
|
lookupHeader={i18n._(t`Inventory`)}
|
||||||
name="inventory"
|
name="inventory"
|
||||||
value={value}
|
value={value}
|
||||||
onLookupSave={onChange}
|
onChange={onChange}
|
||||||
onBlur={onBlur}
|
onBlur={onBlur}
|
||||||
getItems={getInventories}
|
getItems={getInventories}
|
||||||
required={required}
|
required={required}
|
||||||
|
|||||||
@@ -59,13 +59,13 @@ class Lookup extends React.Component {
|
|||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.assertCorrectValueType();
|
this.assertCorrectValueType();
|
||||||
let lookupSelectedItems = [];
|
let selectedItems = [];
|
||||||
if (props.value) {
|
if (props.value) {
|
||||||
lookupSelectedItems = props.multiple ? [...props.value] : [props.value];
|
selectedItems = props.multiple ? [...props.value] : [props.value];
|
||||||
}
|
}
|
||||||
this.state = {
|
this.state = {
|
||||||
isModalOpen: false,
|
isModalOpen: false,
|
||||||
lookupSelectedItems,
|
selectedItems,
|
||||||
results: [],
|
results: [],
|
||||||
count: 0,
|
count: 0,
|
||||||
error: null,
|
error: null,
|
||||||
@@ -76,7 +76,8 @@ class Lookup extends React.Component {
|
|||||||
order_by: props.sortedColumnKey,
|
order_by: props.sortedColumnKey,
|
||||||
});
|
});
|
||||||
this.handleModalToggle = this.handleModalToggle.bind(this);
|
this.handleModalToggle = this.handleModalToggle.bind(this);
|
||||||
this.toggleSelected = this.toggleSelected.bind(this);
|
this.addItem = this.addItem.bind(this);
|
||||||
|
this.removeItem = this.removeItem.bind(this);
|
||||||
this.saveModal = this.saveModal.bind(this);
|
this.saveModal = this.saveModal.bind(this);
|
||||||
this.getData = this.getData.bind(this);
|
this.getData = this.getData.bind(this);
|
||||||
this.clearQSParams = this.clearQSParams.bind(this);
|
this.clearQSParams = this.clearQSParams.bind(this);
|
||||||
@@ -132,46 +133,82 @@ class Lookup extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleSelected(row) {
|
removeItem(row) {
|
||||||
const {
|
const { selectedItems } = this.state;
|
||||||
name,
|
const { onToggleItem } = this.props;
|
||||||
onLookupSave,
|
if (onToggleItem) {
|
||||||
multiple,
|
this.setState({ selectedItems: onToggleItem(selectedItems, row) });
|
||||||
onToggleItem,
|
return;
|
||||||
selectCategoryOptions,
|
|
||||||
} = this.props;
|
|
||||||
const {
|
|
||||||
lookupSelectedItems: updatedSelectedItems,
|
|
||||||
isModalOpen,
|
|
||||||
} = this.state;
|
|
||||||
|
|
||||||
const selectedIndex = updatedSelectedItems.findIndex(
|
|
||||||
selectedRow => selectedRow.id === row.id
|
|
||||||
);
|
|
||||||
if (multiple) {
|
|
||||||
if (selectCategoryOptions) {
|
|
||||||
onToggleItem(row, isModalOpen);
|
|
||||||
}
|
|
||||||
if (selectedIndex > -1) {
|
|
||||||
updatedSelectedItems.splice(selectedIndex, 1);
|
|
||||||
this.setState({ lookupSelectedItems: updatedSelectedItems });
|
|
||||||
} else {
|
|
||||||
this.setState(prevState => ({
|
|
||||||
lookupSelectedItems: [...prevState.lookupSelectedItems, row],
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.setState({ lookupSelectedItems: [row] });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Updates the selected items from parent state
|
|
||||||
// This handles the case where the user removes chips from the lookup input
|
|
||||||
// while the modal is closed
|
|
||||||
if (!isModalOpen) {
|
|
||||||
onLookupSave(updatedSelectedItems, name);
|
|
||||||
}
|
}
|
||||||
|
this.setState({
|
||||||
|
selectedItems: selectedItems.filter(item => item.id !== row.id),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addItem(row) {
|
||||||
|
const { selectedItems } = this.state;
|
||||||
|
const { multiple, onToggleItem } = this.props;
|
||||||
|
if (onToggleItem) {
|
||||||
|
this.setState({ selectedItems: onToggleItem(selectedItems, row) });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const index = selectedItems.findIndex(item => item.id === row.id);
|
||||||
|
|
||||||
|
if (!multiple) {
|
||||||
|
this.setState({ selectedItems: [row] });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (index > -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.setState({ selectedItems: [...selectedItems, row] });
|
||||||
|
}
|
||||||
|
// toggleSelected(row) {
|
||||||
|
// const {
|
||||||
|
// name,
|
||||||
|
// onChange,
|
||||||
|
// multiple,
|
||||||
|
// onToggleItem,
|
||||||
|
// selectCategoryOptions,
|
||||||
|
// onChange,
|
||||||
|
// value
|
||||||
|
// } = this.props;
|
||||||
|
// const {
|
||||||
|
// selectedItems: updatedSelectedItems,
|
||||||
|
// isModalOpen,
|
||||||
|
// } = this.state;
|
||||||
|
|
||||||
|
// const selectedIndex = updatedSelectedItems.findIndex(
|
||||||
|
// selectedRow => selectedRow.id === row.id
|
||||||
|
// );
|
||||||
|
//
|
||||||
|
// if (multiple) {
|
||||||
|
//
|
||||||
|
// if (selectCategoryOptions) {
|
||||||
|
//
|
||||||
|
// onToggleItem(row, isModalOpen);
|
||||||
|
// }
|
||||||
|
// if (selectedIndex > -1) {
|
||||||
|
//
|
||||||
|
// const valueToUpdate = value.filter(itemValue => itemValue.id !==row.id );
|
||||||
|
// onChange(valueToUpdate)
|
||||||
|
// } else {
|
||||||
|
//
|
||||||
|
// onChange([...value, row])
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
//
|
||||||
|
// onChange(row)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Updates the selected items from parent state
|
||||||
|
// This handles the case where the user removes chips from the lookup input
|
||||||
|
// while the modal is closed
|
||||||
|
// if (!isModalOpen) {
|
||||||
|
// onChange(updatedSelectedItems, name);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
handleModalToggle() {
|
handleModalToggle() {
|
||||||
const { isModalOpen } = this.state;
|
const { isModalOpen } = this.state;
|
||||||
const { value, multiple, selectCategory } = this.props;
|
const { value, multiple, selectCategory } = this.props;
|
||||||
@@ -179,11 +216,11 @@ class Lookup extends React.Component {
|
|||||||
// This handles the case where the user closes/cancels the modal and
|
// This handles the case where the user closes/cancels the modal and
|
||||||
// opens it again
|
// opens it again
|
||||||
if (!isModalOpen) {
|
if (!isModalOpen) {
|
||||||
let lookupSelectedItems = [];
|
let selectedItems = [];
|
||||||
if (value) {
|
if (value) {
|
||||||
lookupSelectedItems = multiple ? [...value] : [value];
|
selectedItems = multiple ? [...value] : [value];
|
||||||
}
|
}
|
||||||
this.setState({ lookupSelectedItems });
|
this.setState({ selectedItems });
|
||||||
} else {
|
} else {
|
||||||
this.clearQSParams();
|
this.clearQSParams();
|
||||||
if (selectCategory) {
|
if (selectCategory) {
|
||||||
@@ -195,15 +232,22 @@ class Lookup extends React.Component {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
removeItemAndSave(row) {
|
||||||
|
const { value, onChange, multiple } = this.props;
|
||||||
|
if (multiple) {
|
||||||
|
onChange(value.filter(item => item.id !== row.id));
|
||||||
|
} else if (value.id === row.id) {
|
||||||
|
onChange(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
saveModal() {
|
saveModal() {
|
||||||
const { onLookupSave, name, multiple } = this.props;
|
const { onChange, multiple } = this.props;
|
||||||
const { lookupSelectedItems } = this.state;
|
const { selectedItems } = this.state;
|
||||||
const value = multiple
|
const value = multiple ? selectedItems : selectedItems[0] || null;
|
||||||
? lookupSelectedItems
|
|
||||||
: lookupSelectedItems[0] || null;
|
|
||||||
|
|
||||||
this.handleModalToggle();
|
this.handleModalToggle();
|
||||||
onLookupSave(value, name);
|
onChange(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
clearQSParams() {
|
clearQSParams() {
|
||||||
@@ -215,13 +259,7 @@ class Lookup extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const { isModalOpen, selectedItems, error, results, count } = this.state;
|
||||||
isModalOpen,
|
|
||||||
lookupSelectedItems,
|
|
||||||
error,
|
|
||||||
results,
|
|
||||||
count,
|
|
||||||
} = this.state;
|
|
||||||
const {
|
const {
|
||||||
form,
|
form,
|
||||||
id,
|
id,
|
||||||
@@ -245,7 +283,7 @@ class Lookup extends React.Component {
|
|||||||
{(multiple ? value : [value]).map(chip => (
|
{(multiple ? value : [value]).map(chip => (
|
||||||
<CredentialChip
|
<CredentialChip
|
||||||
key={chip.id}
|
key={chip.id}
|
||||||
onClick={() => this.toggleSelected(chip)}
|
onClick={() => this.removeItemAndSave(chip)}
|
||||||
isReadOnly={!canDelete}
|
isReadOnly={!canDelete}
|
||||||
credential={chip}
|
credential={chip}
|
||||||
/>
|
/>
|
||||||
@@ -256,7 +294,7 @@ class Lookup extends React.Component {
|
|||||||
{(multiple ? value : [value]).map(chip => (
|
{(multiple ? value : [value]).map(chip => (
|
||||||
<Chip
|
<Chip
|
||||||
key={chip.id}
|
key={chip.id}
|
||||||
onClick={() => this.toggleSelected(chip)}
|
onClick={() => this.removeItemAndSave(chip)}
|
||||||
isReadOnly={!canDelete}
|
isReadOnly={!canDelete}
|
||||||
>
|
>
|
||||||
{chip.name}
|
{chip.name}
|
||||||
@@ -287,19 +325,19 @@ class Lookup extends React.Component {
|
|||||||
onClose={this.handleModalToggle}
|
onClose={this.handleModalToggle}
|
||||||
actions={[
|
actions={[
|
||||||
<Button
|
<Button
|
||||||
key="save"
|
key="select"
|
||||||
variant="primary"
|
variant="primary"
|
||||||
onClick={this.saveModal}
|
onClick={this.saveModal}
|
||||||
style={results.length === 0 ? { display: 'none' } : {}}
|
style={selectedItems.length === 0 ? { display: 'none' } : {}}
|
||||||
>
|
>
|
||||||
{i18n._(t`Save`)}
|
{i18n._(t`Select`)}
|
||||||
</Button>,
|
</Button>,
|
||||||
<Button
|
<Button
|
||||||
key="cancel"
|
key="cancel"
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
onClick={this.handleModalToggle}
|
onClick={this.handleModalToggle}
|
||||||
>
|
>
|
||||||
{results.length === 0 ? i18n._(t`Close`) : i18n._(t`Cancel`)}
|
{i18n._(t`Cancel`)}
|
||||||
</Button>,
|
</Button>,
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
@@ -318,6 +356,18 @@ class Lookup extends React.Component {
|
|||||||
/>
|
/>
|
||||||
</ToolbarItem>
|
</ToolbarItem>
|
||||||
)}
|
)}
|
||||||
|
{selectedItems.length > 0 && (
|
||||||
|
<SelectedList
|
||||||
|
label={i18n._(t`Selected`)}
|
||||||
|
selected={selectedItems}
|
||||||
|
showOverflowAfter={5}
|
||||||
|
onRemove={this.removeItem}
|
||||||
|
isReadOnly={!canDelete}
|
||||||
|
isCredentialList={
|
||||||
|
selectCategoryOptions && selectCategoryOptions.length > 0
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<PaginatedDataList
|
<PaginatedDataList
|
||||||
items={results}
|
items={results}
|
||||||
itemCount={count}
|
itemCount={count}
|
||||||
@@ -330,12 +380,8 @@ class Lookup extends React.Component {
|
|||||||
itemId={item.id}
|
itemId={item.id}
|
||||||
name={multiple ? item.name : name}
|
name={multiple ? item.name : name}
|
||||||
label={item.name}
|
label={item.name}
|
||||||
isSelected={
|
isSelected={selectedItems.some(i => i.id === item.id)}
|
||||||
selectCategoryOptions
|
onSelect={() => this.addItem(item)}
|
||||||
? value.some(i => i.id === item.id)
|
|
||||||
: lookupSelectedItems.some(i => i.id === item.id)
|
|
||||||
}
|
|
||||||
onSelect={() => this.toggleSelected(item)}
|
|
||||||
isRadio={
|
isRadio={
|
||||||
!multiple ||
|
!multiple ||
|
||||||
(selectCategoryOptions &&
|
(selectCategoryOptions &&
|
||||||
@@ -347,17 +393,6 @@ class Lookup extends React.Component {
|
|||||||
renderToolbar={props => <DataListToolbar {...props} fillWidth />}
|
renderToolbar={props => <DataListToolbar {...props} fillWidth />}
|
||||||
showPageSizeOptions={false}
|
showPageSizeOptions={false}
|
||||||
/>
|
/>
|
||||||
{lookupSelectedItems.length > 0 && (
|
|
||||||
<SelectedList
|
|
||||||
label={i18n._(t`Selected`)}
|
|
||||||
selected={selectCategoryOptions ? value : lookupSelectedItems}
|
|
||||||
onRemove={this.toggleSelected}
|
|
||||||
isReadOnly={!canDelete}
|
|
||||||
isCredentialList={
|
|
||||||
selectCategoryOptions && selectCategoryOptions.length > 0
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{error ? <div>error</div> : ''}
|
{error ? <div>error</div> : ''}
|
||||||
</Modal>
|
</Modal>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
@@ -374,7 +409,7 @@ Lookup.propTypes = {
|
|||||||
getItems: func.isRequired,
|
getItems: func.isRequired,
|
||||||
lookupHeader: string,
|
lookupHeader: string,
|
||||||
name: string,
|
name: string,
|
||||||
onLookupSave: func.isRequired,
|
onChange: func.isRequired,
|
||||||
value: oneOfType([Item, arrayOf(Item)]),
|
value: oneOfType([Item, arrayOf(Item)]),
|
||||||
sortedColumnKey: string.isRequired,
|
sortedColumnKey: string.isRequired,
|
||||||
multiple: bool,
|
multiple: bool,
|
||||||
|
|||||||
@@ -12,7 +12,25 @@ import Lookup from '@components/Lookup';
|
|||||||
const QuestionCircleIcon = styled(PFQuestionCircleIcon)`
|
const QuestionCircleIcon = styled(PFQuestionCircleIcon)`
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
`;
|
`;
|
||||||
|
function toggleCredentialSelection(credentialsToUpdate, newCredential) {
|
||||||
|
let newCredentialsList;
|
||||||
|
const isSelectedCredentialInState =
|
||||||
|
credentialsToUpdate.filter(cred => cred.id === newCredential.id).length >
|
||||||
|
0;
|
||||||
|
|
||||||
|
if (isSelectedCredentialInState) {
|
||||||
|
newCredentialsList = credentialsToUpdate.filter(
|
||||||
|
cred => cred.id !== newCredential.id
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
newCredentialsList = credentialsToUpdate.filter(
|
||||||
|
credential =>
|
||||||
|
credential.kind === 'vault' || credential.kind !== newCredential.kind
|
||||||
|
);
|
||||||
|
newCredentialsList = [...newCredentialsList, newCredential];
|
||||||
|
}
|
||||||
|
return newCredentialsList;
|
||||||
|
}
|
||||||
class MultiCredentialsLookup extends React.Component {
|
class MultiCredentialsLookup extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
@@ -26,7 +44,6 @@ class MultiCredentialsLookup extends React.Component {
|
|||||||
this
|
this
|
||||||
);
|
);
|
||||||
this.loadCredentials = this.loadCredentials.bind(this);
|
this.loadCredentials = this.loadCredentials.bind(this);
|
||||||
this.toggleCredentialSelection = this.toggleCredentialSelection.bind(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@@ -69,27 +86,7 @@ class MultiCredentialsLookup extends React.Component {
|
|||||||
return CredentialsAPI.read(params);
|
return CredentialsAPI.read(params);
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleCredentialSelection(newCredential) {
|
|
||||||
const { onChange, credentials: credentialsToUpdate } = this.props;
|
|
||||||
|
|
||||||
let newCredentialsList;
|
|
||||||
const isSelectedCredentialInState =
|
|
||||||
credentialsToUpdate.filter(cred => cred.id === newCredential.id).length >
|
|
||||||
0;
|
|
||||||
|
|
||||||
if (isSelectedCredentialInState) {
|
|
||||||
newCredentialsList = credentialsToUpdate.filter(
|
|
||||||
cred => cred.id !== newCredential.id
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
newCredentialsList = credentialsToUpdate.filter(
|
|
||||||
credential =>
|
|
||||||
credential.kind === 'vault' || credential.kind !== newCredential.kind
|
|
||||||
);
|
|
||||||
newCredentialsList = [...newCredentialsList, newCredential];
|
|
||||||
}
|
|
||||||
onChange(newCredentialsList);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleCredentialTypeSelect(value, type) {
|
handleCredentialTypeSelect(value, type) {
|
||||||
const { credentialTypes } = this.state;
|
const { credentialTypes } = this.state;
|
||||||
@@ -99,7 +96,7 @@ class MultiCredentialsLookup extends React.Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { selectedCredentialType, credentialTypes } = this.state;
|
const { selectedCredentialType, credentialTypes } = this.state;
|
||||||
const { tooltip, i18n, credentials } = this.props;
|
const { tooltip, i18n, credentials, onChange } = this.props;
|
||||||
return (
|
return (
|
||||||
<FormGroup label={i18n._(t`Credentials`)} fieldId="multiCredential">
|
<FormGroup label={i18n._(t`Credentials`)} fieldId="multiCredential">
|
||||||
{tooltip && (
|
{tooltip && (
|
||||||
@@ -112,14 +109,14 @@ class MultiCredentialsLookup extends React.Component {
|
|||||||
selectCategoryOptions={credentialTypes}
|
selectCategoryOptions={credentialTypes}
|
||||||
selectCategory={this.handleCredentialTypeSelect}
|
selectCategory={this.handleCredentialTypeSelect}
|
||||||
selectedCategory={selectedCredentialType}
|
selectedCategory={selectedCredentialType}
|
||||||
onToggleItem={this.toggleCredentialSelection}
|
onToggleItem={toggleCredentialSelection}
|
||||||
onloadCategories={this.loadCredentialTypes}
|
onloadCategories={this.loadCredentialTypes}
|
||||||
id="multiCredential"
|
id="multiCredential"
|
||||||
lookupHeader={i18n._(t`Credentials`)}
|
lookupHeader={i18n._(t`Credentials`)}
|
||||||
name="credentials"
|
name="credentials"
|
||||||
value={credentials}
|
value={credentials}
|
||||||
multiple
|
multiple
|
||||||
onLookupSave={() => {}}
|
onChange={onChange}
|
||||||
getItems={this.loadCredentials}
|
getItems={this.loadCredentials}
|
||||||
qsNamespace="credentials"
|
qsNamespace="credentials"
|
||||||
columns={[
|
columns={[
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ function OrganizationLookup({
|
|||||||
name="organization"
|
name="organization"
|
||||||
value={value}
|
value={value}
|
||||||
onBlur={onBlur}
|
onBlur={onBlur}
|
||||||
onLookupSave={onChange}
|
onChange={onChange}
|
||||||
getItems={getOrganizations}
|
getItems={getOrganizations}
|
||||||
required={required}
|
required={required}
|
||||||
sortedColumnKey="name"
|
sortedColumnKey="name"
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ class ProjectLookup extends React.Component {
|
|||||||
name="project"
|
name="project"
|
||||||
value={value}
|
value={value}
|
||||||
onBlur={onBlur}
|
onBlur={onBlur}
|
||||||
onLookupSave={onChange}
|
onChange={onChange}
|
||||||
getItems={loadProjects}
|
getItems={loadProjects}
|
||||||
required={required}
|
required={required}
|
||||||
sortedColumnKey="name"
|
sortedColumnKey="name"
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ export const ScmCredentialFormField = withI18n()(
|
|||||||
value={credential.value}
|
value={credential.value}
|
||||||
onChange={value => {
|
onChange={value => {
|
||||||
onCredentialSelection('scm', value);
|
onCredentialSelection('scm', value);
|
||||||
form.setFieldValue('credential', value.id);
|
form.setFieldValue('credential', value ? value.id : '');
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user