Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Jira Source Configuration and Filter Changes and add license headers #5306

Merged
merged 8 commits into from
Jan 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*
*/

package org.opensearch.dataprepper.plugins.source.jira;

import com.fasterxml.jackson.core.JsonProcessingException;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*
*/

package org.opensearch.dataprepper.plugins.source.jira;

import lombok.Getter;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*
*/

package org.opensearch.dataprepper.plugins.source.jira;


Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*
*/

package org.opensearch.dataprepper.plugins.source.jira;

import io.micrometer.core.instrument.Counter;
Expand All @@ -14,8 +24,10 @@
import javax.inject.Named;
import java.time.Instant;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Queue;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
Expand All @@ -25,9 +37,12 @@
import static org.opensearch.dataprepper.plugins.source.jira.utils.JqlConstants.DELIMITER;
import static org.opensearch.dataprepper.plugins.source.jira.utils.JqlConstants.GREATER_THAN_EQUALS;
import static org.opensearch.dataprepper.plugins.source.jira.utils.JqlConstants.ISSUE_TYPE_IN;
import static org.opensearch.dataprepper.plugins.source.jira.utils.JqlConstants.ISSUE_TYPE_NOT_IN;
import static org.opensearch.dataprepper.plugins.source.jira.utils.JqlConstants.PREFIX;
import static org.opensearch.dataprepper.plugins.source.jira.utils.JqlConstants.PROJECT_IN;
import static org.opensearch.dataprepper.plugins.source.jira.utils.JqlConstants.PROJECT_NOT_IN;
import static org.opensearch.dataprepper.plugins.source.jira.utils.JqlConstants.STATUS_IN;
import static org.opensearch.dataprepper.plugins.source.jira.utils.JqlConstants.STATUS_NOT_IN;
import static org.opensearch.dataprepper.plugins.source.jira.utils.JqlConstants.SUFFIX;


