├── gradle.properties
├── .github
├── dco.yml
└── workflows
│ ├── run-codeql-analysis.yml
│ └── build-and-deploy.yml
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .sdkmanrc
├── .gitignore
├── settings.gradle
├── config
└── checkstyle
│ ├── checkstyle-suppressions.xml
│ └── checkstyle.xml
├── src
├── main
│ ├── java
│ │ └── io
│ │ │ └── spring
│ │ │ └── issuebot
│ │ │ ├── package-info.java
│ │ │ ├── triage
│ │ │ ├── package-info.java
│ │ │ ├── TriageListener.java
│ │ │ ├── TriageFilter.java
│ │ │ ├── TriageProperties.java
│ │ │ ├── LabelledTriageFilter.java
│ │ │ ├── MilestoneAppliedTriageFilter.java
│ │ │ ├── LabelApplyingTriageListener.java
│ │ │ ├── TriageConfiguration.java
│ │ │ ├── OpenedByCollaboratorTriageFilter.java
│ │ │ └── TriageIssueListener.java
│ │ │ ├── github
│ │ │ ├── package-info.java
│ │ │ ├── Page.java
│ │ │ ├── LinkParser.java
│ │ │ ├── Label.java
│ │ │ ├── PullRequest.java
│ │ │ ├── Milestone.java
│ │ │ ├── User.java
│ │ │ ├── RegexLinkParser.java
│ │ │ ├── Comment.java
│ │ │ ├── StandardPage.java
│ │ │ ├── RateLimit.java
│ │ │ ├── GitHubOperations.java
│ │ │ ├── Issue.java
│ │ │ ├── Event.java
│ │ │ └── GitHubTemplate.java
│ │ │ ├── feedback
│ │ │ ├── package-info.java
│ │ │ ├── FeedbackListener.java
│ │ │ ├── FeedbackConfiguration.java
│ │ │ ├── FeedbackProperties.java
│ │ │ ├── StandardFeedbackListener.java
│ │ │ └── FeedbackIssueListener.java
│ │ │ ├── IssueListener.java
│ │ │ ├── MonitoringProperties.java
│ │ │ ├── GitHubProperties.java
│ │ │ ├── IssueBotApplication.java
│ │ │ ├── Repository.java
│ │ │ └── RepositoryMonitor.java
│ └── resources
│ │ └── application.yml
└── test
│ ├── java
│ └── io
│ │ └── spring
│ │ └── issuebot
│ │ ├── IssueBotApplicationTests.java
│ │ ├── triage
│ │ ├── LabelApplyingTriageListenerTests.java
│ │ ├── MilestoneAppliedTriageFilterTests.java
│ │ ├── LabelledTriageFilterTests.java
│ │ ├── OpenedByCollaboratorTriageFilterTests.java
│ │ └── TriageIssueListenerTests.java
│ │ ├── github
│ │ ├── RegexLinkParserTests.java
│ │ └── GitHubTemplateTests.java
│ │ ├── RepositoryMonitorTests.java
│ │ └── feedback
│ │ ├── StandardFeedbackListenerTests.java
│ │ └── FeedbackIssueListenerTests.java
│ └── resources
│ └── io
│ └── spring
│ └── issuebot
│ └── github
│ ├── new-comment.json
│ ├── issue-single-label.json
│ ├── issue-two-labels.json
│ ├── comments-page-two.json
│ ├── events-page-two.json
│ └── events-page-one.json
├── README.md
├── gradlew.bat
└── gradlew
/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.caching=true
2 |
--------------------------------------------------------------------------------
/.github/dco.yml:
--------------------------------------------------------------------------------
1 | require:
2 | members: false
3 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spring-io/issue-bot/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/.sdkmanrc:
--------------------------------------------------------------------------------
1 | # Enable auto-env through the sdkman_auto_env config
2 | # Add key=value pairs of SDKs to use below
3 | java=17.0.14-librca
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle
2 | build/
3 |
4 | bin/
5 | .classpath
6 | .project
7 | .settings
8 | .factorypath
9 |
10 | .idea
11 | *.iml
12 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip
4 | networkTimeout=10000
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | mavenCentral()
4 | gradlePluginPortal()
5 | }
6 | }
7 |
8 | plugins {
9 | id "io.spring.develocity.conventions" version "0.0.22"
10 | }
11 |
12 | rootProject.name="issue-bot"
13 |
14 | enableFeaturePreview("STABLE_CONFIGURATION_CACHE")
15 |
--------------------------------------------------------------------------------
/config/checkstyle/checkstyle-suppressions.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.github/workflows/run-codeql-analysis.yml:
--------------------------------------------------------------------------------
1 | name: "Run CodeQL Analysis"
2 | on:
3 | push:
4 | pull_request:
5 | workflow_dispatch:
6 | schedule:
7 | - cron: '45 0 * * 1'
8 | permissions: read-all
9 | jobs:
10 | run-analysis:
11 | permissions:
12 | actions: read
13 | contents: read
14 | security-events: write
15 | uses: spring-io/github-actions/.github/workflows/codeql-analysis.yml@6e66995f7d29de1e4ff76e4f0def7a10163fe910
16 |
--------------------------------------------------------------------------------
/config/checkstyle/checkstyle.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/main/java/io/spring/issuebot/package-info.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2023 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | /**
18 | * Central classes for the Issue Bot.
19 | */
20 | package io.spring.issuebot;
21 |
--------------------------------------------------------------------------------
/src/main/java/io/spring/issuebot/triage/package-info.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2023 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | /**
18 | * Classes for triaging issues.
19 | */
20 | package io.spring.issuebot.triage;
21 |
--------------------------------------------------------------------------------
/src/main/java/io/spring/issuebot/github/package-info.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2023 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | /**
18 | * Classes for interacting with GitHub's API.
19 | */
20 | package io.spring.issuebot.github;
21 |
--------------------------------------------------------------------------------
/src/main/java/io/spring/issuebot/feedback/package-info.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2023 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | /**
18 | * Classes for managing user-provided feedback on issues.
19 | */
20 | package io.spring.issuebot.feedback;
21 |
--------------------------------------------------------------------------------
/src/test/java/io/spring/issuebot/IssueBotApplicationTests.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2025 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.spring.issuebot;
18 |
19 | import org.junit.jupiter.api.Test;
20 |
21 | import org.springframework.boot.test.context.SpringBootTest;
22 |
23 | /**
24 | * Tests for {@link IssueBotApplication}.
25 | *
26 | * @author Andy Wilkinson
27 | */
28 | @SpringBootTest(properties = "issuebot.monitoring.enabled=false")
29 | class IssueBotApplicationTests {
30 |
31 | @Test
32 | void contextLoads() {
33 |
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/java/io/spring/issuebot/github/Page.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2018 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.spring.issuebot.github;
18 |
19 | import java.util.List;
20 |
21 | /**
22 | * A page of results.
23 | *
24 | * @param the type of the contents of the page
25 | * @author Andy Wilkinson
26 | */
27 | public interface Page {
28 |
29 | /**
30 | * Returns the next page, if any.
31 | * @return the next page or {@code null}
32 | */
33 | Page next();
34 |
35 | /**
36 | * Returns the contents of the page.
37 | * @return the contents
38 | */
39 | List getContent();
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/src/main/java/io/spring/issuebot/github/LinkParser.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2018 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.spring.issuebot.github;
18 |
19 | import java.util.Map;
20 |
21 | /**
22 | * A {@code LinkParser} can be used to parse the
23 | * {@code Link} header that is
24 | * returned by the GitHub API.
25 | *
26 | * @author Andy Wilkinson
27 | */
28 | interface LinkParser {
29 |
30 | /**
31 | * Parse the given {@code header} into a map of rel:url pairs.
32 | * @param header the header to parse
33 | * @return the map of links
34 | */
35 | Map parse(String header);
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/.github/workflows/build-and-deploy.yml:
--------------------------------------------------------------------------------
1 | name: Build and deploy
2 | on:
3 | push:
4 | branches:
5 | - main
6 | permissions:
7 | contents: read
8 | jobs:
9 | build:
10 | name: Build and deploy
11 | runs-on: ubuntu-latest
12 | steps:
13 | - name: Check out
14 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
15 |
16 | - name: Set up JDK 17
17 | uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0
18 | with:
19 | java-version: '17'
20 | distribution: 'liberica'
21 |
22 | - name: Set up Gradle
23 | uses: gradle/actions/setup-gradle@94baf225fe0a508e581a564467443d0e2379123b # v4.3.0
24 |
25 | - name: Build
26 | run: ./gradlew build
27 |
28 | - name: Set up Azure
29 | uses: azure/login@a65d910e8af852a8061c627c456678983e180302 # v2.2.0
30 | with:
31 | creds: ${{ secrets.AZURE_CREDENTIALS_SPRING_ASA }}
32 |
33 | - name: Set up Azure Spring Extension
34 | run: az extension add --name spring
35 |
36 | - name: Deploy
37 | run: |
38 | az spring app deploy \
39 | --name issue-bot \
40 | --service spring-asa \
41 | --resource-group spring-asa \
42 | --artifact-path build/libs/issue-bot.jar
43 |
--------------------------------------------------------------------------------
/src/main/java/io/spring/issuebot/triage/TriageListener.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2018 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.spring.issuebot.triage;
18 |
19 | import io.spring.issuebot.github.Issue;
20 |
21 | /**
22 | * A {@code TriageListener} is notified of issues' triage requirements.
23 | *
24 | * @author Andy Wilkinson
25 | */
26 | interface TriageListener {
27 |
28 | /**
29 | * Notification that the given {@code issue} requires triage.
30 | * @param issue the issue
31 | */
32 | void requiresTriage(Issue issue);
33 |
34 | /**
35 | * Notification that the given {@code issue} does not require triage.
36 | * @param issue the issue
37 | */
38 | void doesNotRequireTriage(Issue issue);
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/io/spring/issuebot/github/Label.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2018 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.spring.issuebot.github;
18 |
19 | import com.fasterxml.jackson.annotation.JsonCreator;
20 | import com.fasterxml.jackson.annotation.JsonProperty;
21 |
22 | /**
23 | * A label that can be applied to a GitHub issue.
24 | *
25 | * @author Andy Wilkinson
26 | */
27 | public class Label {
28 |
29 | private final String name;
30 |
31 | /**
32 | * Creates a new label with the given {@code name}.
33 | * @param name the name of the label
34 | */
35 | @JsonCreator
36 | public Label(@JsonProperty("name") String name) {
37 | this.name = name;
38 | }
39 |
40 | public String getName() {
41 | return this.name;
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/java/io/spring/issuebot/github/PullRequest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2018 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.spring.issuebot.github;
18 |
19 | import com.fasterxml.jackson.annotation.JsonCreator;
20 | import com.fasterxml.jackson.annotation.JsonProperty;
21 |
22 | /**
23 | * Details of a GitHub pull request.
24 | *
25 | * @author Andy Wilkinson
26 | */
27 | public class PullRequest {
28 |
29 | private final String url;
30 |
31 | /**
32 | * Creates a new {@code PullRequest} that has the given {@code url} in the GitHub API.
33 | * @param url the url
34 | */
35 | @JsonCreator
36 | public PullRequest(@JsonProperty("url") String url) {
37 | this.url = url;
38 | }
39 |
40 | public String getUrl() {
41 | return this.url;
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/java/io/spring/issuebot/github/Milestone.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2018 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.spring.issuebot.github;
18 |
19 | import com.fasterxml.jackson.annotation.JsonCreator;
20 | import com.fasterxml.jackson.annotation.JsonProperty;
21 |
22 | /**
23 | * A milestone to which a GitHub Issue can be added.
24 | *
25 | * @author Andy Wilkinson
26 | */
27 | public class Milestone {
28 |
29 | private final String title;
30 |
31 | /**
32 | * Creates a new {@code Milestone} with the given {@code title}.
33 | * @param title the title
34 | */
35 | @JsonCreator
36 | public Milestone(@JsonProperty("title") String title) {
37 | this.title = title;
38 | }
39 |
40 | public String getTitle() {
41 | return this.title;
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/java/io/spring/issuebot/github/User.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2018 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.spring.issuebot.github;
18 |
19 | import com.fasterxml.jackson.annotation.JsonCreator;
20 | import com.fasterxml.jackson.annotation.JsonProperty;
21 |
22 | /**
23 | * A GitHub user.
24 | *
25 | * @author Andy Wilkinson
26 | */
27 | public class User {
28 |
29 | private final String login;
30 |
31 | /**
32 | * Creates a new {@code User} with the given login.
33 | * @param login the login
34 | */
35 | @JsonCreator
36 | public User(@JsonProperty("login") String login) {
37 | this.login = login;
38 | }
39 |
40 | public String getLogin() {
41 | return this.login;
42 | }
43 |
44 | @Override
45 | public String toString() {
46 | return this.login;
47 | }
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/src/main/java/io/spring/issuebot/triage/TriageFilter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.spring.issuebot.triage;
18 |
19 | import io.spring.issuebot.Repository;
20 | import io.spring.issuebot.github.Issue;
21 |
22 | /**
23 | * A {@code TriageFilter} is used to identify issues which are waiting for triage.
24 | *
25 | * @author Andy Wilkinson
26 | */
27 | interface TriageFilter {
28 |
29 | /**
30 | * Returns {@code true} if the given issue has already been triaged, otherwise
31 | * {@code false}.
32 | * @param repository the repository to which the issue belongs
33 | * @param issue the issue
34 | * @return {@code true} if the issue has been triaged, {@code false} otherwise
35 | */
36 | boolean triaged(Repository repository, Issue issue);
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/src/main/java/io/spring/issuebot/triage/TriageProperties.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2018 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.spring.issuebot.triage;
18 |
19 | import org.springframework.boot.context.properties.ConfigurationProperties;
20 | import org.springframework.boot.context.properties.EnableConfigurationProperties;
21 |
22 | /**
23 | * {@link EnableConfigurationProperties Configuration properties} for triaging GitHub
24 | * issues.
25 | *
26 | * @author Andy Wilkinson
27 | */
28 | @ConfigurationProperties(prefix = "issuebot.triage")
29 | class TriageProperties {
30 |
31 | /**
32 | * The name of the label that should be applied to issues that are waiting for triage.
33 | */
34 | private String label;
35 |
36 | public String getLabel() {
37 | return this.label;
38 | }
39 |
40 | public void setLabel(String label) {
41 | this.label = label;
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/src/test/resources/io/spring/issuebot/github/new-comment.json:
--------------------------------------------------------------------------------
1 | {
2 | "url": "https://api.github.com/repos/wilkinsona/issue-bot-test/issues/comments/162885552",
3 | "html_url": "https://github.com/wilkinsona/issue-bot-test/issues/1#issuecomment-162885552",
4 | "issue_url": "https://api.github.com/repos/wilkinsona/issue-bot-test/issues/1",
5 | "id": 162885552,
6 | "user": {
7 | "login": "wilkinsona",
8 | "id": 914682,
9 | "avatar_url": "https://avatars.githubusercontent.com/u/914682?v=3",
10 | "gravatar_id": "",
11 | "url": "https://api.github.com/users/wilkinsona",
12 | "html_url": "https://github.com/wilkinsona",
13 | "followers_url": "https://api.github.com/users/wilkinsona/followers",
14 | "following_url": "https://api.github.com/users/wilkinsona/following{/other_user}",
15 | "gists_url": "https://api.github.com/users/wilkinsona/gists{/gist_id}",
16 | "starred_url": "https://api.github.com/users/wilkinsona/starred{/owner}{/repo}",
17 | "subscriptions_url": "https://api.github.com/users/wilkinsona/subscriptions",
18 | "organizations_url": "https://api.github.com/users/wilkinsona/orgs",
19 | "repos_url": "https://api.github.com/users/wilkinsona/repos",
20 | "events_url": "https://api.github.com/users/wilkinsona/events{/privacy}",
21 | "received_events_url": "https://api.github.com/users/wilkinsona/received_events",
22 | "type": "User",
23 | "site_admin": false
24 | },
25 | "created_at": "2015-12-08T13:53:45Z",
26 | "updated_at": "2015-12-08T13:53:45Z",
27 | "body": "A test comment"
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/java/io/spring/issuebot/IssueListener.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.spring.issuebot;
18 |
19 | import io.spring.issuebot.github.Issue;
20 |
21 | /**
22 | * An {@code IssueListener} is notified of issues found during monitoring of a specific
23 | * repository.
24 | *
25 | * @author Andy Wilkinson
26 | */
27 | public interface IssueListener {
28 |
29 | /**
30 | * Notification that the given {@code issue} is open.
31 | * @param repository the repository to which the issue belongs
32 | * @param issue the open issue
33 | */
34 | default void onOpenIssue(Repository repository, Issue issue) {
35 |
36 | }
37 |
38 | /**
39 | * Notification that the given {@code issue} is being closed.
40 | * @param repository the repository to which the issue belongs
41 | * @param issue the open issue
42 | */
43 | default void onIssueClosure(Repository repository, Issue issue) {
44 |
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/src/main/java/io/spring/issuebot/triage/LabelledTriageFilter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.spring.issuebot.triage;
18 |
19 | import io.spring.issuebot.Repository;
20 | import io.spring.issuebot.github.Issue;
21 | import org.slf4j.Logger;
22 | import org.slf4j.LoggerFactory;
23 |
24 | /**
25 | * A {@link TriageFilter} that considers an issue as having been triaged if any labels
26 | * have been applied to it.
27 | *
28 | * @author Andy Wilkinson
29 | */
30 | final class LabelledTriageFilter implements TriageFilter {
31 |
32 | private static final Logger log = LoggerFactory.getLogger(LabelledTriageFilter.class);
33 |
34 | @Override
35 | public boolean triaged(Repository repository, Issue issue) {
36 | if (issue.getLabels() != null && !issue.getLabels().isEmpty()) {
37 | log.debug("{} has been triaged. It has been labelled.", issue);
38 | return true;
39 | }
40 | return false;
41 | }
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/src/main/java/io/spring/issuebot/MonitoringProperties.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2025 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.spring.issuebot;
18 |
19 | import java.util.List;
20 |
21 | import org.springframework.boot.context.properties.ConfigurationProperties;
22 |
23 | /**
24 | * Properties for configuring repository monitoring.
25 | *
26 | * @author Andy Wilkinson
27 | */
28 | @ConfigurationProperties(prefix = "issuebot.monitoring")
29 | public class MonitoringProperties {
30 |
31 | private List repositories;
32 |
33 | private boolean enabled = true;
34 |
35 | public List getRepositories() {
36 | return this.repositories;
37 | }
38 |
39 | public void setRepositories(List repositories) {
40 | this.repositories = repositories;
41 | }
42 |
43 | public boolean isEnabled() {
44 | return this.enabled;
45 | }
46 |
47 | public void setEnabled(boolean enabled) {
48 | this.enabled = enabled;
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/src/main/java/io/spring/issuebot/triage/MilestoneAppliedTriageFilter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.spring.issuebot.triage;
18 |
19 | import io.spring.issuebot.Repository;
20 | import io.spring.issuebot.github.Issue;
21 | import org.slf4j.Logger;
22 | import org.slf4j.LoggerFactory;
23 |
24 | /**
25 | * A {@link TriageFilter} that considers an issue as having been triaged if a milestone
26 | * has been applied to it.
27 | *
28 | * @author Andy Wilkinson
29 | */
30 | final class MilestoneAppliedTriageFilter implements TriageFilter {
31 |
32 | private static final Logger log = LoggerFactory.getLogger(LabelledTriageFilter.class);
33 |
34 | @Override
35 | public boolean triaged(Repository repository, Issue issue) {
36 | if (issue.getMilestone() != null) {
37 | log.debug("Issue has been triaged. It has been added to milestone {}", issue.getMilestone().getTitle());
38 | return true;
39 | }
40 | return false;
41 | }
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/src/test/java/io/spring/issuebot/triage/LabelApplyingTriageListenerTests.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2020 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.spring.issuebot.triage;
18 |
19 | import io.spring.issuebot.github.GitHubOperations;
20 | import io.spring.issuebot.github.Issue;
21 | import org.junit.jupiter.api.Test;
22 |
23 | import static org.mockito.Mockito.mock;
24 | import static org.mockito.Mockito.verify;
25 |
26 | /**
27 | * Tests for {@link LabelApplyingTriageListener}.
28 | *
29 | * @author Andy Wilkinson
30 | */
31 | class LabelApplyingTriageListenerTests {
32 |
33 | private final GitHubOperations gitHub = mock(GitHubOperations.class);
34 |
35 | private final LabelApplyingTriageListener listener = new LabelApplyingTriageListener(this.gitHub, "test");
36 |
37 | @Test
38 | void requiresTriage() {
39 | Issue issue = new Issue(null, null, null, null, null, null, null, null);
40 | this.listener.requiresTriage(issue);
41 | verify(this.gitHub).addLabel(issue, "test");
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/java/io/spring/issuebot/github/RegexLinkParser.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2018 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.spring.issuebot.github;
18 |
19 | import java.util.HashMap;
20 | import java.util.Map;
21 | import java.util.regex.Matcher;
22 | import java.util.regex.Pattern;
23 |
24 | import org.springframework.util.StringUtils;
25 |
26 | /**
27 | * A {@code LinkParser} that uses a regular expression to parse the header.
28 | *
29 | * @author Andy Wilkinson
30 | */
31 | public class RegexLinkParser implements LinkParser {
32 |
33 | private static final Pattern LINK_PATTERN = Pattern.compile("<(.+)>; rel=\"(.+)\"");
34 |
35 | @Override
36 | public Map parse(String input) {
37 | Map links = new HashMap<>();
38 | for (String link : StringUtils.commaDelimitedListToStringArray(input)) {
39 | Matcher matcher = LINK_PATTERN.matcher(link.trim());
40 | if (matcher.matches()) {
41 | links.put(matcher.group(2), matcher.group(1));
42 | }
43 | }
44 | return links;
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/src/main/java/io/spring/issuebot/github/Comment.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.spring.issuebot.github;
18 |
19 | import java.time.OffsetDateTime;
20 |
21 | import com.fasterxml.jackson.annotation.JsonCreator;
22 | import com.fasterxml.jackson.annotation.JsonProperty;
23 |
24 | /**
25 | * A comment that has been made on a GitHub issue.
26 | *
27 | * @author Andy Wilkinson
28 | */
29 | public final class Comment {
30 |
31 | private final User user;
32 |
33 | private final OffsetDateTime creationTime;
34 |
35 | /**
36 | * Creates a new comment that was authored by the given {@code user} at the given
37 | * {@code creationTime}.
38 | * @param user the user
39 | * @param creationTime the creation time
40 | */
41 | @JsonCreator
42 | public Comment(@JsonProperty("user") User user, @JsonProperty("created_at") OffsetDateTime creationTime) {
43 | this.user = user;
44 | this.creationTime = creationTime;
45 | }
46 |
47 | public User getUser() {
48 | return this.user;
49 | }
50 |
51 | public OffsetDateTime getCreationTime() {
52 | return this.creationTime;
53 | }
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/src/test/java/io/spring/issuebot/triage/MilestoneAppliedTriageFilterTests.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2025 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.spring.issuebot.triage;
18 |
19 | import io.spring.issuebot.Repository;
20 | import io.spring.issuebot.github.Issue;
21 | import io.spring.issuebot.github.Milestone;
22 | import org.junit.jupiter.api.Test;
23 |
24 | import static org.assertj.core.api.Assertions.assertThat;
25 |
26 | /**
27 | * Tests for {@link MilestoneAppliedTriageFilter}.
28 | *
29 | * @author Andy Wilkinson
30 | */
31 | class MilestoneAppliedTriageFilterTests {
32 |
33 | private final TriageFilter filter = new MilestoneAppliedTriageFilter();
34 |
35 | private final Repository repository = new Repository();
36 |
37 | @Test
38 | void issueWithMilestoneApplied() {
39 | assertThat(this.filter.triaged(this.repository,
40 | new Issue(null, null, null, null, null, null, new Milestone("test"), null)))
41 | .isTrue();
42 | }
43 |
44 | @Test
45 | void issueWithNoMilestoneApplied() {
46 | assertThat(this.filter.triaged(this.repository, new Issue(null, null, null, null, null, null, null, null)))
47 | .isFalse();
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/src/main/java/io/spring/issuebot/github/StandardPage.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.spring.issuebot.github;
18 |
19 | import java.util.List;
20 | import java.util.function.Supplier;
21 |
22 | /**
23 | * Standard implementation of {@link Page}.
24 | *
25 | * @param the type of the contents of the page
26 | * @author Andy Wilkinson
27 | */
28 | public class StandardPage implements Page {
29 |
30 | private final List content;
31 |
32 | private final Supplier> nextSupplier;
33 |
34 | /**
35 | * Creates a new {@code StandardPage} that has the given {@code content}. The given
36 | * {@code nextSupplier} will be used to obtain the next page {@link #next when
37 | * requested}.
38 | * @param content the content
39 | * @param nextSupplier the supplier of the next page
40 | */
41 | public StandardPage(List content, Supplier> nextSupplier) {
42 | this.content = content;
43 | this.nextSupplier = nextSupplier;
44 | }
45 |
46 | @Override
47 | public Page next() {
48 | return this.nextSupplier.get();
49 | }
50 |
51 | @Override
52 | public List getContent() {
53 | return this.content;
54 | }
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Issue Bot for GitHub issues
2 |
3 | The issue bot helps manage open GitHub issues.
4 |
5 | ## Features
6 |
7 | The issue bot helps by managing [Waiting for Triage](#waiting-for-triage) and monitoring [Waiting for Feedback](#waiting-for-feedback).
8 |
9 | ### Waiting for Triage
10 |
11 | The issue bot labels issues and pull requests as waiting for triage.
12 | An issue or pull request is deemed as waiting for triage when all of the following is true:
13 | * It is open
14 | * It was not opened by a collaborator
15 | * It is not assigned to a milestone
16 | * It has no labels.
17 |
18 | When the issue is no longer waiting for triage, the label should be manually removed.
19 |
20 | ### Waiting for Feedback
21 |
22 | The issue bot monitors open issues and pull requests that are labeled as waiting for
23 | feedback. Monitoring begins when the waiting for feedback label is applied. Any comment
24 | that is not from a collaborator is considered to be feedback.
25 |
26 | For issues, the bot will:
27 |
28 | * Comment with a reminder of feedback is not provided within 7 days of the label being
29 | applied.
30 | * Close the issue if feedback is not provided within a further 7 days.
31 | * Add the feedback provided label and remove the waiting for feedback label when feedback
32 | is provided.
33 |
34 | For pull requests, the bot will:
35 |
36 | * Add the feedback provided label and remove the waiting for feedback label when feedback
37 | is provided.
38 |
39 | ## Monitoring a Repository
40 |
41 | Configured repositories are scanned periodically.
42 | To add a repository:
43 |
44 | * First ensure the repository has the labels:
45 | * `status: waiting-for-feedback`
46 | * `status: feedback-provided`
47 | * `status: feedback-reminder`
48 | * Then send a pull request similar to https://github.com/spring-io/issue-bot/pull/12
49 |
--------------------------------------------------------------------------------
/src/main/java/io/spring/issuebot/feedback/FeedbackListener.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.spring.issuebot.feedback;
18 |
19 | import java.time.OffsetDateTime;
20 |
21 | import io.spring.issuebot.Repository;
22 | import io.spring.issuebot.github.Issue;
23 |
24 | /**
25 | * A {@code FeedbackListener} is notified when feedback has been provided or is still
26 | * required for an {@link Issue}.
27 | *
28 | * @author Andy Wilkinson
29 | */
30 | public interface FeedbackListener {
31 |
32 | /**
33 | * Notification that feedback has been provided for the given {@code issue}.
34 | * @param repository the repository to which the issue belongs
35 | * @param issue the issue
36 | */
37 | void feedbackProvided(Repository repository, Issue issue);
38 |
39 | /**
40 | * Notification that feedback is still required for the given {@code issue} having
41 | * been requested at the given {@code requestTime}.
42 | * @param repository the repository to which the issue belongs
43 | * @param issue the issue
44 | * @param requestTime the time when feedback was requested
45 | */
46 | void feedbackRequired(Repository repository, Issue issue, OffsetDateTime requestTime);
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/java/io/spring/issuebot/triage/LabelApplyingTriageListener.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2018 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.spring.issuebot.triage;
18 |
19 | import io.spring.issuebot.github.GitHubOperations;
20 | import io.spring.issuebot.github.Issue;
21 |
22 | /**
23 | * A {@link TriageListener} that applies a label to any issues that require triage.
24 | *
25 | * @author Andy Wilkinson
26 | */
27 | final class LabelApplyingTriageListener implements TriageListener {
28 |
29 | private final GitHubOperations gitHub;
30 |
31 | private final String label;
32 |
33 | /**
34 | * Creates a new {@code LabelApplyingTriageListener} that will use the given
35 | * {@code gitHubOperations} to apply the given {@code label} to any issues that
36 | * require triage.
37 | * @param gitHubOperations the GitHubOperations
38 | * @param label the label
39 | */
40 | LabelApplyingTriageListener(GitHubOperations gitHubOperations, String label) {
41 | this.gitHub = gitHubOperations;
42 | this.label = label;
43 | }
44 |
45 | @Override
46 | public void requiresTriage(Issue issue) {
47 | this.gitHub.addLabel(issue, this.label);
48 | }
49 |
50 | @Override
51 | public void doesNotRequireTriage(Issue issue) {
52 | this.gitHub.removeLabel(issue, this.label);
53 | }
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/src/main/java/io/spring/issuebot/triage/TriageConfiguration.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.spring.issuebot.triage;
18 |
19 | import java.util.Arrays;
20 |
21 | import io.spring.issuebot.GitHubProperties;
22 | import io.spring.issuebot.MonitoringProperties;
23 | import io.spring.issuebot.github.GitHubOperations;
24 |
25 | import org.springframework.boot.context.properties.EnableConfigurationProperties;
26 | import org.springframework.context.annotation.Bean;
27 | import org.springframework.context.annotation.Configuration;
28 |
29 | /**
30 | * Central configuration for the beans involved in identifying issues that require triage.
31 | *
32 | * @author Andy Wilkinson
33 | */
34 | @Configuration
35 | @EnableConfigurationProperties(TriageProperties.class)
36 | class TriageConfiguration {
37 |
38 | @Bean
39 | TriageIssueListener triageIssueListener(GitHubOperations gitHubOperations, TriageProperties triageProperties,
40 | MonitoringProperties monitoringProperties, GitHubProperties gitHubProperties) {
41 | return new TriageIssueListener(
42 | Arrays.asList(new OpenedByCollaboratorTriageFilter(monitoringProperties.getRepositories()),
43 | new LabelledTriageFilter(), new MilestoneAppliedTriageFilter()),
44 | new LabelApplyingTriageListener(gitHubOperations, triageProperties.getLabel()));
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/src/test/java/io/spring/issuebot/triage/LabelledTriageFilterTests.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2025 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.spring.issuebot.triage;
18 |
19 | import java.util.Collections;
20 |
21 | import io.spring.issuebot.Repository;
22 | import io.spring.issuebot.github.Issue;
23 | import io.spring.issuebot.github.Label;
24 | import org.junit.jupiter.api.Test;
25 |
26 | import static org.assertj.core.api.Assertions.assertThat;
27 |
28 | /**
29 | * Tests for {@link LabelledTriageFilter}.
30 | *
31 | * @author Andy Wilkinson
32 | */
33 | class LabelledTriageFilterTests {
34 |
35 | private final TriageFilter filter = new LabelledTriageFilter();
36 |
37 | private final Repository repository = new Repository();
38 |
39 | @Test
40 | void issueWithLabels() {
41 | assertThat(this.filter.triaged(this.repository,
42 | new Issue(null, null, null, null, null, Collections.singletonList(new Label("test")), null, null)))
43 | .isTrue();
44 | }
45 |
46 | @Test
47 | void issueWithNullLabels() {
48 | assertThat(this.filter.triaged(this.repository, new Issue(null, null, null, null, null, null, null, null)))
49 | .isFalse();
50 | }
51 |
52 | @Test
53 | void issueWithNoLabels() {
54 | assertThat(this.filter.triaged(this.repository,
55 | new Issue(null, null, null, null, null, Collections.emptyList(), null, null)))
56 | .isFalse();
57 | }
58 |
59 | }
60 |
--------------------------------------------------------------------------------
/src/test/java/io/spring/issuebot/github/RegexLinkParserTests.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2020 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.spring.issuebot.github;
18 |
19 | import java.util.Map;
20 |
21 | import org.junit.jupiter.api.Test;
22 |
23 | import static org.assertj.core.api.Assertions.assertThat;
24 |
25 | /**
26 | * Tests for {@link RegexLinkParser}.
27 | *
28 | * @author Andy Wilkinson
29 | */
30 | class RegexLinkParserTests {
31 |
32 | private final LinkParser linkParser = new RegexLinkParser();
33 |
34 | @Test
35 | void emptyInput() {
36 | assertThat(this.linkParser.parse("")).isEmpty();
37 | }
38 |
39 | @Test
40 | void nullInput() {
41 | assertThat(this.linkParser.parse(null)).isEmpty();
42 | }
43 |
44 | @Test
45 | void singleLink() {
46 | Map links = this.linkParser.parse("; rel=\"foo\"");
47 | assertThat(links).hasSize(1);
48 | assertThat(links).containsEntry("foo", "url");
49 | }
50 |
51 | @Test
52 | void notALink() {
53 | Map links = this.linkParser.parse("; foo bar");
54 | assertThat(links).isEmpty();
55 | }
56 |
57 | @Test
58 | void multipleLinks() {
59 | Map links = this.linkParser.parse("; rel=\"foo\", ; rel=\"bar\"");
60 | assertThat(links).hasSize(2);
61 | assertThat(links).containsEntry("foo", "url-one");
62 | assertThat(links).containsEntry("bar", "url-two");
63 | }
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/src/main/java/io/spring/issuebot/triage/OpenedByCollaboratorTriageFilter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2025 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.spring.issuebot.triage;
18 |
19 | import java.util.List;
20 | import java.util.Map;
21 | import java.util.function.Function;
22 | import java.util.stream.Collectors;
23 |
24 | import io.spring.issuebot.Repository;
25 | import io.spring.issuebot.github.Issue;
26 | import org.slf4j.Logger;
27 | import org.slf4j.LoggerFactory;
28 |
29 | /**
30 | * A {@link TriageFilter} that considers an issue as having been triaged if it was opened
31 | * by a collaborator.
32 | *
33 | * @author Andy Wilkinson
34 | */
35 | final class OpenedByCollaboratorTriageFilter implements TriageFilter {
36 |
37 | private static final Logger log = LoggerFactory.getLogger(OpenedByCollaboratorTriageFilter.class);
38 |
39 | private final Map> repositoryCollaborators;
40 |
41 | OpenedByCollaboratorTriageFilter(List repositories) {
42 | this.repositoryCollaborators = repositories.stream()
43 | .collect(Collectors.toMap(Function.identity(), Repository::getCollaborators));
44 | }
45 |
46 | @Override
47 | public boolean triaged(Repository repository, Issue issue) {
48 | if (this.repositoryCollaborators.get(repository).contains(issue.getUser().getLogin())) {
49 | log.debug("{} has been triaged. It was opened by {}", issue, issue.getUser());
50 | return true;
51 | }
52 | return false;
53 | }
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/src/test/java/io/spring/issuebot/triage/OpenedByCollaboratorTriageFilterTests.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2025 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.spring.issuebot.triage;
18 |
19 | import java.util.Arrays;
20 | import java.util.Collections;
21 |
22 | import io.spring.issuebot.Repository;
23 | import io.spring.issuebot.github.Issue;
24 | import io.spring.issuebot.github.User;
25 | import org.junit.jupiter.api.BeforeEach;
26 | import org.junit.jupiter.api.Test;
27 |
28 | import static org.assertj.core.api.Assertions.assertThat;
29 |
30 | /**
31 | * Tests for {@link OpenedByCollaboratorTriageFilter}.
32 | *
33 | * @author Andy Wilkinson
34 | */
35 | class OpenedByCollaboratorTriageFilterTests {
36 |
37 | private final Repository repository = new Repository();
38 |
39 | private TriageFilter filter;
40 |
41 | @BeforeEach
42 | void setUp() {
43 | this.repository.setCollaborators(Arrays.asList("Alice", "Brenda", "Charlie"));
44 | this.filter = new OpenedByCollaboratorTriageFilter(Collections.singletonList(this.repository));
45 | }
46 |
47 | @Test
48 | void openedByCollaborator() {
49 | assertThat(this.filter.triaged(this.repository,
50 | new Issue(null, null, null, null, new User("Alice"), null, null, null)))
51 | .isTrue();
52 | }
53 |
54 | @Test
55 | void openedByAnotherUser() {
56 | assertThat(this.filter.triaged(this.repository,
57 | new Issue(null, null, null, null, new User("Debbie"), null, null, null)))
58 | .isFalse();
59 | }
60 |
61 | }
62 |
--------------------------------------------------------------------------------
/src/main/java/io/spring/issuebot/GitHubProperties.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.spring.issuebot;
18 |
19 | import org.springframework.boot.context.properties.ConfigurationProperties;
20 | import org.springframework.boot.context.properties.NestedConfigurationProperty;
21 |
22 | /**
23 | * {@link ConfigurationProperties} for connecting to GitHub.
24 | *
25 | * @author Andy Wilkinson
26 | */
27 | @ConfigurationProperties(prefix = "issuebot.github")
28 | public class GitHubProperties {
29 |
30 | @NestedConfigurationProperty
31 | private Credentials credentials = new Credentials();
32 |
33 | public Credentials getCredentials() {
34 | return this.credentials;
35 | }
36 |
37 | public void setCredentials(Credentials credentials) {
38 | this.credentials = credentials;
39 | }
40 |
41 | /**
42 | * Configuration for the credentials used to authenticate with GitHub.
43 | */
44 | public static class Credentials {
45 |
46 | /**
47 | * The username used for authentication with GitHub.
48 | */
49 | private String username;
50 |
51 | /**
52 | * The password used for authentication with GitHub.
53 | */
54 | private String password;
55 |
56 | public String getUsername() {
57 | return this.username;
58 | }
59 |
60 | public void setUsername(String username) {
61 | this.username = username;
62 | }
63 |
64 | public String getPassword() {
65 | return this.password;
66 | }
67 |
68 | public void setPassword(String password) {
69 | this.password = password;
70 | }
71 |
72 | }
73 |
74 | }
75 |
--------------------------------------------------------------------------------
/src/main/java/io/spring/issuebot/feedback/FeedbackConfiguration.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.spring.issuebot.feedback;
18 |
19 | import java.util.List;
20 |
21 | import io.spring.issuebot.GitHubProperties;
22 | import io.spring.issuebot.IssueListener;
23 | import io.spring.issuebot.MonitoringProperties;
24 | import io.spring.issuebot.github.GitHubOperations;
25 |
26 | import org.springframework.boot.context.properties.EnableConfigurationProperties;
27 | import org.springframework.context.annotation.Bean;
28 | import org.springframework.context.annotation.Configuration;
29 |
30 | /**
31 | * Central configuration for the beans involved in managing issues that are waiting for
32 | * feedback.
33 | *
34 | * @author Andy Wilkinson
35 | */
36 | @Configuration
37 | @EnableConfigurationProperties(FeedbackProperties.class)
38 | class FeedbackConfiguration {
39 |
40 | @Bean
41 | FeedbackIssueListener feedbackIssueListener(GitHubOperations gitHub, GitHubProperties gitHubProperties,
42 | MonitoringProperties monitoringProperties, FeedbackProperties feedbackProperties,
43 | List issueListener) {
44 | return new FeedbackIssueListener(gitHub, feedbackProperties.getRequiredLabel(),
45 | monitoringProperties.getRepositories(), gitHubProperties.getCredentials().getUsername(),
46 | new StandardFeedbackListener(gitHub, feedbackProperties.getProvidedLabel(),
47 | feedbackProperties.getRequiredLabel(), feedbackProperties.getReminderLabel(),
48 | feedbackProperties.getReminderComment(), feedbackProperties.getCloseComment(), issueListener));
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/src/main/java/io/spring/issuebot/IssueBotApplication.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2025 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.spring.issuebot;
18 |
19 | import java.util.List;
20 |
21 | import io.spring.issuebot.github.GitHubOperations;
22 | import io.spring.issuebot.github.GitHubTemplate;
23 | import io.spring.issuebot.github.RegexLinkParser;
24 |
25 | import org.springframework.boot.SpringApplication;
26 | import org.springframework.boot.autoconfigure.SpringBootApplication;
27 | import org.springframework.boot.context.properties.EnableConfigurationProperties;
28 | import org.springframework.context.annotation.Bean;
29 | import org.springframework.scheduling.annotation.EnableScheduling;
30 |
31 | /**
32 | * Main class for launching Issue Bot.
33 | *
34 | * @author Andy Wilkinson
35 | */
36 | @SpringBootApplication
37 | @EnableScheduling
38 | @EnableConfigurationProperties({ GitHubProperties.class, MonitoringProperties.class })
39 | public class IssueBotApplication {
40 |
41 | public static void main(String[] args) {
42 | SpringApplication.run(IssueBotApplication.class, args);
43 | }
44 |
45 | @Bean
46 | GitHubTemplate gitHubTemplate(GitHubProperties gitHubProperties) {
47 | return new GitHubTemplate(gitHubProperties.getCredentials().getUsername(),
48 | gitHubProperties.getCredentials().getPassword(), new RegexLinkParser());
49 | }
50 |
51 | @Bean
52 | RepositoryMonitor repositoryMonitor(GitHubOperations gitHub, MonitoringProperties monitoringProperties,
53 | List issueListeners) {
54 | return new RepositoryMonitor(gitHub, monitoringProperties.getRepositories(), monitoringProperties.isEnabled(),
55 | issueListeners);
56 | }
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/src/main/java/io/spring/issuebot/triage/TriageIssueListener.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.spring.issuebot.triage;
18 |
19 | import java.util.List;
20 |
21 | import io.spring.issuebot.IssueListener;
22 | import io.spring.issuebot.Repository;
23 | import io.spring.issuebot.github.Issue;
24 |
25 | /**
26 | * {@link IssueListener} that identifies open issues that require triage.
27 | *
28 | * @author Andy Wilkinson
29 | */
30 | final class TriageIssueListener implements IssueListener {
31 |
32 | private final List triageFilters;
33 |
34 | private final TriageListener triageListener;
35 |
36 | /**
37 | * Creates a new {@code TriageIssueListener} that will use the given
38 | * {@code triageFilters} to identify issues that require triage and notify the given
39 | * {@code triageListener} of those that do.
40 | * @param triageFilters the triage filters
41 | * @param triageListener the triage listener
42 | */
43 | TriageIssueListener(List triageFilters, TriageListener triageListener) {
44 | this.triageFilters = triageFilters;
45 | this.triageListener = triageListener;
46 | }
47 |
48 | @Override
49 | public void onOpenIssue(Repository repository, Issue issue) {
50 | if (requiresTriage(repository, issue)) {
51 | this.triageListener.requiresTriage(issue);
52 | }
53 | }
54 |
55 | private boolean requiresTriage(Repository repository, Issue issue) {
56 | for (TriageFilter filter : this.triageFilters) {
57 | if (filter.triaged(repository, issue)) {
58 | return false;
59 | }
60 | }
61 | return true;
62 | }
63 |
64 | @Override
65 | public void onIssueClosure(Repository repository, Issue issue) {
66 | this.triageListener.doesNotRequireTriage(issue);
67 | }
68 |
69 | }
70 |
--------------------------------------------------------------------------------
/src/main/java/io/spring/issuebot/github/RateLimit.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2020 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.spring.issuebot.github;
18 |
19 | import org.springframework.http.HttpHeaders;
20 | import org.springframework.http.client.ClientHttpResponse;
21 |
22 | /**
23 | * The current rate limit for interacting with GitHub's REST API.
24 | *
25 | * @author Andy Wilkinson
26 | */
27 | public final class RateLimit {
28 |
29 | private static final String HEADER_REMAINING = "X-RateLimit-Remaining";
30 |
31 | private static final String HEADER_LIMIT = "X-RateLimit-Limit";
32 |
33 | private static final String HEADER_RESET = "X-RateLimit-Reset";
34 |
35 | private final int limit;
36 |
37 | private final int remaining;
38 |
39 | private final long reset;
40 |
41 | private RateLimit(int limit, int remaining, long reset) {
42 | this.limit = limit;
43 | this.remaining = remaining;
44 | this.reset = reset;
45 | }
46 |
47 | static RateLimit from(ClientHttpResponse response) {
48 | HttpHeaders headers = response.getHeaders();
49 | int remaining = Integer.parseInt(headers.getFirst(HEADER_REMAINING));
50 | int limit = Integer.parseInt(headers.getFirst(HEADER_LIMIT));
51 | long reset = Long.parseLong(headers.getFirst(HEADER_RESET)) * 1000;
52 | return new RateLimit(limit, remaining, reset);
53 | }
54 |
55 | /**
56 | * Returns the maximum number of requests permitted per rate limit window.
57 | * @return the requests limit
58 | *
59 | */
60 | public int getLimit() {
61 | return this.limit;
62 | }
63 |
64 | /**
65 | * Returns the number of remaining number of requests that will be permitted in the
66 | * current rate limit window.
67 | * @return the remaining number of requests
68 | */
69 | public int getRemaining() {
70 | return this.remaining;
71 | }
72 |
73 | /**
74 | * The time, in milliseconds since the epoch, at which the rate limit window will
75 | * reset.
76 | * @return the window reset time
77 | */
78 | public long getReset() {
79 | return this.reset;
80 | }
81 |
82 | }
83 |
--------------------------------------------------------------------------------
/src/test/java/io/spring/issuebot/triage/TriageIssueListenerTests.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2020 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.spring.issuebot.triage;
18 |
19 | import java.util.Arrays;
20 |
21 | import io.spring.issuebot.IssueListener;
22 | import io.spring.issuebot.Repository;
23 | import io.spring.issuebot.github.Issue;
24 | import org.junit.jupiter.api.Test;
25 |
26 | import static org.mockito.BDDMockito.given;
27 | import static org.mockito.Mockito.mock;
28 | import static org.mockito.Mockito.verify;
29 | import static org.mockito.Mockito.verifyNoMoreInteractions;
30 |
31 | /**
32 | * Tests for {@link TriageIssueListener}.
33 | *
34 | * @author Andy Wilkinson
35 | */
36 | class TriageIssueListenerTests {
37 |
38 | private final Repository repository = new Repository();
39 |
40 | private final TriageFilter triageFilterOne = mock(TriageFilter.class);
41 |
42 | private final TriageFilter triageFilterTwo = mock(TriageFilter.class);
43 |
44 | private final TriageListener listener = mock(TriageListener.class);
45 |
46 | private final IssueListener issueListener = new TriageIssueListener(
47 | Arrays.asList(this.triageFilterOne, this.triageFilterTwo), this.listener);
48 |
49 | @Test
50 | void listenerIsCalledWhenIssueRequiresTriage() {
51 | Issue issue = new Issue(null, null, null, null, null, null, null, null);
52 | given(this.triageFilterOne.triaged(this.repository, issue)).willReturn(false);
53 | given(this.triageFilterTwo.triaged(this.repository, issue)).willReturn(false);
54 | this.issueListener.onOpenIssue(this.repository, issue);
55 | verify(this.triageFilterOne).triaged(this.repository, issue);
56 | verify(this.triageFilterTwo).triaged(this.repository, issue);
57 | verify(this.listener).requiresTriage(issue);
58 | }
59 |
60 | @Test
61 | void listenerIsNotCalledWhenIssueHasAlreadyBeenTriaged() {
62 | Issue issue = new Issue(null, null, null, null, null, null, null, null);
63 | given(this.triageFilterOne.triaged(this.repository, issue)).willReturn(true);
64 | this.issueListener.onOpenIssue(this.repository, issue);
65 | verify(this.triageFilterOne).triaged(this.repository, issue);
66 | verifyNoMoreInteractions(this.triageFilterTwo, this.listener);
67 | }
68 |
69 | }
70 |
--------------------------------------------------------------------------------
/src/main/java/io/spring/issuebot/github/GitHubOperations.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2023 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.spring.issuebot.github;
18 |
19 | import io.spring.issuebot.github.Issue.ClosureReason;
20 |
21 | /**
22 | * Operations that can be performed against the GitHub API.
23 | *
24 | * @author Andy Wilkinson
25 | */
26 | public interface GitHubOperations {
27 |
28 | /**
29 | * Returns the open issues in the {@code repository} owned by the given
30 | * {@code organization}.
31 | * @param organization the name of the organization
32 | * @param repository the name of the repository
33 | * @return the issues
34 | */
35 | Page getIssues(String organization, String repository);
36 |
37 | /**
38 | * Returns the comments that have been made on the given {@code issue}.
39 | * @param issue the issue
40 | * @return the comments
41 | */
42 | Page getComments(Issue issue);
43 |
44 | /**
45 | * Adds the given {@code label} to the given {@code issue}.
46 | * @param issue the issue
47 | * @param label the label
48 | * @return the modified issue
49 | */
50 | Issue addLabel(Issue issue, String label);
51 |
52 | /**
53 | * Removes the given {@code label} from the given {@code issue}.
54 | * @param issue the issue
55 | * @param label the label
56 | * @return the modified issue
57 | */
58 | Issue removeLabel(Issue issue, String label);
59 |
60 | /**
61 | * Adds the given {@code comment} to the given {@code issue}.
62 | * @param issue the issue
63 | * @param comment the comment
64 | * @return the added comment
65 | */
66 | Comment addComment(Issue issue, String comment);
67 |
68 | /**
69 | * Closes the given {@code issue}.
70 | * @param issue the issue
71 | * @param reason the reason for closing the issue
72 | * @return the modified issue
73 | */
74 | Issue close(Issue issue, ClosureReason reason);
75 |
76 | /**
77 | * Returns the events that have occurred on the given {@code issue}.
78 | * @param issue the issue
79 | * @return the events
80 | */
81 | Page getEvents(Issue issue);
82 |
83 | /**
84 | * Returns the current rate limit information or {@code null} if the limit is
85 | * currently unknown.
86 | * @return the rate limit or {@code null}
87 | */
88 | RateLimit getRateLimit();
89 |
90 | }
91 |
--------------------------------------------------------------------------------
/src/main/java/io/spring/issuebot/Repository.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.spring.issuebot;
18 |
19 | import java.util.ArrayList;
20 | import java.util.List;
21 | import java.util.Objects;
22 |
23 | /**
24 | * A repository that is monitored.
25 | *
26 | * @author Andy Wilkinson
27 | */
28 | public class Repository {
29 |
30 | /**
31 | * The name of the organization that owns the repository.
32 | */
33 | private String organization;
34 |
35 | /**
36 | * The name of the repository.
37 | */
38 | private String name;
39 |
40 | /**
41 | * The names of the repository's collaborators.
42 | */
43 | private List collaborators = new ArrayList<>();
44 |
45 | public String getOrganization() {
46 | return this.organization;
47 | }
48 |
49 | public void setOrganization(String organization) {
50 | this.organization = organization;
51 | }
52 |
53 | public String getName() {
54 | return this.name;
55 | }
56 |
57 | public void setName(String name) {
58 | this.name = name;
59 | }
60 |
61 | public List getCollaborators() {
62 | return this.collaborators;
63 | }
64 |
65 | public void setCollaborators(List collaborators) {
66 | this.collaborators = collaborators;
67 | }
68 |
69 | @Override
70 | public boolean equals(Object obj) {
71 | if (this == obj) {
72 | return true;
73 | }
74 | if (obj == null) {
75 | return false;
76 | }
77 | if (getClass() != obj.getClass()) {
78 | return false;
79 | }
80 | Repository other = (Repository) obj;
81 | if (this.name == null) {
82 | if (other.name != null) {
83 | return false;
84 | }
85 | }
86 | else if (!this.name.equals(other.name)) {
87 | return false;
88 | }
89 | if (this.organization == null) {
90 | return other.organization == null;
91 | }
92 | return Objects.equals(this.organization, other.organization);
93 | }
94 |
95 | @Override
96 | public int hashCode() {
97 | final int prime = 31;
98 | int result = 1;
99 | result = prime * result + ((this.name == null) ? 0 : this.name.hashCode());
100 | result = prime * result + ((this.organization == null) ? 0 : this.organization.hashCode());
101 | return result;
102 | }
103 |
104 | }
105 |
--------------------------------------------------------------------------------
/src/main/java/io/spring/issuebot/feedback/FeedbackProperties.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.spring.issuebot.feedback;
18 |
19 | import org.springframework.boot.context.properties.ConfigurationProperties;
20 | import org.springframework.boot.context.properties.EnableConfigurationProperties;
21 |
22 | /**
23 | * {@link EnableConfigurationProperties Configuration properties} for configuring the
24 | * monitoring of issues that require user feedback.
25 | *
26 | * @author Andy Wilkinson
27 | */
28 | @ConfigurationProperties(prefix = "issuebot.feedback")
29 | final class FeedbackProperties {
30 |
31 | /**
32 | * Name of the label that is applied when feedback is required.
33 | */
34 | private String requiredLabel;
35 |
36 | /**
37 | * Name of the label that is applied when feedback has been provided.
38 | */
39 | private String providedLabel;
40 |
41 | /**
42 | * Name of the label that is applied when the feedback reminder comment has been made.
43 | */
44 | private String reminderLabel;
45 |
46 | /**
47 | * The text of the comment that is added as a reminder that feedback is required.
48 | */
49 | private String reminderComment;
50 |
51 | /**
52 | * The text of the comment that is added when an issue is closed as feedback has not
53 | * been provided.
54 | */
55 | private String closeComment;
56 |
57 | public String getRequiredLabel() {
58 | return this.requiredLabel;
59 | }
60 |
61 | public void setRequiredLabel(String requiredLabel) {
62 | this.requiredLabel = requiredLabel;
63 | }
64 |
65 | public String getProvidedLabel() {
66 | return this.providedLabel;
67 | }
68 |
69 | public void setProvidedLabel(String providedLabel) {
70 | this.providedLabel = providedLabel;
71 | }
72 |
73 | public String getReminderLabel() {
74 | return this.reminderLabel;
75 | }
76 |
77 | public void setReminderLabel(String reminderLabel) {
78 | this.reminderLabel = reminderLabel;
79 | }
80 |
81 | public String getReminderComment() {
82 | return this.reminderComment;
83 | }
84 |
85 | public void setReminderComment(String reminderComment) {
86 | this.reminderComment = reminderComment;
87 | }
88 |
89 | public String getCloseComment() {
90 | return this.closeComment;
91 | }
92 |
93 | public void setCloseComment(String closeComment) {
94 | this.closeComment = closeComment;
95 | }
96 |
97 | }
98 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%"=="" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%"=="" set DIRNAME=.
29 | @rem This is normally unused
30 | set APP_BASE_NAME=%~n0
31 | set APP_HOME=%DIRNAME%
32 |
33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
35 |
36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
38 |
39 | @rem Find java.exe
40 | if defined JAVA_HOME goto findJavaFromJavaHome
41 |
42 | set JAVA_EXE=java.exe
43 | %JAVA_EXE% -version >NUL 2>&1
44 | if %ERRORLEVEL% equ 0 goto execute
45 |
46 | echo.
47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
48 | echo.
49 | echo Please set the JAVA_HOME variable in your environment to match the
50 | echo location of your Java installation.
51 |
52 | goto fail
53 |
54 | :findJavaFromJavaHome
55 | set JAVA_HOME=%JAVA_HOME:"=%
56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
57 |
58 | if exist "%JAVA_EXE%" goto execute
59 |
60 | echo.
61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
62 | echo.
63 | echo Please set the JAVA_HOME variable in your environment to match the
64 | echo location of your Java installation.
65 |
66 | goto fail
67 |
68 | :execute
69 | @rem Setup the command line
70 |
71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
72 |
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if %ERRORLEVEL% equ 0 goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | set EXIT_CODE=%ERRORLEVEL%
85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
87 | exit /b %EXIT_CODE%
88 |
89 | :mainEnd
90 | if "%OS%"=="Windows_NT" endlocal
91 |
92 | :omega
93 |
--------------------------------------------------------------------------------
/src/main/java/io/spring/issuebot/RepositoryMonitor.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2025 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.spring.issuebot;
18 |
19 | import java.util.Date;
20 | import java.util.List;
21 |
22 | import io.spring.issuebot.github.GitHubOperations;
23 | import io.spring.issuebot.github.Issue;
24 | import io.spring.issuebot.github.Page;
25 | import io.spring.issuebot.github.RateLimit;
26 | import org.slf4j.Logger;
27 | import org.slf4j.LoggerFactory;
28 |
29 | import org.springframework.scheduling.annotation.Scheduled;
30 |
31 | /**
32 | * Central class for monitoring the configured repository.
33 | *
34 | * @author Andy Wilkinson
35 | */
36 | class RepositoryMonitor {
37 |
38 | private static final Logger log = LoggerFactory.getLogger(RepositoryMonitor.class);
39 |
40 | private final GitHubOperations gitHub;
41 |
42 | private final List repositories;
43 |
44 | private final boolean enabled;
45 |
46 | private final List issueListeners;
47 |
48 | RepositoryMonitor(GitHubOperations gitHub, List repositories, boolean enabled,
49 | List issueListeners) {
50 | this.gitHub = gitHub;
51 | this.repositories = repositories;
52 | this.enabled = enabled;
53 | this.issueListeners = issueListeners;
54 | }
55 |
56 | @Scheduled(fixedRate = 5 * 60 * 1000)
57 | void monitor() {
58 | if (this.enabled) {
59 | for (Repository repository : this.repositories) {
60 | monitor(repository);
61 | }
62 | }
63 | }
64 |
65 | private void monitor(Repository repository) {
66 | log.info("Monitoring {}/{}", repository.getOrganization(), repository.getName());
67 | try {
68 | Page page = this.gitHub.getIssues(repository.getOrganization(), repository.getName());
69 | while (page != null) {
70 | for (Issue issue : page.getContent()) {
71 | for (IssueListener issueListener : this.issueListeners) {
72 | try {
73 | issueListener.onOpenIssue(repository, issue);
74 | }
75 | catch (Exception ex) {
76 | log.warn("Listener '{}' failed when handling issue '{}'", issueListener, issue, ex);
77 | }
78 | }
79 | }
80 | page = page.next();
81 | }
82 | }
83 | catch (Exception ex) {
84 | log.warn("A failure occurred during monitoring of {}/{}", repository.getOrganization(),
85 | repository.getName(), ex);
86 | }
87 | RateLimit rateLimit = this.gitHub.getRateLimit();
88 | if (rateLimit == null) {
89 | log.info("Monitoring of {}/{} completed. Remaining rate limit unknown", repository.getOrganization(),
90 | repository.getName());
91 | }
92 | else {
93 | log.info("Monitoring of {}/{} completed. Remaining rate limit {} of {} resets at {}",
94 | repository.getOrganization(), repository.getName(), rateLimit.getRemaining(), rateLimit.getLimit(),
95 | new Date(rateLimit.getReset()));
96 | }
97 |
98 | }
99 |
100 | }
101 |
--------------------------------------------------------------------------------
/src/test/resources/io/spring/issuebot/github/issue-single-label.json:
--------------------------------------------------------------------------------
1 | {
2 | "url": "https://api.github.com/repos/spring-projects/spring-boot/issues/4644",
3 | "labels_url": "https://api.github.com/repos/spring-projects/spring-boot/issues/4644/labels{/name}",
4 | "comments_url": "https://api.github.com/repos/spring-projects/spring-boot/issues/4644/comments",
5 | "events_url": "https://api.github.com/repos/spring-projects/spring-boot/issues/4644/events",
6 | "html_url": "https://github.com/spring-projects/spring-boot/issues/4644",
7 | "id": 119467644,
8 | "number": 4644,
9 | "title": "Upgrade to Spring Framework 4.2.4.RELEASE",
10 | "user": {
11 | "login": "wilkinsona",
12 | "id": 914682,
13 | "avatar_url": "https://avatars.githubusercontent.com/u/914682?v=3",
14 | "gravatar_id": "",
15 | "url": "https://api.github.com/users/wilkinsona",
16 | "html_url": "https://github.com/wilkinsona",
17 | "followers_url": "https://api.github.com/users/wilkinsona/followers",
18 | "following_url": "https://api.github.com/users/wilkinsona/following{/other_user}",
19 | "gists_url": "https://api.github.com/users/wilkinsona/gists{/gist_id}",
20 | "starred_url": "https://api.github.com/users/wilkinsona/starred{/owner}{/repo}",
21 | "subscriptions_url": "https://api.github.com/users/wilkinsona/subscriptions",
22 | "organizations_url": "https://api.github.com/users/wilkinsona/orgs",
23 | "repos_url": "https://api.github.com/users/wilkinsona/repos",
24 | "events_url": "https://api.github.com/users/wilkinsona/events{/privacy}",
25 | "received_events_url": "https://api.github.com/users/wilkinsona/received_events",
26 | "type": "User",
27 | "site_admin": false
28 | },
29 | "labels": [
30 | {
31 | "url": "https://api.github.com/repos/spring-projects/spring-boot/labels/test",
32 | "name": "test",
33 | "color": "e11d21"
34 | }
35 | ],
36 | "state": "open",
37 | "locked": false,
38 | "assignee": null,
39 | "milestone": {
40 | "url": "https://api.github.com/repos/spring-projects/spring-boot/milestones/49",
41 | "html_url": "https://github.com/spring-projects/spring-boot/milestones/1.3.1",
42 | "labels_url": "https://api.github.com/repos/spring-projects/spring-boot/milestones/49/labels",
43 | "id": 1396509,
44 | "number": 49,
45 | "title": "1.3.1",
46 | "description": "",
47 | "creator": {
48 | "login": "philwebb",
49 | "id": 519772,
50 | "avatar_url": "https://avatars.githubusercontent.com/u/519772?v=3",
51 | "gravatar_id": "",
52 | "url": "https://api.github.com/users/philwebb",
53 | "html_url": "https://github.com/philwebb",
54 | "followers_url": "https://api.github.com/users/philwebb/followers",
55 | "following_url": "https://api.github.com/users/philwebb/following{/other_user}",
56 | "gists_url": "https://api.github.com/users/philwebb/gists{/gist_id}",
57 | "starred_url": "https://api.github.com/users/philwebb/starred{/owner}{/repo}",
58 | "subscriptions_url": "https://api.github.com/users/philwebb/subscriptions",
59 | "organizations_url": "https://api.github.com/users/philwebb/orgs",
60 | "repos_url": "https://api.github.com/users/philwebb/repos",
61 | "events_url": "https://api.github.com/users/philwebb/events{/privacy}",
62 | "received_events_url": "https://api.github.com/users/philwebb/received_events",
63 | "type": "User",
64 | "site_admin": false
65 | },
66 | "open_issues": 41,
67 | "closed_issues": 31,
68 | "state": "open",
69 | "created_at": "2015-11-05T22:07:18Z",
70 | "updated_at": "2015-11-30T10:51:50Z",
71 | "due_on": "2015-12-16T00:00:00Z",
72 | "closed_at": null
73 | },
74 | "comments": 0,
75 | "created_at": "2015-11-30T10:51:50Z",
76 | "updated_at": "2015-11-30T10:52:18Z",
77 | "closed_at": null,
78 | "body": ""
79 | }
80 |
--------------------------------------------------------------------------------
/src/main/java/io/spring/issuebot/feedback/StandardFeedbackListener.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2023 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.spring.issuebot.feedback;
18 |
19 | import java.time.OffsetDateTime;
20 | import java.util.List;
21 |
22 | import io.spring.issuebot.IssueListener;
23 | import io.spring.issuebot.Repository;
24 | import io.spring.issuebot.github.GitHubOperations;
25 | import io.spring.issuebot.github.Issue;
26 | import io.spring.issuebot.github.Issue.ClosureReason;
27 | import io.spring.issuebot.github.Label;
28 |
29 | /**
30 | * Standard implementation of {@link FeedbackListener}.
31 | *
32 | * @author Andy Wilkinson
33 | */
34 | final class StandardFeedbackListener implements FeedbackListener {
35 |
36 | private final GitHubOperations gitHub;
37 |
38 | private final String providedLabel;
39 |
40 | private final String requiredLabel;
41 |
42 | private final String reminderLabel;
43 |
44 | private final String reminderComment;
45 |
46 | private final String closeComment;
47 |
48 | private final List issueListeners;
49 |
50 | StandardFeedbackListener(GitHubOperations gitHub, String providedLabel, String requiredLabel, String reminderLabel,
51 | String reminderComment, String closeComment, List issueListeners) {
52 | this.gitHub = gitHub;
53 | this.providedLabel = providedLabel;
54 | this.requiredLabel = requiredLabel;
55 | this.reminderLabel = reminderLabel;
56 | this.reminderComment = reminderComment;
57 | this.closeComment = closeComment;
58 | this.issueListeners = issueListeners;
59 | }
60 |
61 | @Override
62 | public void feedbackProvided(Repository repository, Issue issue) {
63 | this.gitHub.addLabel(issue, this.providedLabel);
64 | this.gitHub.removeLabel(issue, this.requiredLabel);
65 | if (hasReminderLabel(issue)) {
66 | this.gitHub.removeLabel(issue, this.reminderLabel);
67 | }
68 | }
69 |
70 | @Override
71 | public void feedbackRequired(Repository repository, Issue issue, OffsetDateTime requestTime) {
72 | if (issue.getPullRequest() != null) {
73 | return;
74 | }
75 | OffsetDateTime now = OffsetDateTime.now();
76 | if (requestTime.plusDays(14).isBefore(now)) {
77 | close(repository, issue);
78 | }
79 | else if (requestTime.plusDays(7).isBefore(now) && !hasReminderLabel(issue)) {
80 | remind(issue);
81 | }
82 | }
83 |
84 | private void close(Repository repository, Issue issue) {
85 | this.gitHub.addComment(issue, this.closeComment);
86 | this.gitHub.close(issue, ClosureReason.NOT_PLANNED);
87 | this.gitHub.removeLabel(issue, this.requiredLabel);
88 | this.gitHub.removeLabel(issue, this.reminderLabel);
89 | this.issueListeners.forEach((listener) -> listener.onIssueClosure(repository, issue));
90 | }
91 |
92 | private boolean hasReminderLabel(Issue issue) {
93 | if (issue.getLabels() != null) {
94 | for (Label label : issue.getLabels()) {
95 | if (this.reminderLabel.equals(label.getName())) {
96 | return true;
97 | }
98 | }
99 | }
100 | return false;
101 | }
102 |
103 | private void remind(Issue issue) {
104 | this.gitHub.addComment(issue, this.reminderComment);
105 | this.gitHub.addLabel(issue, this.reminderLabel);
106 | }
107 |
108 | }
109 |
--------------------------------------------------------------------------------
/src/main/java/io/spring/issuebot/github/Issue.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2023 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.spring.issuebot.github;
18 |
19 | import java.util.List;
20 |
21 | import com.fasterxml.jackson.annotation.JsonCreator;
22 | import com.fasterxml.jackson.annotation.JsonProperty;
23 |
24 | /**
25 | * A GitHub issue.
26 | *
27 | * @author Andy Wilkinson
28 | */
29 | public class Issue {
30 |
31 | private final String url;
32 |
33 | private final String commentsUrl;
34 |
35 | private final String eventsUrl;
36 |
37 | private final String labelsUrl;
38 |
39 | private final User user;
40 |
41 | private final List