Skip to content

Commit

Permalink
EPMRPP-94816 || Implement functionality to add Assignee for issue by …
Browse files Browse the repository at this point in the history
…typing its name
  • Loading branch information
APiankouski authored Sep 3, 2024
1 parent e2f46f7 commit 8d88760
Show file tree
Hide file tree
Showing 9 changed files with 283 additions and 20 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ on:

env:
GH_USER_NAME: github.actor
SCRIPTS_VERSION: 5.10.0
BOM_VERSION: 5.11.0
SCRIPTS_VERSION: 5.12.0
BOM_VERSION: 5.12.0

jobs:
release:
Expand Down
6 changes: 3 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ dependencies {
implementation 'com.epam.reportportal:plugin-api'
annotationProcessor 'com.epam.reportportal:plugin-api'
} else {
implementation 'com.github.reportportal:commons-dao:823f284'
implementation 'com.github.reportportal:plugin-api:a23eec0'
annotationProcessor 'com.github.reportportal:plugin-api:a23eec0'
implementation 'com.github.reportportal:commons-dao:5807fb1'
implementation 'com.github.reportportal:plugin-api:a9a8b73'
annotationProcessor 'com.github.reportportal:plugin-api:a9a8b73'
}
implementation 'org.hibernate:hibernate-core:5.6.15.Final'

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import com.epam.reportportal.extension.jira.command.GetIssueTypesCommand;
import com.epam.reportportal.extension.jira.command.PostTicketCommand;
import com.epam.reportportal.extension.jira.command.RetrieveCreationParamsCommand;
import com.epam.reportportal.extension.jira.command.UserSearchCommand;
import com.epam.reportportal.extension.jira.command.atlassian.CloudJiraClientProviderExtended;
import com.epam.reportportal.extension.jira.command.RetrieveUpdateParamsCommand;
import com.epam.reportportal.extension.jira.command.connection.TestConnectionCommand;
Expand Down Expand Up @@ -231,6 +232,7 @@ private Map<String, CommonPluginCommand<?>> getCommonCommands() {

private Map<String, PluginCommand<?>> getCommands() {
List<PluginCommand<?>> commands = new ArrayList<>();
commands.add(new UserSearchCommand(projectRepository, cloudJiraClientProviderSupplier.get()));
commands.add(new TestConnectionCommand(cloudJiraClientProviderSupplier.get()));
commands.add(new GetIssueFieldsCommand(projectRepository, cloudJiraClientProviderExtendedSupplier.get()));
commands.add(new GetIssueTypesCommand(projectRepository, cloudJiraClientProviderSupplier.get()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import com.epam.ta.reportportal.entity.integration.Integration;
import com.epam.reportportal.rules.exception.ReportPortalException;
import com.epam.reportportal.rules.exception.ErrorType;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -96,6 +97,7 @@ protected List<PostFormField> invokeCommand(Integration integration, Map<String,
String fieldType = issueField.getSchema().getType();
boolean isRequired = issueField.isRequired();
List<AllowedValue> allowed = new ArrayList<>();
String commandName = null;

// Provide values for custom fields with predefined options
if (issueField.getAllowedValues() != null) {
Expand Down Expand Up @@ -140,6 +142,7 @@ protected List<PostFormField> invokeCommand(Integration integration, Map<String,
}
if (fieldID.equalsIgnoreCase(IssueFieldId.ASSIGNEE_FIELD.id)) {
allowed = getJiraProjectAssignee(jiraProject);
commandName = "searchUsers";
}

//@formatter:off
Expand All @@ -156,7 +159,12 @@ protected List<PostFormField> invokeCommand(Integration integration, Map<String,
continue;
}

result.add(new PostFormField(fieldID, fieldName, fieldType, isRequired, defValue, allowed));
PostFormField postForm = new PostFormField(fieldID, fieldName, fieldType, isRequired, defValue,
allowed);
if (StringUtils.isNotEmpty(commandName)) {
postForm.setCommandName(commandName);
}
result.add(postForm);
}
return result;
} catch (Exception e) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright 2024 EPAM Systems
*
* 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.
*/
package com.epam.reportportal.extension.jira.command;

import static java.util.Optional.ofNullable;

import com.atlassian.jira.rest.client.api.domain.User;
import com.epam.reportportal.extension.ProjectMemberCommand;
import com.epam.reportportal.extension.jira.command.atlassian.AsynchronousUserRestClientExtended;
import com.epam.reportportal.extension.jira.command.utils.CloudJiraClientProvider;
import com.epam.reportportal.extension.jira.dto.UserDto;
import com.epam.reportportal.extension.jira.utils.UserMapper;
import com.epam.ta.reportportal.dao.ProjectRepository;
import com.epam.ta.reportportal.entity.integration.Integration;
import com.epam.ta.reportportal.entity.integration.IntegrationParams;
import java.util.List;
import java.util.Map;

/**
* @author <a href="mailto:[email protected]">Andrei Piankouski</a>
*/
public class UserSearchCommand extends ProjectMemberCommand<List<UserDto>> {

private static final String SEARCH_TERM = "term";
private final CloudJiraClientProvider cloudJiraClientProvider;

public UserSearchCommand(ProjectRepository projectRepository,
CloudJiraClientProvider cloudJiraClientProvider) {
super(projectRepository);
this.cloudJiraClientProvider = cloudJiraClientProvider;
}

@Override
protected List<UserDto> invokeCommand(Integration integration, Map<String, Object> params) {
IntegrationParams integrationParams = integration.getParams();
String query = (String) ofNullable(params.get(SEARCH_TERM)).orElse("");
AsynchronousUserRestClientExtended userClient;
userClient = cloudJiraClientProvider.createUserClient(integrationParams);
Iterable<User> users = userClient.findUsers(query).claim();
return UserMapper.toUserDtoList(users);
}

@Override
public String getName() {
return "searchUsers";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* Copyright 2024 EPAM Systems
*
* 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.
*/
package com.epam.reportportal.extension.jira.command.atlassian;

import com.atlassian.httpclient.api.HttpClient;
import com.atlassian.jira.rest.client.api.domain.User;
import com.atlassian.jira.rest.client.internal.async.AbstractAsynchronousRestClient;
import com.atlassian.jira.rest.client.internal.json.UsersJsonParser;
import io.atlassian.util.concurrent.Promise;
import java.net.URI;
import javax.annotation.Nullable;
import javax.ws.rs.core.UriBuilder;

/**
* @author <a href="mailto:[email protected]">Andrei Piankouski</a>
*/
public class AsynchronousUserRestClientExtended extends AbstractAsynchronousRestClient {

private static final String USER_URI_PREFIX = "user";
private static final String SEARCH_URI_PREFIX = "search";

private static final String QUERY_ATTRIBUTE = "query";
private static final String START_AT_ATTRIBUTE = "startAt";
private static final String MAX_RESULTS_ATTRIBUTE = "maxResults";
private static final String INCLUDE_ACTIVE_ATTRIBUTE = "includeActive";
private static final String INCLUDE_INACTIVE_ATTRIBUTE = "includeInactive";

private final UsersJsonParser usersJsonParser = new UsersJsonParser();

private final URI baseUri;

public AsynchronousUserRestClientExtended(URI serverUri,
HttpClient client) {
super(client);
this.baseUri = UriBuilder.fromUri(serverUri).path("/rest/api/latest").build();
}

public Promise<Iterable<User>> findUsers(String username) {
return findUsers(username, null, null, null, null);
}

public Promise<Iterable<User>> findUsers(String username, @Nullable Integer startAt,
@Nullable Integer maxResults, @Nullable Boolean includeActive,
@Nullable Boolean includeInactive) {
UriBuilder uriBuilder = UriBuilder.fromUri(baseUri).path(USER_URI_PREFIX).path(SEARCH_URI_PREFIX)
.queryParam(QUERY_ATTRIBUTE, username);

addOptionalQueryParam(uriBuilder, START_AT_ATTRIBUTE, startAt);
addOptionalQueryParam(uriBuilder, MAX_RESULTS_ATTRIBUTE, maxResults);
addOptionalQueryParam(uriBuilder, INCLUDE_ACTIVE_ATTRIBUTE, includeActive);
addOptionalQueryParam(uriBuilder, INCLUDE_INACTIVE_ATTRIBUTE, includeInactive);
addOptionalQueryParam(uriBuilder, INCLUDE_INACTIVE_ATTRIBUTE, includeInactive);
addOptionalQueryParam(uriBuilder, INCLUDE_INACTIVE_ATTRIBUTE, includeInactive);

final URI usersUri = uriBuilder.build();
return getAndParse(usersUri, usersJsonParser);
}

private void addOptionalQueryParam(final UriBuilder uriBuilder, final String key, final Object value) {
if (value != null) {
uriBuilder.queryParam(key, value);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@
package com.epam.reportportal.extension.jira.command.utils;

import com.atlassian.jira.rest.client.api.JiraRestClient;
import com.atlassian.jira.rest.client.auth.BasicHttpAuthenticationHandler;
import com.atlassian.jira.rest.client.internal.async.AsynchronousHttpClientFactory;
import com.atlassian.jira.rest.client.internal.async.AsynchronousJiraRestClientFactory;
import com.atlassian.jira.rest.client.internal.async.DisposableHttpClient;
import com.epam.reportportal.extension.jira.command.atlassian.AsynchronousUserRestClientExtended;
import com.epam.ta.reportportal.entity.integration.IntegrationParams;
import com.epam.reportportal.rules.exception.ReportPortalException;
import com.epam.reportportal.rules.exception.ErrorType;
Expand All @@ -31,26 +35,55 @@ public class CloudJiraClientProvider {

protected final BasicTextEncryptor textEncryptor;

private static final String USER_EMAIL_NOT_SPECIFIED = "User email is not specified.";
private static final String API_TOKEN_NOT_SPECIFIED = "Api token is not specified.";
private static final String URL_NOT_SPECIFIED = "Url to the Cloud Jira is not specified.";

public CloudJiraClientProvider(BasicTextEncryptor textEncryptor) {
this.textEncryptor = textEncryptor;
}

public JiraRestClient get(IntegrationParams integrationParams) {
String providedUsername = CloudJiraProperties.EMAIL.getParam(integrationParams).orElseThrow(
() -> new ReportPortalException(ErrorType.UNABLE_INTERACT_WITH_INTEGRATION,
"User email is not specified."
));
String credentials = textEncryptor.decrypt(
CloudJiraProperties.API_TOKEN.getParam(integrationParams).orElseThrow(
() -> new ReportPortalException(ErrorType.UNABLE_INTERACT_WITH_INTEGRATION,
"Api token is not specified."
)));
String url = CloudJiraProperties.URL.getParam(integrationParams).orElseThrow(
() -> new ReportPortalException(ErrorType.UNABLE_INTERACT_WITH_INTEGRATION,
"Url to the Cloud Jira is not specified."
));
CloudJiraDetails details = extractAndDecryptDetails(integrationParams);
return new AsynchronousJiraRestClientFactory().createWithBasicHttpAuthentication(
URI.create(url), providedUsername, credentials);
URI.create(details.url), details.providedUsername, details.credentials);
}

public AsynchronousUserRestClientExtended createUserClient(IntegrationParams integrationParams) {
CloudJiraDetails details = extractAndDecryptDetails(integrationParams);
DisposableHttpClient httpClient = new AsynchronousHttpClientFactory()
.createClient(URI.create(details.url),
new BasicHttpAuthenticationHandler(details.providedUsername, details.credentials));
return new AsynchronousUserRestClientExtended(URI.create(details.url), httpClient);
}

private CloudJiraDetails extractAndDecryptDetails(IntegrationParams integrationParams) {
String providedUsername = getTextParamOrThrow(CloudJiraProperties.EMAIL, integrationParams,
USER_EMAIL_NOT_SPECIFIED);
String credentials = textEncryptor.decrypt(
getTextParamOrThrow(CloudJiraProperties.API_TOKEN, integrationParams,
API_TOKEN_NOT_SPECIFIED));
String url = getTextParamOrThrow(CloudJiraProperties.URL, integrationParams, URL_NOT_SPECIFIED);
return new CloudJiraDetails(providedUsername, credentials, url);
}

private String getTextParamOrThrow(CloudJiraProperties param, IntegrationParams params,
String errorMessage) {
return param.getParam(params).orElseThrow(
() -> new ReportPortalException(ErrorType.UNABLE_INTERACT_WITH_INTEGRATION, errorMessage));
}

private static class CloudJiraDetails {

final String providedUsername;
final String credentials;
final String url;

CloudJiraDetails(String providedUsername, String credentials, String url) {
this.providedUsername = providedUsername;
this.credentials = credentials;
this.url = url;
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright 2024 EPAM Systems
*
* 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.
*/
package com.epam.reportportal.extension.jira.dto;

/**
* @author <a href="mailto:[email protected]">Andrei Piankouski</a>
*/
public class UserDto {

private String id;
private String name;

public UserDto() {
}

public String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright 2024 EPAM Systems
*
* 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.
*/
package com.epam.reportportal.extension.jira.utils;

import com.atlassian.jira.rest.client.api.domain.User;
import com.epam.reportportal.extension.jira.dto.UserDto;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

/**
* @author <a href="mailto:[email protected]">Andrei Piankouski</a>
*/
public class UserMapper {

public static List<UserDto> toUserDtoList(Iterable<User> users) {
return StreamSupport.stream(users.spliterator(), false)
.map(user -> {
UserDto dto = new UserDto();
dto.setId(user.getAccountId());
dto.setName(user.getDisplayName());
return dto;
})
.collect(Collectors.toList());
}
}

0 comments on commit 8d88760

Please sign in to comment.