mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-05-19 17:14:57 +00:00
[ADF-3761]Add auto tests for task filter cloud (#3994)
* Added first test * Implemented created standalone task call. * no message * Implement start task * Added claim, complete task calls. Added e2e tests for task filters. * Fix lint errors * Fix spelling * Modified the name of a method. * Modified appListCloudComponent page
This commit is contained in:
parent
74851ac651
commit
776d25820b
64
e2e/actions/APS-cloud/apiservice.ts
Normal file
64
e2e/actions/APS-cloud/apiservice.ts
Normal file
@ -0,0 +1,64 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import AlfrescoApi = require('alfresco-js-api-node');
|
||||
import TestConfig = require('../../test.config');
|
||||
|
||||
export class ApiService {
|
||||
|
||||
HOST_SSO = TestConfig.adf.hostSso;
|
||||
|
||||
apiService = new AlfrescoApi({
|
||||
provider: 'BPM',
|
||||
authType: 'OAUTH',
|
||||
oauth2: {
|
||||
host: `${this.HOST_SSO}/auth/realms/springboot`,
|
||||
authType: '/protocol/openid-connect/token',
|
||||
clientId: 'activiti',
|
||||
scope: 'openid',
|
||||
secret: '',
|
||||
implicitFlow: false,
|
||||
silentLogin: false,
|
||||
redirectUri: '/',
|
||||
redirectUriLogout: '/logout'
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
async login(username, password) {
|
||||
await this.apiService.login(username, password);
|
||||
}
|
||||
|
||||
async performBpmOperation(path, method, queryParams, postBody) {
|
||||
const uri = this.HOST_SSO + path;
|
||||
const pathParams = {}, formParams = {};
|
||||
const authNames = [];
|
||||
const contentTypes = ['application/json'];
|
||||
const accepts = ['application/json'];
|
||||
|
||||
const headerParams = {
|
||||
'Authorization': 'bearer ' + this.apiService.oauth2Auth.token
|
||||
};
|
||||
|
||||
return this.apiService.bpmClient.callCustomApi(uri, method, pathParams, queryParams, headerParams, formParams, postBody,
|
||||
authNames, contentTypes, accepts, {})
|
||||
.catch(error => {
|
||||
throw (error);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
64
e2e/actions/APS-cloud/tasks.ts
Normal file
64
e2e/actions/APS-cloud/tasks.ts
Normal file
@ -0,0 +1,64 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ApiService } from './apiservice';
|
||||
|
||||
export class Tasks {
|
||||
|
||||
api: ApiService = new ApiService();
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
async init(username, password) {
|
||||
await this.api.login(username, password);
|
||||
}
|
||||
|
||||
async createStandaloneTask(taskName, appName) {
|
||||
const path = '/' + appName + '-rb/v1/tasks';
|
||||
const method = 'POST';
|
||||
|
||||
const queryParams = {}, postBody = {
|
||||
'name': taskName,
|
||||
'payloadType': 'CreateTaskPayload'
|
||||
};
|
||||
|
||||
const data = await this.api.performBpmOperation(path, method, queryParams, postBody);
|
||||
return data;
|
||||
}
|
||||
|
||||
async completeTask(taskId, appName) {
|
||||
const path = '/' + appName + '-rb/v1/tasks/' + taskId + '/complete';
|
||||
const method = 'POST';
|
||||
|
||||
const queryParams = {}, postBody = {'payloadType': 'CompleteTaskPayload'};
|
||||
|
||||
const data = await this.api.performBpmOperation(path, method, queryParams, postBody);
|
||||
return data;
|
||||
}
|
||||
|
||||
async claimTask(taskId, appName) {
|
||||
const path = '/' + appName + '-rb/v1/tasks/' + taskId + '/claim';
|
||||
const method = 'POST';
|
||||
|
||||
const queryParams = {}, postBody = {};
|
||||
|
||||
const data = await this.api.performBpmOperation(path, method, queryParams, postBody);
|
||||
return data;
|
||||
}
|
||||
|
||||
}
|
64
e2e/pages/adf/demo-shell/tasksCloudDemoPage.ts
Normal file
64
e2e/pages/adf/demo-shell/tasksCloudDemoPage.ts
Normal file
@ -0,0 +1,64 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import Util = require('../../../util/util');
|
||||
|
||||
import { TaskFiltersCloudComponent } from '../process_cloud/taskFiltersCloudComponent';
|
||||
import { TaskListCloudComponent } from '../process_cloud/taskListCloudComponent';
|
||||
import { element, by } from 'protractor';
|
||||
|
||||
export class TasksCloudDemoPage {
|
||||
|
||||
myTasks = element(by.css('span[data-automation-id="My Tasks_filter"]'));
|
||||
cancelledTasks = element(by.css('span[data-automation-id="Cancelled Tasks_filter"]'));
|
||||
completedTasks = element(by.css('span[data-automation-id="Completed Tasks_filter"]'));
|
||||
suspendedTasks = element(by.css('span[data-automation-id="Suspended Tasks_filter"]'));
|
||||
activeFilter = element(by.css("mat-list-item[class*='active'] span"));
|
||||
|
||||
taskFiltersCloudComponent(filter) {
|
||||
return new TaskFiltersCloudComponent(filter);
|
||||
}
|
||||
|
||||
taskListCloudComponent() {
|
||||
return new TaskListCloudComponent();
|
||||
}
|
||||
|
||||
myTasksFilter() {
|
||||
return new TaskFiltersCloudComponent(this.myTasks);
|
||||
}
|
||||
|
||||
cancelledTasksFilter() {
|
||||
return new TaskFiltersCloudComponent(this.cancelledTasks);
|
||||
}
|
||||
|
||||
completedTasksFilter() {
|
||||
return new TaskFiltersCloudComponent(this.completedTasks);
|
||||
}
|
||||
|
||||
suspendedTasksFilter() {
|
||||
return new TaskFiltersCloudComponent(this.suspendedTasks);
|
||||
}
|
||||
|
||||
customTaskFilter(filterName) {
|
||||
return new TaskFiltersCloudComponent(element(by.css(`span[data-automation-id="${filterName}_filter"]`)));
|
||||
}
|
||||
|
||||
checkActiveFilterActive () {
|
||||
Util.waitUntilElementIsVisible(this.activeFilter);
|
||||
return this.activeFilter.getText();
|
||||
}
|
||||
}
|
@ -19,6 +19,7 @@ import Util = require('../../util/util');
|
||||
import TestConfig = require('../../test.config');
|
||||
import { element, by } from 'protractor';
|
||||
import { ProcessServicesPage } from './process_services/processServicesPage';
|
||||
import { AppListCloudComponent } from './process_cloud/appListCloudComponent';
|
||||
|
||||
export class NavigationBarPage {
|
||||
|
||||
@ -26,7 +27,7 @@ export class NavigationBarPage {
|
||||
taskListButton = element(by.css("a[data-automation-id='Task List']"));
|
||||
configEditorButton = element(by.css('a[data-automation-id="Configuration Editor"]'));
|
||||
processServicesButton = element(by.css('a[data-automation-id="Process Services"]'));
|
||||
processCloudButton = element(by.css('a[data-automation-id="Process Cloud"]'));
|
||||
processServicesCloudButton = element(by.css('a[data-automation-id="Process Cloud"]'));
|
||||
loginButton = element(by.css('a[data-automation-id="Login"]'));
|
||||
trashcanButton = element(by.css('a[data-automation-id="Trashcan"]'));
|
||||
userProfileButton = element(by.css('button[data-automation-id="adf-user-profile"]'));
|
||||
@ -50,12 +51,6 @@ export class NavigationBarPage {
|
||||
this.taskListButton.click();
|
||||
}
|
||||
|
||||
clickProcessCloudButton() {
|
||||
Util.waitUntilElementIsVisible(this.processCloudButton);
|
||||
this.processCloudButton.click();
|
||||
|
||||
}
|
||||
|
||||
clickConfigEditorButton() {
|
||||
Util.waitUntilElementIsVisible(this.configEditorButton);
|
||||
this.configEditorButton.click();
|
||||
@ -67,6 +62,12 @@ export class NavigationBarPage {
|
||||
return new ProcessServicesPage();
|
||||
}
|
||||
|
||||
navigateToProcessServicesCloudPage() {
|
||||
Util.waitUntilElementIsVisible(this.processServicesCloudButton);
|
||||
this.processServicesCloudButton.click();
|
||||
return new AppListCloudComponent();
|
||||
}
|
||||
|
||||
clickLoginButton() {
|
||||
Util.waitUntilElementIsVisible(this.loginButton);
|
||||
this.loginButton.click();
|
||||
@ -171,6 +172,6 @@ export class NavigationBarPage {
|
||||
}
|
||||
|
||||
checkContentServicesButtonIsDisplayed() {
|
||||
Util.waitUntilElementIsVisible(contentServicesButton);
|
||||
Util.waitUntilElementIsVisible(this.contentServicesButton);
|
||||
}
|
||||
}
|
||||
|
57
e2e/pages/adf/process_cloud/taskFiltersCloudComponent.ts
Normal file
57
e2e/pages/adf/process_cloud/taskFiltersCloudComponent.ts
Normal file
@ -0,0 +1,57 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import Util = require('../../../util/util');
|
||||
import { by } from 'protractor';
|
||||
|
||||
export class TaskFiltersCloudComponent {
|
||||
|
||||
filter;
|
||||
taskIcon = by.xpath("ancestor::div[@class='mat-list-item-content']/mat-icon");
|
||||
|
||||
constructor(filter) {
|
||||
this.filter = filter;
|
||||
}
|
||||
|
||||
checkTaskFilterIsDisplayed() {
|
||||
Util.waitUntilElementIsVisible(this.filter);
|
||||
return this;
|
||||
}
|
||||
|
||||
getTaskFilterIcon() {
|
||||
Util.waitUntilElementIsVisible(this.filter);
|
||||
let icon = this.filter.element(this.taskIcon);
|
||||
Util.waitUntilElementIsVisible(icon);
|
||||
return icon.getText();
|
||||
}
|
||||
|
||||
checkTaskFilterHasNoIcon() {
|
||||
Util.waitUntilElementIsVisible(this.filter);
|
||||
Util.waitUntilElementIsNotOnPage(this.filter.element(this.taskIcon));
|
||||
}
|
||||
|
||||
clickTaskFilter() {
|
||||
Util.waitUntilElementIsVisible(this.filter);
|
||||
return this.filter.click();
|
||||
}
|
||||
|
||||
checkTaskFilterNotDisplayed() {
|
||||
Util.waitUntilElementIsNotVisible(this.filter);
|
||||
return this.filter;
|
||||
}
|
||||
|
||||
}
|
43
e2e/pages/adf/process_cloud/taskListCloudComponent.ts
Normal file
43
e2e/pages/adf/process_cloud/taskListCloudComponent.ts
Normal file
@ -0,0 +1,43 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import Util = require('../../../util/util');
|
||||
import DataTablePage = require('../dataTablePage');
|
||||
import { element, by } from 'protractor';
|
||||
|
||||
export class TaskListCloudComponent {
|
||||
|
||||
taskList = element(by.css('adf-tasklist'));
|
||||
noTasksFound = element(by.css("p[class='adf-empty-content__title']"));
|
||||
|
||||
dataTable = new DataTablePage(this.taskList);
|
||||
|
||||
getDataTable() {
|
||||
return this.dataTable;
|
||||
}
|
||||
|
||||
checkTaskListIsLoaded() {
|
||||
Util.waitUntilElementIsVisible(this.taskList);
|
||||
return this;
|
||||
}
|
||||
|
||||
getNoTasksFoundMessage() {
|
||||
Util.waitUntilElementIsVisible(this.noTasksFound);
|
||||
return this.noTasksFound.getText();
|
||||
}
|
||||
|
||||
}
|
@ -17,7 +17,7 @@
|
||||
|
||||
import { LoginAPSPage } from '../pages/adf/loginApsPage';
|
||||
import { SettingsPage } from '../pages/adf/settingsPage';
|
||||
import { AppListCloudComponent } from '../pages/adf/process_services_cloud/appListCloudComponent';
|
||||
import { AppListCloudComponent } from '../pages/adf/process_cloud/appListCloudComponent';
|
||||
import TestConfig = require('../test.config');
|
||||
import { browser } from 'protractor';
|
||||
import { NavigationBarPage } from '../pages/adf/navigationBarPage';
|
||||
@ -36,7 +36,7 @@ describe('Applications list', () => {
|
||||
await settingsPage.setProviderBpmSso(TestConfig.adf.hostSso, TestConfig.adf.hostSso + path);
|
||||
browser.ignoreSynchronization = true;
|
||||
await loginApsPage.loginAPS(TestConfig.adf.adminEmail, TestConfig.adf.adminPassword);
|
||||
await navigationBarPage.clickProcessCloudButton();
|
||||
await navigationBarPage.navigateToProcessServicesCloudPage();
|
||||
|
||||
appListCloudComponent.checkApsContainer();
|
||||
|
||||
|
126
e2e/process-services-cloud/task_filters_cloud.e2e.ts
Normal file
126
e2e/process-services-cloud/task_filters_cloud.e2e.ts
Normal file
@ -0,0 +1,126 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import TestConfig = require('../test.config');
|
||||
import resources = require('../util/resources');
|
||||
|
||||
import { LoginAPSPage } from '../pages/adf/loginApsPage';
|
||||
import { SettingsPage } from '../pages/adf/settingsPage';
|
||||
import { NavigationBarPage } from '../pages/adf/navigationBarPage';
|
||||
import TasksListPage = require('../pages/adf/process_services/tasksListPage');
|
||||
import { TasksCloudDemoPage } from '../pages/adf/demo-shell/tasksCloudDemoPage';
|
||||
import { AppListCloudComponent } from '../pages/adf/process_cloud/appListCloudComponent';
|
||||
|
||||
import AlfrescoApi = require('alfresco-js-api-node');
|
||||
import { Tasks } from '../actions/APS-cloud/tasks';
|
||||
import { browser } from 'protractor';
|
||||
|
||||
describe('Task filters cloud', () => {
|
||||
|
||||
describe('Filters', () => {
|
||||
const settingsPage = new SettingsPage();
|
||||
const loginApsPage = new LoginAPSPage();
|
||||
const navigationBarPage = new NavigationBarPage();
|
||||
let appListCloudComponent = new AppListCloudComponent();
|
||||
let tasksCloudDemoPage = new TasksCloudDemoPage();
|
||||
const tasksService: Tasks = new Tasks();
|
||||
|
||||
const path = '/auth/realms/springboot';
|
||||
let silentLogin;
|
||||
const newTask = 'newTask', completedTask = 'completedTask1', myTask = 'myTask';
|
||||
const simpleApp = 'simple-app';
|
||||
const user = 'devopsuser', password = 'password';
|
||||
|
||||
beforeAll(async () => {
|
||||
silentLogin = false;
|
||||
await settingsPage.setProviderBpmSso(TestConfig.adf.hostSso, TestConfig.adf.hostSso + path, silentLogin);
|
||||
await loginApsPage.clickOnSSOButton();
|
||||
browser.ignoreSynchronization = true;
|
||||
await loginApsPage.loginAPS(user, password);
|
||||
});
|
||||
|
||||
beforeEach(async (done) => {
|
||||
await navigationBarPage.navigateToProcessServicesCloudPage();
|
||||
await appListCloudComponent.checkApsContainer();
|
||||
await appListCloudComponent.goToApp(simpleApp);
|
||||
done();
|
||||
});
|
||||
|
||||
it('[C290011] Should display default filters when an app is deployed', () => {
|
||||
tasksCloudDemoPage.cancelledTasksFilter().checkTaskFilterIsDisplayed();
|
||||
tasksCloudDemoPage.myTasksFilter().checkTaskFilterIsDisplayed();
|
||||
tasksCloudDemoPage.suspendedTasksFilter().checkTaskFilterIsDisplayed();
|
||||
tasksCloudDemoPage.completedTasksFilter().checkTaskFilterIsDisplayed();
|
||||
});
|
||||
|
||||
it('[C290009] Should display default filters and created task', async() => {
|
||||
await tasksService.init(user, password);
|
||||
await tasksService.createStandaloneTask(newTask, simpleApp);
|
||||
|
||||
tasksCloudDemoPage.suspendedTasksFilter().clickTaskFilter();
|
||||
expect(tasksCloudDemoPage.checkActiveFilterActive()).toBe('Suspended Tasks');
|
||||
tasksCloudDemoPage.taskListCloudComponent().getDataTable().checkContentIsNotDisplayed(newTask);
|
||||
|
||||
tasksCloudDemoPage.cancelledTasksFilter().clickTaskFilter();
|
||||
expect(tasksCloudDemoPage.checkActiveFilterActive()).toBe('Cancelled Tasks');
|
||||
tasksCloudDemoPage.taskListCloudComponent().getDataTable().checkContentIsNotDisplayed(newTask);
|
||||
|
||||
tasksCloudDemoPage.completedTasksFilter().clickTaskFilter();
|
||||
expect(tasksCloudDemoPage.checkActiveFilterActive()).toBe('Completed Tasks');
|
||||
tasksCloudDemoPage.taskListCloudComponent().getDataTable().checkContentIsNotDisplayed(newTask);
|
||||
|
||||
tasksCloudDemoPage.myTasksFilter().clickTaskFilter();
|
||||
expect(tasksCloudDemoPage.checkActiveFilterActive()).toBe('My Tasks');
|
||||
tasksCloudDemoPage.taskListCloudComponent().getDataTable().checkContentIsDisplayed(newTask);
|
||||
});
|
||||
|
||||
// failing due to ACTIVITI-2463
|
||||
xit('[C289955] Should display task in Complete Tasks List when task is completed', async() => {
|
||||
await tasksService.init(user, password);
|
||||
let task = await tasksService.createStandaloneTask(completedTask, simpleApp);
|
||||
|
||||
await tasksService.claimTask(task.entry.id, simpleApp);
|
||||
await tasksService.completeTask(task.entry.id, simpleApp);
|
||||
|
||||
tasksCloudDemoPage.myTasksFilter().clickTaskFilter();
|
||||
expect(tasksCloudDemoPage.checkActiveFilterActive()).toBe('My Tasks');
|
||||
tasksCloudDemoPage.taskListCloudComponent().getDataTable().checkContentIsNotDisplayed(completedTask);
|
||||
|
||||
tasksCloudDemoPage.cancelledTasksFilter().clickTaskFilter();
|
||||
expect(tasksCloudDemoPage.checkActiveFilterActive()).toBe('Cancelled Tasks');
|
||||
tasksCloudDemoPage.taskListCloudComponent().getDataTable().checkContentIsNotDisplayed(completedTask);
|
||||
|
||||
tasksCloudDemoPage.suspendedTasksFilter().clickTaskFilter();
|
||||
expect(tasksCloudDemoPage.checkActiveFilterActive()).toBe('Suspended Tasks');
|
||||
tasksCloudDemoPage.taskListCloudComponent().getDataTable().checkContentIsNotDisplayed(completedTask);
|
||||
|
||||
tasksCloudDemoPage.completedTasksFilter().clickTaskFilter();
|
||||
expect(tasksCloudDemoPage.checkActiveFilterActive()).toBe('Completed Tasks');
|
||||
tasksCloudDemoPage.taskListCloudComponent().getDataTable().checkContentIsDisplayed(completedTask);
|
||||
});
|
||||
|
||||
it('[C289957] Should display task filter results when task filter is selected', async () => {
|
||||
await tasksService.init(user, password);
|
||||
let task = await tasksService.createStandaloneTask(myTask, simpleApp);
|
||||
|
||||
tasksCloudDemoPage.myTasksFilter().clickTaskFilter();
|
||||
expect(tasksCloudDemoPage.checkActiveFilterActive()).toBe('My Tasks');
|
||||
tasksCloudDemoPage.taskListCloudComponent().getDataTable().checkContentIsDisplayed(myTask);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user