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

Number of open issue probe #353

Open
wants to merge 50 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
2773dcf
Fetched issue tracker from update center
Jagrutiti Jul 23, 2023
68aa505
Removed issueTracker from models
Jagrutiti Jul 24, 2023
54a82a3
Modified the existing test cases that were affected and updated then …
Jagrutiti Jul 24, 2023
abb1001
Fixing checkstyle issues
Jagrutiti Jul 24, 2023
4d8ae5d
Fixing checkstyle issues
Jagrutiti Jul 24, 2023
c4c1605
Updated probe names and added java docs
Jagrutiti Jul 24, 2023
99d9cf5
Added test and code to count open GitHub issues
Jagrutiti Jul 29, 2023
719c57d
Added test code and completed the test case
Jagrutiti Jul 30, 2023
925afa3
Removed conficting dependencies
Jagrutiti Jul 30, 2023
58b135a
Fixed incorrect artifact name
Jagrutiti Jul 30, 2023
c71a32a
Merge branch 'main' into feature/open-issue-probe
Jagrutiti Jul 30, 2023
5fd8b2d
Fixed JIRA API calling
Jagrutiti Aug 1, 2023
6f9ede3
Merge branch 'main' into feature/open-issue-probe
Jagrutiti Aug 1, 2023
1a54932
Added IssueTrackerDetectionProbe
Jagrutiti Aug 2, 2023
4a144e5
Update IssueTrackerDetectionProbe and added a test for the same
Jagrutiti Aug 5, 2023
d3fd18b
Added abstract probe class and jira and github issues count probe
Jagrutiti Aug 5, 2023
d960448
Fixing checkstyle issues
Jagrutiti Aug 5, 2023
eda1b13
Fixed the test case for open jira issues count
Jagrutiti Aug 6, 2023
97aed28
Removing the issueTracker field from update center
Jagrutiti Aug 6, 2023
528ab43
Added a narrowed depedency in pom file and removed unwanted mock in …
Jagrutiti Aug 6, 2023
5f22f38
Added more test case for IssueTrackerDetectionProbe
Jagrutiti Aug 6, 2023
e13f3a8
Fixed checkstyle issues
Jagrutiti Aug 6, 2023
9bcb49e
Adding more test case for jira open issues
Jagrutiti Aug 6, 2023
4989e81
Apply suggestions from code review
Jagrutiti Aug 8, 2023
7db7e7a
Fixed the abstract class and updated test cases
Jagrutiti Aug 9, 2023
e45ce77
Updated the test cases
Jagrutiti Aug 9, 2023
510d340
Modifying the abstract class to make it better
Jagrutiti Aug 9, 2023
3cf7406
Refactored code in GitHub open issues probe
Jagrutiti Aug 12, 2023
b9cda9c
Restructured IssueTrackerDetectionProbe and improved its readability
Jagrutiti Aug 13, 2023
37884d5
Added a new test case for IssueTrackerDertectionProbe
Jagrutiti Aug 13, 2023
898c86c
Refactored code, test cases, fixed border cases
Jagrutiti Aug 13, 2023
fdd1a4e
Updating IssueTrackerDetectionProbe ORDER
Jagrutiti Aug 13, 2023
1940e67
Fixed method names
Jagrutiti Aug 14, 2023
151df0d
Merge branch 'main' into feature/open-issue-probe
Jagrutiti Aug 14, 2023
12ef4aa
Removed unused imports
Jagrutiti Aug 14, 2023
7f6cb04
Merge branch 'feature/open-issue-probe' of https://github.com/Jagruti…
Jagrutiti Aug 14, 2023
6a7214a
Fixed JavaDocs
Jagrutiti Aug 15, 2023
8e0f12a
Update core/src/main/java/io/jenkins/pluginhealth/scoring/probes/GitH…
Jagrutiti Aug 19, 2023
12db24b
Removed unwanted dependencies and fixed log statements
Jagrutiti Aug 19, 2023
793114b
Adding a comment
Jagrutiti Aug 22, 2023
e13b17e
Update core/src/test/java/io/jenkins/pluginhealth/scoring/probes/GitH…
Jagrutiti Aug 23, 2023
f4ef76e
Made minor changes based on code review
Jagrutiti Aug 23, 2023
1e194e9
Merge branch 'feature/open-issue-probe' of https://github.com/Jagruti…
Jagrutiti Aug 23, 2023
d9fad0c
Adding null checks
Jagrutiti Aug 24, 2023
663282c
Apply suggestions from code review
Jagrutiti Sep 6, 2023
c099e40
Fixed foramatting, updated library uses based on code review
Jagrutiti Sep 6, 2023
ec28408
Updated syntax for null check
Jagrutiti Sep 6, 2023
6a740d2
Merge branch 'main' into feature/open-issue-probe
Jagrutiti Sep 7, 2023
9244d60
Fixing merge issues
Jagrutiti Sep 7, 2023
b722a87
Fixing checkstyle issues
Jagrutiti Sep 7, 2023
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
6 changes: 6 additions & 0 deletions core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -102,5 +102,11 @@
<artifactId>postgresql</artifactId>
<scope>test</scope>
</dependency>
<!-- Used to invoke API calls -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.14</version>
</dependency>
Copy link
Collaborator