Expand Down Expand Up @@ -117,26 +132,41 @@ private void addItemsToQueue(List<IssueBean> issueList, Queue<ItemInfo> itemInfo
private StringBuilder createIssueFilterCriteria(JiraSourceConfig configuration, Instant ts) {

log.info("Creating issue filter criteria");
if (!CollectionUtils.isEmpty(JiraConfigHelper.getProjectKeyFilter(configuration))) {
if (!CollectionUtils.isEmpty(JiraConfigHelper.getProjectNameIncludeFilter(configuration)) || !CollectionUtils.isEmpty(JiraConfigHelper.getProjectNameExcludeFilter(configuration)) ) {
validateProjectFilters(configuration);
}
StringBuilder jiraQl = new StringBuilder(UPDATED + GREATER_THAN_EQUALS + ts.toEpochMilli());
if (!CollectionUtils.isEmpty(JiraConfigHelper.getProjectKeyFilter(configuration))) {
jiraQl.append(PROJECT_IN).append(JiraConfigHelper.getProjectKeyFilter(configuration).stream()
if (!CollectionUtils.isEmpty(JiraConfigHelper.getProjectNameIncludeFilter(configuration))) {
jiraQl.append(PROJECT_IN).append(JiraConfigHelper.getProjectNameIncludeFilter(configuration).stream()
.collect(Collectors.joining(DELIMITER, PREFIX, SUFFIX)))
.append(CLOSING_ROUND_BRACKET);
}
if (!CollectionUtils.isEmpty(JiraConfigHelper.getProjectNameExcludeFilter(configuration))) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think, you should also add additional validation where a project listed in include is not listed in exclude and vice versa

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will add to validate Project Filters

jiraQl.append(PROJECT_NOT_IN).append(JiraConfigHelper.getProjectNameExcludeFilter(configuration).stream()
.collect(Collectors.joining(DELIMITER, PREFIX, SUFFIX)))
.append(CLOSING_ROUND_BRACKET);
}
if (!CollectionUtils.isEmpty(JiraConfigHelper.getIssueTypeIncludeFilter(configuration))) {
jiraQl.append(ISSUE_TYPE_IN).append(JiraConfigHelper.getIssueTypeIncludeFilter(configuration).stream()
.collect(Collectors.joining(DELIMITER, PREFIX, SUFFIX)))
.append(CLOSING_ROUND_BRACKET);
}
if (!CollectionUtils.isEmpty(JiraConfigHelper.getIssueTypeExcludeFilter(configuration))) {
jiraQl.append(ISSUE_TYPE_NOT_IN).append(JiraConfigHelper.getIssueTypeExcludeFilter(configuration).stream()
.collect(Collectors.joining(DELIMITER, PREFIX, SUFFIX)))
.append(CLOSING_ROUND_BRACKET);
}
if (!CollectionUtils.isEmpty(JiraConfigHelper.getIssueTypeFilter(configuration))) {
jiraQl.append(ISSUE_TYPE_IN).append(JiraConfigHelper.getIssueTypeFilter(configuration).stream()
if (!CollectionUtils.isEmpty(JiraConfigHelper.getIssueStatusIncludeFilter(configuration))) {
jiraQl.append(STATUS_IN).append(JiraConfigHelper.getIssueStatusIncludeFilter(configuration).stream()
.collect(Collectors.joining(DELIMITER, PREFIX, SUFFIX)))
.append(CLOSING_ROUND_BRACKET);
}
if (!CollectionUtils.isEmpty(JiraConfigHelper.getIssueStatusFilter(configuration))) {
jiraQl.append(STATUS_IN).append(JiraConfigHelper.getIssueStatusFilter(configuration).stream()
if (!CollectionUtils.isEmpty(JiraConfigHelper.getIssueStatusExcludeFilter(configuration))) {
jiraQl.append(STATUS_NOT_IN).append(JiraConfigHelper.getIssueStatusExcludeFilter(configuration).stream()
.collect(Collectors.joining(DELIMITER, PREFIX, SUFFIX)))
.append(CLOSING_ROUND_BRACKET);
}
log.trace("Created issue filter criteria JiraQl query: {}", jiraQl);
log.error("Created issue filter criteria JiraQl query: {}", jiraQl);
return jiraQl;
}

Expand All @@ -148,9 +178,21 @@ private StringBuilder createIssueFilterCriteria(JiraSourceConfig configuration,
private void validateProjectFilters(JiraSourceConfig configuration) {
log.trace("Validating project filters");
List<String> badFilters = new ArrayList<>();
Set<String> includedProjects = new HashSet<>();
List<String> includedAndExcludedProjects = new ArrayList<>();
Pattern regex = Pattern.compile("[^A-Z0-9]");
JiraConfigHelper.getProjectKeyFilter(configuration).forEach(projectFilter -> {
JiraConfigHelper.getProjectNameIncludeFilter(configuration).forEach(projectFilter -> {
Matcher matcher = regex.matcher(projectFilter);
includedProjects.add(projectFilter);
if (matcher.find() || projectFilter.length() <= 1 || projectFilter.length() > 10) {
badFilters.add(projectFilter);
}
});
JiraConfigHelper.getProjectNameExcludeFilter(configuration).forEach(projectFilter -> {
Matcher matcher = regex.matcher(projectFilter);
if (includedProjects.contains(projectFilter)) {
includedAndExcludedProjects.add(projectFilter);
}
if (matcher.find() || projectFilter.length() <= 1 || projectFilter.length() > 10) {
badFilters.add(projectFilter);
}
Expand All @@ -162,6 +204,14 @@ private void validateProjectFilters(JiraSourceConfig configuration) {
"Invalid project key found in filter configuration for "
+ filters);
}
if (!includedAndExcludedProjects.isEmpty()) {
String filters = String.join("\"" + includedAndExcludedProjects + "\"", ", ");
log.error("One or more project keys found in both include and exclude: {}", includedAndExcludedProjects);
throw new BadRequestException("Bad request exception occurred " +
"Project filters is invalid because the following projects are listed in both include and exclude"
+ filters);
}

}

}
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*
*/

package org.opensearch.dataprepper.plugins.source.jira;


Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*
*/

package org.opensearch.dataprepper.plugins.source.jira;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please add the license header. This should be added to all the files.

See: https://github.com/opensearch-project/data-prepper/blob/main/CONTRIBUTING.md#license-headers

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added to all jira source files


import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.validation.constraints.Size;
import jakarta.validation.Valid;
import lombok.Getter;
import org.opensearch.dataprepper.plugins.source.jira.configuration.AuthenticationConfig;
import org.opensearch.dataprepper.plugins.source.jira.configuration.FilterConfig;
import org.opensearch.dataprepper.plugins.source.source_crawler.base.CrawlerSourceConfig;

import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import static org.opensearch.dataprepper.plugins.source.jira.utils.Constants.OAUTH2;

@Getter
public class JiraSourceConfig implements CrawlerSourceConfig {
Expand All @@ -20,50 +28,23 @@ public class JiraSourceConfig implements CrawlerSourceConfig {
/**
* Jira account url
*/
@JsonProperty("account_url")
private String accountUrl;
@JsonProperty("hosts")
private List<String> hosts;

/**
* A map of connector credentials specific to this source
* Authentication Config to Access Jira
*/
@JsonProperty("connector_credentials")
private Map<String, String> connectorCredentials;
@JsonProperty("authentication")
@Valid
private AuthenticationConfig authenticationConfig;

/**
* List of projects to ingest
*/
@JsonProperty("projects")
@Size(max = 1000, message = "Project type filter should not be more than 1000")
private List<String> project = new ArrayList<>();

/**
* List of specific issue types to ingest.
* Ex: Story, Epic, Task etc
* Filter Config to filter what tickets get ingested
*/
@JsonProperty("issue_types")
@Size(max = 1000, message = "Issue type filter should be less than 1000")
private List<String> issueType = new ArrayList<>();
@JsonProperty("filter")
private FilterConfig filterConfig;

/**
* Optional Inclusion patterns for filtering some tickets
*/
@JsonProperty("inclusion_patterns")
@Size(max = 100, message = "inclusion pattern filters should not be more than 1000")
private List<String> inclusionPatterns;

/**
* Optional Exclusion patterns for excluding some tickets
*/
@JsonProperty("exclusion_patterns")
@Size(max = 1000, message = "exclusion pattern filter should be less than 1000")
private List<String> exclusionPatterns;

/**
* Optional Status filter to ingest the tickets
*/
@JsonProperty("statuses")
@Size(max = 1000, message = "Status filter should be less than 1000")
private List<String> status = new ArrayList<>();

/**
* Number of worker threads to spawn to parallel source fetching
Expand All @@ -78,43 +59,11 @@ public class JiraSourceConfig implements CrawlerSourceConfig {
@JsonProperty("backoff_time")
private Duration backOff = DEFAULT_BACKOFF_MILLIS;

public String getJiraId() {
return this.getConnectorCredentials().get("jira_id");
}

public String getJiraCredential() {
return this.getConnectorCredentials().get("jira_credential");
public String getAccountUrl() {
return this.getHosts().get(0);
}

public String getAuthType() {
return this.getConnectorCredentials().get("auth_type");
}

public String getAccessToken() {
return fetchGivenOAuthAttribute("access_token");
}

public String getRefreshToken() {
return fetchGivenOAuthAttribute("refresh_token");
}

public String getClientId() {
return fetchGivenOAuthAttribute("client_id");
}

public String getClientSecret() {
return fetchGivenOAuthAttribute("client_secret");
return this.getAuthenticationConfig().getAuthType();
}

private String fetchGivenOAuthAttribute(String givenAttribute) {
if (!OAUTH2.equals(getAuthType())) {
throw new RuntimeException("Authentication Type is not OAuth2.");
}
String attributeValue = this.getConnectorCredentials().get(givenAttribute);
if (attributeValue == null || attributeValue.isEmpty()) {
throw new RuntimeException(String.format("%s is required for OAuth2 AuthType", givenAttribute));
}
return attributeValue;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*
*/

package org.opensearch.dataprepper.plugins.source.jira.configuration;

import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.validation.Valid;
import jakarta.validation.constraints.AssertTrue;
import lombok.Getter;

import static org.opensearch.dataprepper.plugins.source.jira.utils.Constants.BASIC;
import static org.opensearch.dataprepper.plugins.source.jira.utils.Constants.OAUTH2;

@Getter
public class AuthenticationConfig {
@JsonProperty("basic")
@Valid
private BasicConfig basicConfig;

@JsonProperty("oauth2")
@Valid
private Oauth2Config oauth2Config;

@AssertTrue(message = "Authentication config should have either basic or oauth2")
private boolean isValidAuthenticationConfig() {
boolean hasBasic = basicConfig != null;
boolean hasOauth = oauth2Config != null;
return hasBasic ^ hasOauth;
}

public String getAuthType() {
if (basicConfig != null) {
return BASIC;
} else {
return OAUTH2;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*
*/

package org.opensearch.dataprepper.plugins.source.jira.configuration;

import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.validation.constraints.AssertTrue;
import lombok.Getter;

@Getter
public class BasicConfig {
@JsonProperty("username")
private String username;

@JsonProperty("password")
private String password;

@AssertTrue(message = "Username and Password are both required for Basic Auth")
private boolean isBasicConfigValid() {
return username != null && password != null;
}
}
Loading
Loading