This is an automated email from the ASF dual-hosted git repository. dpavlov pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/ignite-teamcity-bot.git
commit ce7aef2b1d0ac5cf6c2e28dc6d59d9a53023846f Author: Dmitrii Ryabov <somefire...@gmail.com> AuthorDate: Wed Oct 3 19:32:34 2018 +0300 IGNITE-9697 Autocomplete branch for TC field - Fixes #24. Signed-off-by: Dmitriy Pavlov <dpav...@apache.org> --- .../java/org/apache/ignite/ci/HelperConfig.java | 6 + .../main/java/org/apache/ignite/ci/ITeamcity.java | 19 +++ .../apache/ignite/ci/IgnitePersistentTeamcity.java | 20 +++ .../apache/ignite/ci/IgniteTeamcityConnection.java | 42 +++++- .../ignite/ci/conf/ServerIntegrationLinks.java | 43 ++++++ .../apache/ignite/ci/web/rest/TriggerBuild.java | 20 +++ ignite-tc-helper-web/src/main/webapp/index.html | 12 +- .../src/main/webapp/js/common-1.6.js | 156 +++++++++++++++++++++ ignite-tc-helper-web/src/main/webapp/services.html | 13 +- 9 files changed, 321 insertions(+), 10 deletions(-) diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/HelperConfig.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/HelperConfig.java index 67be102..c39fd1e 100644 --- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/HelperConfig.java +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/HelperConfig.java @@ -54,6 +54,12 @@ public class HelperConfig { /** JIRA authorization token property name. */ public static final String JIRA_AUTH_TOKEN = "jira.auth_token"; + /** JIRA authorization token property name. */ + public static final String GIT_API_URL = "git.api_url"; + + /** JIRA authorization token property name. */ + public static final String JIRA_API_URL = "jira.api_url"; + /** Slack authorization token property name. */ public static final String SLACK_AUTH_TOKEN = "slack.auth_token"; public static final String SLACK_CHANNEL = "slack.channel"; diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/ITeamcity.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/ITeamcity.java index 3193ea8..8f58657 100644 --- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/ITeamcity.java +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/ITeamcity.java @@ -319,6 +319,25 @@ public interface ITeamcity { */ boolean sendJiraComment(String ticket, String comment); + /** + * @param url URL for git integration. + */ + void setGitApiUrl(String url); + + /** + * @return URL for git integration. + */ + String getGitApiUrl(); + + /** + * @param url URL for JIRA integration. + */ + void setJiraApiUrl(String url); + + /** + * @return URL for JIRA integration. + */ + String getJiraApiUrl(); default void setAuthData(String user, String password) { setAuthToken( diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/IgnitePersistentTeamcity.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/IgnitePersistentTeamcity.java index ec53d9b..2e9f873 100644 --- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/IgnitePersistentTeamcity.java +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/IgnitePersistentTeamcity.java @@ -1169,6 +1169,26 @@ public class IgnitePersistentTeamcity implements IAnalyticsEnabledTeamcity, ITea } /** {@inheritDoc} */ + @Override public void setGitApiUrl(String url) { + teamcity.setGitApiUrl(url); + } + + /** {@inheritDoc} */ + @Override public String getGitApiUrl() { + return teamcity.getGitApiUrl(); + } + + /** {@inheritDoc} */ + @Override public void setJiraApiUrl(String url) { + teamcity.setJiraApiUrl(url); + } + + /** {@inheritDoc} */ + @Override public String getJiraApiUrl() { + return teamcity.getJiraApiUrl(); + } + + /** {@inheritDoc} */ @Override public PullRequest getPullRequest(String branchForTc) { return teamcity.getPullRequest(branchForTc); } diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/IgniteTeamcityConnection.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/IgniteTeamcityConnection.java index 6b63f1b..3d68ccb 100644 --- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/IgniteTeamcityConnection.java +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/IgniteTeamcityConnection.java @@ -104,6 +104,12 @@ public class IgniteTeamcityConnection implements ITeamcity { /** JIRA authorization token. */ private String jiraBasicAuthTok; + /** URL for git integration. */ + private String gitApiUrl; + + /** URL for JIRA integration. */ + private String jiraApiUrl; + private String configName; //main properties file name private String tcName; @@ -130,8 +136,10 @@ public class IgniteTeamcityConnection implements ITeamcity { } setGitToken(HelperConfig.prepareGithubHttpAuthToken(props)); + setGitApiUrl(props.getProperty(HelperConfig.GIT_API_URL)); setJiraToken(HelperConfig.prepareJiraHttpAuthToken(props)); + setJiraApiUrl(props.getProperty(HelperConfig.JIRA_API_URL)); final File logsDirFile = HelperConfig.resolveLogs(workDir, props); @@ -173,8 +181,14 @@ public class IgniteTeamcityConnection implements ITeamcity { /** {@inheritDoc} */ @AutoProfiling @Override public boolean sendJiraComment(String ticket, String comment) { + if (isNullOrEmpty(jiraApiUrl)) { + logger.error("Failed to notify JIRA [errMsg=JIRA API URL is not configured for this server.]"); + + return false; + } + try { - String url = "https://issues.apache.org/jira/rest/api/2/issue/" + ticket + "/comment"; + String url = jiraApiUrl + "issue/" + ticket + "/comment"; HttpUtil.sendPostAsStringToJira(jiraBasicAuthTok, url, "{\"body\": \"" + comment + "\"}"); @@ -188,8 +202,31 @@ public class IgniteTeamcityConnection implements ITeamcity { } /** {@inheritDoc} */ + @Override public void setGitApiUrl(String url) { + gitApiUrl = url; + } + + /** {@inheritDoc} */ + @Override public String getGitApiUrl() { + return gitApiUrl; + } + + /** {@inheritDoc} */ + @Override public void setJiraApiUrl(String url) { + jiraApiUrl = url; + } + + /** {@inheritDoc} */ + @Override public String getJiraApiUrl() { + return jiraApiUrl; + } + + /** {@inheritDoc} */ @AutoProfiling @Override public PullRequest getPullRequest(String branchForTc) { + if (!isNullOrEmpty(gitApiUrl)) + throw new IllegalStateException("Git API URL is not configured for this server."); + String id = null; // Get PR id from string "pull/XXXX/head" @@ -203,8 +240,7 @@ public class IgniteTeamcityConnection implements ITeamcity { } } - //todo github address can be probably associated with server - String pr = "https://api.github.com/repos/apache/ignite/pulls/" + id; + String pr = gitApiUrl + "pulls/" + id; try (InputStream is = HttpUtil.sendGetToGit(gitAuthTok, pr)) { InputStreamReader reader = new InputStreamReader(is); diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/conf/ServerIntegrationLinks.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/conf/ServerIntegrationLinks.java new file mode 100644 index 0000000..a0f0eb9 --- /dev/null +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/conf/ServerIntegrationLinks.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ignite.ci.conf; + +/** + * + */ +public class ServerIntegrationLinks { + /** Server ID. */ + public final String srvId; + + /** URL for git integration. */ + public final String gitApiUrl; + + /** URL for JIRA integration. */ + public final String jiraApiUrl; + + /** + * @param srvId Server ID. + * @param gitApiUrl URL for git integration. + * @param jiraApiUrl URL for JIRA integration. + */ + public ServerIntegrationLinks(String srvId, String gitApiUrl, String jiraApiUrl) { + this.srvId = srvId; + this.gitApiUrl = gitApiUrl; + this.jiraApiUrl = jiraApiUrl; + } +} diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/TriggerBuild.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/TriggerBuild.java index c5da089..6a0ce3a 100644 --- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/TriggerBuild.java +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/TriggerBuild.java @@ -20,6 +20,9 @@ package org.apache.ignite.ci.web.rest; import com.google.common.base.Strings; import java.util.Arrays; import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.GET; @@ -30,6 +33,7 @@ import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import org.apache.ignite.ci.ITcHelper; import org.apache.ignite.ci.ITeamcity; +import org.apache.ignite.ci.conf.ServerIntegrationLinks; import org.apache.ignite.ci.github.PullRequest; import org.apache.ignite.ci.observer.BuildObserver; import org.apache.ignite.ci.tcmodel.result.Build; @@ -222,4 +226,20 @@ public class TriggerBuild { return new SimpleResult("OK"); } + @GET + @Path("integrationUrls") + public Set<ServerIntegrationLinks> getIntegrationUrls(@NotNull @QueryParam("serverIds") String srvIds) { + final ICredentialsProv prov = ICredentialsProv.get(req); + + String[] srvIds0 = srvIds.split(","); + + return Arrays.stream(srvIds0).map(srvId -> { + if (!prov.hasAccess(srvId)) + return null; + + ITeamcity teamcity = CtxListener.server(srvId, context, req); + + return new ServerIntegrationLinks(srvId, teamcity.getGitApiUrl(), teamcity.getGitApiUrl()); + }).filter(Objects::nonNull).collect(Collectors.toSet()); + } } diff --git a/ignite-tc-helper-web/src/main/webapp/index.html b/ignite-tc-helper-web/src/main/webapp/index.html index 1dc6eba..2e0dd83 100644 --- a/ignite-tc-helper-web/src/main/webapp/index.html +++ b/ignite-tc-helper-web/src/main/webapp/index.html @@ -43,6 +43,7 @@ function loadData() { success: function(result) { $("#loadStatus").html(""); showSuitesForPrCheckData(result); + tryToFillAutocompleteLists(); }, error: showErrInLoadStatus }); @@ -51,6 +52,7 @@ function loadData() { url: "rest/branches/getServerIds", success: function(result) { $("#loadStatus").html(""); + setupAutocompleteList(result); showBuildsOnServers(result); }, error: showErrInLoadStatus @@ -78,24 +80,26 @@ function showCurrentBranchesData(result) { function showSuitesForPrCheckData(result) { var res = ""; + for (var i = 0; i < result.length; i++) { var chainAtServer = result[i]; - //res+="<a href='pr.html?serverId=private&branchForTc=ignite-gg-12790-1&suiteId=id8xIgniteGridGainTests_RunAll="+id+"'>Check PR</a><br>"; res += "<form action='pr.html'>"; res += "Server: <input type='text' name='serverId' value=" + chainAtServer.serverId + " readonly>"; res += "Chain: <input type='text' name='suiteId' value=" + chainAtServer.suiteId + ">"; - res += "Base branch: <input type='text' name='baseBranchForTc' title='Etalon branch,e.g refs/heads/master'> "; - res += "<b>Branch:</b> <input type='text' name='branchForTc' title='Tested branch, e.g. pull/4790/head or ignite-9349' required> "; + res += "Base branch: <input class='branchForTc" + chainAtServer.serverId + + "' type='text' name='baseBranchForTc' title='Etalon branch, e.g refs/heads/master'> "; + res += "<b>Branch:</b> <input class='branchForTc" + chainAtServer.serverId + + "' type='text' name='branchForTc' title='Tested branch, e.g. pull/4790/head or ignite-9349' required> "; res += "<input type='submit' name='action' value='Latest' title='Show latest runs'>"; // res+="<input type='submit' name='action' value='Chain'>"; res += "<input type='submit' name='action' value='History' title='Show last 10 runs merged'>"; res += "</form>"; } + $("#suitesForPrCheck").html(res); } - function showBuildsOnServers(result) { var res = ""; for (var i = 0; i < result.length; i++) { diff --git a/ignite-tc-helper-web/src/main/webapp/js/common-1.6.js b/ignite-tc-helper-web/src/main/webapp/js/common-1.6.js index 08cffdf..8f15680 100644 --- a/ignite-tc-helper-web/src/main/webapp/js/common-1.6.js +++ b/ignite-tc-helper-web/src/main/webapp/js/common-1.6.js @@ -210,3 +210,159 @@ function tcHelperLogout() { } catch (e) { } } + +/** + * Change autocomplete filter to show results only when they starts from written text. + */ +$.ui.autocomplete.filter = function (array, term) { + var matcher = new RegExp("^" + $.ui.autocomplete.escapeRegex(term), "i"); + + return $.grep(array, function (value) { + return matcher.test(value.label || value.value || value); + }); +}; + +var callbackRegistry = {}; + +/** + * Send request to another site. + * + * @param url URL. + * @param onSuccess Function for success response. + * @param onError Function for fail response. + */ +function scriptRequest(url, onSuccess, onError) { + var scriptOk = false; + var callbackName = 'cb' + String(Math.random()).slice(-6); + + url += ~url.indexOf('?') ? '&' : '?'; + url += 'callback=callbackRegistry.' + callbackName; + + callbackRegistry[callbackName] = function(data) { + scriptOk = true; + + delete callbackRegistry[callbackName]; + + onSuccess(data); + }; + + function checkCallback() { + if (scriptOk) + return; + + delete callbackRegistry[callbackName]; + + console.error("Request to \"" + url + "\" was failed.") + } + + var script = document.createElement('script'); + + script.onload = script.onerror = checkCallback; + script.src = url; + + document.body.appendChild(script); +} + +/** Key-value map. Key - server id. Value - url to git api. */ +var gitUrls = new Map(); + +/** Branches for TeamCity. */ +var branchesForTc = {}; + +/** + * Fill git URLs and send requests to them. + * + * @param srvIds - Set of server IDs. + */ +function setupAutocompleteList(srvIds) { + for (let srvId of srvIds) + gitUrls.set(srvId, ""); + + startFillAutocompleteListsProcess(); +} + +/** + * Retrieves recent PR numbers and fills autocomplete lists for the fields branchForTc. + */ +function startFillAutocompleteListsProcess() { + _receiveIntegrationUrls(); +} + +/** + * Receive api links for the servers. + * + * @private + */ +function _receiveIntegrationUrls() { + var url = "rest/build/integrationUrls?serverIds="; + + for (let key of gitUrls.keys()) + url += key + ","; + + $.ajax({ + url: url, + success: function (result) { + _fillGitUrls(result); + _sendRequestsToFillAutocompleteLists(); + } + }); +} + +/** + * Fill git api URLs. + * + * @param result Array of ServerIntegrationLinks. + * + * @private + */ +function _fillGitUrls(result) { + for (let links of result) + gitUrls.set(links.srvId, links.gitApiUrl); +} + +/** + * Send requests to the git to get pull requests for the branch autocomplete lists. + * + * @private + */ +function _sendRequestsToFillAutocompleteLists() { + for (var entry of gitUrls.entries()) + scriptRequest(entry[1] + "pulls?sort=updated&direction=desc", _fillBranchAutocompleteList); +} + +/** + * Takes all "branchForTc<server>" and add autocomplete list to them. + * + * @param result Response from git. + */ +function _fillBranchAutocompleteList(result) { + if (!result.data || !result.data[0]) + return; + + for (var entry of gitUrls.entries()) { + if (!result.data[0].url.startsWith(entry[1])) + continue; + + branchesForTc[entry[0]] = [{label:"master", value:"refs/heads/master"}]; + + for (let pr of result.data) + branchesForTc[entry[0]].push({label: pr.number, value: "pull/" + pr.number + "/head"}); + + $(".branchForTc" + entry[0]).autocomplete({source: branchesForTc[entry[0]]}); + } +} + +/** + * Fills autocomplete lists for the branchForTc fields, if lists are available. + */ +function tryToFillAutocompleteLists() { + for (var entry of gitUrls.entries()) { + var fields = $(".branchForTc" + entry[0]); + + for (let field of fields) { + if (branchesForTc[entry[0]] && branchesForTc[entry[0]].length > 1 && + field.autocomplete("option", "source").length < 2) + field.autocomplete({source: branchesForTc[entry[0]]}); + } + } +} diff --git a/ignite-tc-helper-web/src/main/webapp/services.html b/ignite-tc-helper-web/src/main/webapp/services.html index 17746f0..eb33737 100644 --- a/ignite-tc-helper-web/src/main/webapp/services.html +++ b/ignite-tc-helper-web/src/main/webapp/services.html @@ -37,10 +37,16 @@ function loadData() { success: function(result) { $("#loadStatus").html(""); showSuitesForTeamCityRunData(result); + tryToFillAutocompleteLists(); showCommentJiraForm(result); }, error: showErrInLoadStatus }); + + $.ajax({ + url: "rest/branches/getServerIds", + success: setupAutocompleteList + }); } function showSuitesForTeamCityRunData(result) { @@ -55,7 +61,8 @@ function showSuitesForTeamCityRunData(result) { res += "Server: <input type='text' name='serverId' value='" + chainAtServer.serverId + "' readonly>"; res += "Chain: <input type='text' name='suiteId' value='" + chainAtServer.suiteId + "' readonly>"; - res += "Branch: <input type='text' name='branchForTc' required> "; + res += "Branch: <input type='text' name='branchForTc' class='branchForTc" + chainAtServer.serverId + + "' required> "; res += "Ticket: <input type='text' name='ticketId'>"; res += "<button name='jira' type='button' onclick='trigBuild(\"tests\")'>Start tests</button>"; res += "<button name='jira' onclick='trigBuild(\"tests+jira\")'>Start tests and comment JIRA ticket on ready</button>"; @@ -85,12 +92,12 @@ function showCommentJiraForm(result) { res += "Server: <input type='text' name='serverId' value=" + chainAtServer.serverId +" readonly>" ; res += "Chain: <input type='text' name='suiteId' value='" + chainAtServer.suiteId + "' readonly>"; - res += "Branch: <input type='text' name='branchForTc' required> "; + res += "Branch: <input type='text' name='branchForTc' class='branchForTc" + chainAtServer.serverId + + "' required> "; res += "Ticket: <input type='text' name='ticketId'> "; res += "<button name='action' onclick='notifyJira()'>Notify</button>"; } - //todo enabled this once feature is ready $("#notifyJira").html(res); }