Choose a reason for hiding this comment

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

What's your rationale for using this Apache httpclient instead of the Java HttpClient for example?

Copy link
Member Author

Choose a reason for hiding this comment

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

Apache HTTPClient is an additional dependency. While I can do the same thing using Java HTTPClient. So I shouldn't be using it.

</dependencies>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,33 @@

public record Plugin(String name, VersionNumber version, String scm,
ZonedDateTime releaseTimestamp, List<String> labels,
int popularity, String requiredCore, String defaultBranch) {
int popularity, String requiredCore, String defaultBranch, List<IssueTrackers> issueTrackers) {

/* This constructor is used in test cases
Jagrutiti marked this conversation as resolved.
Show resolved Hide resolved
when List<IssueTrackers> is not required as a parameter.
*/
public static Plugin of(String name, VersionNumber version, String scm,
ZonedDateTime releaseTimestamp, List<String> labels,
int popularity, String requiredCore, String defaultBranch) {
return new Plugin(name, version, scm, releaseTimestamp,
labels, popularity, requiredCore, defaultBranch, List.of());
}

public io.jenkins.pluginhealth.scoring.model.Plugin toPlugin() {
return new io.jenkins.pluginhealth.scoring.model.Plugin(this.name(), this.version(), this.scm(), this.releaseTimestamp());
}

public List<IssueTrackers> getIssueTrackers() {
return this.issueTrackers;
}

Jagrutiti marked this conversation as resolved.
Show resolved Hide resolved
/**
* Gets issue tracker details about a plugin.
*
* @param type The type of platform used to track issues. For ex: GitHub, JIRA
* @param reportUrl An url to report issues about the plugin
* @param viewUrl An url to view all the issues in the plugin
*/
public record IssueTrackers(String type, String viewUrl, String reportUrl) {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* MIT License
*
* Copyright (c) 2023 Jenkins Infra
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

package io.jenkins.pluginhealth.scoring.probes;

import java.util.Optional;

import io.jenkins.pluginhealth.scoring.model.Plugin;
import io.jenkins.pluginhealth.scoring.model.ProbeResult;

public abstract class AbstractOpenIssuesProbe extends Probe {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Hey @Jagrutiti,

There is something wrong with that class which comes from its design.

On purpose, I won't give you more indication for now, to let you figure it out by yourself.

Please have a look at other similar classes in the project, and please read more about the purpose of abstract classes, and the general purpose of abstraction in OOP.

You already implemented similar kind of classes in previous PRs, but perhaps the extra guidance we provided made that you did not get to understand the reason why we recommended this design, which is why this time I won't share much details (yet) and let you take some time to think about the design choices and understand the reason for them.

Copy link
Member Author

Choose a reason for hiding this comment

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

Hi @aneveux ,

I did read about implementing an abstract class in OOPS.

The articles spoke of data abstraction and functional abstraction. In our case I believe, we are doing functional abstraction.

Having two different methods is important here because the implementation to count issues from both JIRA and GitHub have nothing in common.

I had done a similar implementation in AbstractDependencyBotConfigurationProbe but in that case RenovateProbe and DependabotProbe had the same job. Look for files only with different names.

The next point that I come across is why abstract classes are used. They are designed for:

  • hide complex functionality
  • code reusability

The abstract classes that I implemented did provide code reusability and their functionalities were hidden from the child class. They only had to implement the abstract class.

In the NumberofOpenIssuesProbe code reusability is not possible because the libraries and API used in both cases are different.

About the design issues, JIRA_HOST and restTemplate are used in only case of JIRA issues.

Maybe I should change the JIRA_HOST to HOST_URL and that should be assigned by all the sub-classes.

Maybe JIRA_HOST and restTemplate should be assigned using a constructor in the subclasses?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Having two different methods is important here because the implementation to count issues from both JIRA and GitHub have nothing in common.

Let me just say, what if tomorrow there is a third issue tracker used in the Jenkins project, what would we have to add or changed in here to make it fit?

In OOP, it should require a minimal addition, very specific.

If you look at the DependencyBot abstract class, how are the different bot handled?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Hey @Jagrutiti 👋

Sorry for the late reply, I'm out of the office right now and spend less time on the computer 😝

Adrien actually gave a nice indication regarding the design: what happens if you had to add a third issue tracker?

The idea behind code abstraction is that the abstract class that you write should not know at all about its possible implementations. In your case, not only your abstract class knows about Jira and GitHub, but it even provides code for it.

An abstract class should contain all the code that is supposed to be common between all of its subclasses, and no more. If there is some code that is used by only one of the subclasses, it is rarely a good fit for the abstract class.

That being said though, maybe while implementing your code you will notice that there is nothing to share between the two classes that you write because they are too different from each other, and in that case, it would be better to challenge the need for abstraction, and simply write 2 classes that are doing their job.

Introducing abstraction just for the sake of it just makes the source code more complicated, hence less readable.

It is tempting to want to use good practices like abstraction while writing code when we manipulate things that seem similar, but it is important to check that they are indeed similar.

Hope that helps you understand my concern with the class design.

Copy link
Member Author

Choose a reason for hiding this comment

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

Thank you for explaining @aneveux.

In past cases when I used abstract classes. 99% of the code was common.

In this case, there was no common code. Maybe I should have asked this in the meeting itself but it did not occur to me then.

I should have taken inspiration from doApply abstract method in Probe class. But I did not understand the "why" part until I made this blunder.

Copy link
Collaborator

Choose a reason for hiding this comment

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

That's alright @Jagrutiti =) We learn by making mistakes, and if you never experienced such a case before, it would have been difficult to think about it and ask a question during the meeting.

If you understand it better now, then that's great =)

public static final int ORDER = IssueTrackerDetectionProbe.ORDER + 100;

@Override
protected ProbeResult doApply(Plugin plugin, ProbeContext context) {
final Optional<Integer> openIssuesCount = getCountOfOpenIssues(context);
return openIssuesCount.isPresent()
? ProbeResult.success(key(), String.format("%d open issues found in the %s plugin.", openIssuesCount.get(), plugin.getName()))
: ProbeResult.failure(key(), String.format("Could not find open issues in the %s plugin.", plugin.getName()));
}

abstract Optional<Integer> getCountOfOpenIssues(ProbeContext context);

@Override
public String[] getProbeResultRequirement() {
return new String[] { SCMLinkValidationProbe.KEY, IssueTrackerDetectionProbe.KEY };
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* MIT License
*
* Copyright (c) 2023 Jenkins Infra
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

package io.jenkins.pluginhealth.scoring.probes;

import java.io.IOException;
import java.util.Optional;

import org.kohsuke.github.GHRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Component
@Order(AbstractOpenIssuesProbe.ORDER)
class GitHubOpenIssuesProbe extends AbstractOpenIssuesProbe {
public static final String KEY = "github-open-issues";
private static final Logger LOGGER = LoggerFactory.getLogger(GitHubOpenIssuesProbe.class);

/**
* Get total number of open GitHub issues in a plugin.
*
* @param context {@link ProbeContext}
* @return Optional an Integer value will give total count of open issues.
*/
@Override
Optional<Integer> getCountOfOpenIssues(ProbeContext context) {
/* Stores the GitHub URL to view all existing issues in the plugin. Ex: https://github.com/jenkinsci/cloudevents-plugin/issues */
String issueTrackerViewUrl = context.getIssueTrackerUrlsByNames().get("github");
Copy link
Collaborator

Choose a reason for hiding this comment

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

context.getIssueTrackerUrlsByNames() cannot be null right?

If you are not sure, you can add a first check to validate that the plugin has an issue tracker configured? (a simple if before trying to .get("github");)

Copy link
Member Author

Choose a reason for hiding this comment

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

IssueTrackerDetectionProbe is the requirement for GitHubOpenIssuesProbe and UpdateCenterPluginPublicationProbe is the requirement for IssueTrackerDetectionProbe that is why a part of my believes that this issue might not occur.

It is better to be safe than sorry.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I like the "better safe than sorry" principal. 👍


if (issueTrackerViewUrl == null) {
LOGGER.info("The plugin does not use GitHub issues to tracker issues.");
Jagrutiti marked this conversation as resolved.
Show resolved Hide resolved
return Optional.empty();
}

try {
final Optional<String> repositoryName = context.getRepositoryName(issueTrackerViewUrl.substring(0, issueTrackerViewUrl.lastIndexOf("/")));
if (repositoryName.isPresent()) {
final GHRepository ghRepository = context.getGitHub().getRepository(repositoryName.get());
return Optional.of(ghRepository.getOpenIssueCount());
}
} catch (IOException ex) {
LOGGER.error("Cannot read open issues on GitHub for the plugin. {}", ex);
Jagrutiti marked this conversation as resolved.
Show resolved Hide resolved
}
return Optional.empty();
}

@Override
public String key() {
return KEY;
}

@Override
public String getDescription() {
return "Returns the total number of open issues in GitHub.";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* MIT License
*
* Copyright (c) 2023 Jenkins Infra
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

package io.jenkins.pluginhealth.scoring.probes;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import io.jenkins.pluginhealth.scoring.model.Plugin;
import io.jenkins.pluginhealth.scoring.model.ProbeResult;
import io.jenkins.pluginhealth.scoring.model.updatecenter.Plugin.IssueTrackers;
import io.jenkins.pluginhealth.scoring.model.updatecenter.UpdateCenter;

import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Component
@Order(value = IssueTrackerDetectionProbe.ORDER)
class IssueTrackerDetectionProbe extends Probe {
public static final String KEY = "issue-tracker-detection";
public static final int ORDER = UpdateCenterPluginPublicationProbe.ORDER + 100;

@Override
protected ProbeResult doApply(Plugin plugin, ProbeContext context) {
Map<String, String> issueTrackerDetails = getIssueTrackersForAPlugin(plugin.getName(), context.getUpdateCenter());

if (issueTrackerDetails.isEmpty()) {
return ProbeResult.failure(key(), String.format("No issue tracker data available for %s plugin in Update Center.", plugin.getName()));
}
context.setIssueTrackerNameAndUrl(issueTrackerDetails);
return ProbeResult.success(key(), "Issue tracker detected and returned successfully.");
Jagrutiti marked this conversation as resolved.
Show resolved Hide resolved
}

@Override
public String[] getProbeResultRequirement() {
return new String[]{UpdateCenterPluginPublicationProbe.KEY};
}

@Override
public String key() {
return KEY;
}

@Override
public String getDescription() {
return "Detects the issues tracker type from Update Center.";
}

/**
* Gets issue trackers for a specific plugin from Update Center.
*
* @param pluginName name of the plugin to fetch issue tracker data for.
* @param updateCenter The {@link UpdateCenter}.
* @return A Map of filtered data from issue trackers.
*/
private Map<String, String> getIssueTrackersForAPlugin(String pluginName, UpdateCenter updateCenter) {
return updateCenter.plugins().get(pluginName) != null
? filterIssueTrackersForTypeAndViewUrl(updateCenter.plugins().get(pluginName).getIssueTrackers())
Jagrutiti marked this conversation as resolved.
Show resolved Hide resolved
Jagrutiti marked this conversation as resolved.
Show resolved Hide resolved
: Map.of();
}

/**
* Filters IssueTrackers for "type" and "viewUrl".
*
* @param issueTrackers Accepts a list of {@link IssueTrackers}.
* @return A Map of {@code IssueTrackers::type} and {@code IssueTrackers::viewUrl}.
*/
private Map<String, String> filterIssueTrackersForTypeAndViewUrl(List<IssueTrackers> issueTrackers) {
return issueTrackers.stream()
.collect(Collectors.toMap(IssueTrackers::type, IssueTrackers::viewUrl));
}

}
Loading
Loading