Line data Source code
1 : // Copyright (C) 2020 The Android Open Source Project 2 : // 3 : // Licensed under the Apache License, Version 2.0 (the "License"); 4 : // you may not use this file except in compliance with the License. 5 : // You may obtain a copy of the License at 6 : // 7 : // http://www.apache.org/licenses/LICENSE-2.0 8 : // 9 : // Unless required by applicable law or agreed to in writing, software 10 : // distributed under the License is distributed on an "AS IS" BASIS, 11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 : // See the License for the specific language governing permissions and 13 : // limitations under the License. 14 : 15 : package com.google.gerrit.httpd.raw; 16 : 17 : import static com.google.common.collect.ImmutableList.toImmutableList; 18 : 19 : import com.google.common.collect.ImmutableList; 20 : import com.google.common.collect.ImmutableSet; 21 : import com.google.common.primitives.Ints; 22 : import com.google.gerrit.common.Nullable; 23 : import com.google.gerrit.common.UsedAt; 24 : import com.google.gerrit.common.UsedAt.Project; 25 : import com.google.gerrit.extensions.api.config.Server; 26 : import com.google.gerrit.extensions.client.ListChangesOption; 27 : import com.google.gerrit.extensions.restapi.RestApiException; 28 : import com.google.gerrit.extensions.restapi.Url; 29 : import java.net.URI; 30 : import java.net.URISyntaxException; 31 : import java.util.ArrayList; 32 : import java.util.List; 33 : import java.util.Optional; 34 : import java.util.regex.Matcher; 35 : import java.util.regex.Pattern; 36 : import java.util.stream.Stream; 37 : 38 : /** Helper for generating preloading parts of {@code index.html}. */ 39 : @UsedAt(Project.GOOGLE) 40 : public class IndexPreloadingUtil { 41 1 : enum RequestedPage { 42 1 : CHANGE, 43 1 : DIFF, 44 1 : DASHBOARD, 45 1 : PAGE_WITHOUT_PRELOADING, 46 : } 47 : 48 : public static final String CHANGE_CANONICAL_PATH = "/c/(?<project>.+)/\\+/(?<changeNum>\\d+)"; 49 : public static final String BASE_PATCH_NUM_PATH_PART = "(/(-?\\d+|edit)(\\.\\.(\\d+|edit))?)"; 50 3 : public static final Pattern CHANGE_URL_PATTERN = 51 3 : Pattern.compile(CHANGE_CANONICAL_PATH + BASE_PATCH_NUM_PATH_PART + "?" + "/?$"); 52 3 : public static final Pattern DIFF_URL_PATTERN = 53 3 : Pattern.compile(CHANGE_CANONICAL_PATH + BASE_PATCH_NUM_PATH_PART + "(/(.+))" + "/?$"); 54 3 : public static final Pattern DASHBOARD_PATTERN = Pattern.compile("/dashboard/self$"); 55 : public static final String ROOT_PATH = "/"; 56 : 57 : // These queries should be kept in sync with PolyGerrit: 58 : // polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.ts 59 : public static final String DASHBOARD_HAS_UNPUBLISHED_DRAFTS_QUERY = "has:draft limit:10"; 60 : public static final String YOUR_TURN = "attention:${user} limit:25"; 61 : public static final String DASHBOARD_ASSIGNED_QUERY = 62 : "assignee:${user} (-is:wip OR " + "owner:self OR assignee:self) is:open limit:25"; 63 : public static final String DASHBOARD_WORK_IN_PROGRESS_QUERY = 64 : "is:open owner:${user} is:wip limit:25"; 65 : public static final String DASHBOARD_OUTGOING_QUERY = "is:open owner:${user} -is:wip limit:25"; 66 : public static final String DASHBOARD_INCOMING_QUERY = 67 : "is:open -owner:${user} -is:wip (reviewer:${user} OR assignee:${user}) limit:25"; 68 : public static final String CC_QUERY = "is:open -is:wip cc:${user} limit:10"; 69 : public static final String DASHBOARD_RECENTLY_CLOSED_QUERY = 70 : "is:closed (-is:wip OR owner:self) " 71 : + "(owner:${user} OR reviewer:${user} OR assignee:${user} " 72 : + "OR cc:${user}) -age:4w limit:10"; 73 : public static final String NEW_USER = "owner:${user} limit:1"; 74 : 75 3 : public static final String SELF_DASHBOARD_HAS_UNPUBLISHED_DRAFTS_QUERY = 76 3 : DASHBOARD_HAS_UNPUBLISHED_DRAFTS_QUERY.replaceAll("\\$\\{user}", "self"); 77 3 : public static final String SELF_YOUR_TURN = YOUR_TURN.replaceAll("\\$\\{user}", "self"); 78 3 : public static final String SELF_DASHBOARD_ASSIGNED_QUERY = 79 3 : DASHBOARD_ASSIGNED_QUERY.replaceAll("\\$\\{user}", "self"); 80 3 : public static final ImmutableList<String> SELF_DASHBOARD_QUERIES = 81 3 : Stream.of( 82 : DASHBOARD_WORK_IN_PROGRESS_QUERY, 83 : DASHBOARD_OUTGOING_QUERY, 84 : DASHBOARD_INCOMING_QUERY, 85 : CC_QUERY, 86 : DASHBOARD_RECENTLY_CLOSED_QUERY, 87 : NEW_USER) 88 3 : .map(query -> query.replaceAll("\\$\\{user}", "self")) 89 3 : .collect(toImmutableList()); 90 3 : public static final ImmutableSet<ListChangesOption> DASHBOARD_OPTIONS = 91 3 : ImmutableSet.of( 92 : ListChangesOption.LABELS, 93 : ListChangesOption.DETAILED_ACCOUNTS, 94 : ListChangesOption.SUBMIT_REQUIREMENTS); 95 : 96 3 : public static final ImmutableSet<ListChangesOption> CHANGE_DETAIL_OPTIONS = 97 3 : ImmutableSet.of( 98 : ListChangesOption.ALL_COMMITS, 99 : ListChangesOption.ALL_REVISIONS, 100 : ListChangesOption.CHANGE_ACTIONS, 101 : ListChangesOption.DETAILED_LABELS, 102 : ListChangesOption.DOWNLOAD_COMMANDS, 103 : ListChangesOption.MESSAGES, 104 : ListChangesOption.SUBMITTABLE, 105 : ListChangesOption.WEB_LINKS, 106 : ListChangesOption.SKIP_DIFFSTAT, 107 : ListChangesOption.SUBMIT_REQUIREMENTS); 108 : 109 : @Nullable 110 : public static String getPath(@Nullable String requestedURL) throws URISyntaxException { 111 1 : if (requestedURL == null) { 112 1 : return null; 113 : } 114 1 : URI uri = new URI(requestedURL); 115 1 : return uri.getPath(); 116 : } 117 : 118 : public static RequestedPage parseRequestedPage(@Nullable String requestedPath) { 119 1 : if (requestedPath == null) { 120 1 : return RequestedPage.PAGE_WITHOUT_PRELOADING; 121 : } 122 : 123 1 : Optional<String> changeRequestsPath = 124 1 : computeChangeRequestsPath(requestedPath, RequestedPage.CHANGE); 125 1 : if (changeRequestsPath.isPresent()) { 126 1 : return RequestedPage.CHANGE; 127 : } 128 : 129 1 : changeRequestsPath = computeChangeRequestsPath(requestedPath, RequestedPage.DIFF); 130 1 : if (changeRequestsPath.isPresent()) { 131 0 : return RequestedPage.DIFF; 132 : } 133 : 134 1 : Matcher dashboardMatcher = IndexPreloadingUtil.DASHBOARD_PATTERN.matcher(requestedPath); 135 1 : if (dashboardMatcher.matches()) { 136 1 : return RequestedPage.DASHBOARD; 137 : } 138 : 139 1 : if (ROOT_PATH.equals(requestedPath)) { 140 1 : return RequestedPage.DASHBOARD; 141 : } 142 : 143 1 : return RequestedPage.PAGE_WITHOUT_PRELOADING; 144 : } 145 : 146 : public static Optional<String> computeChangeRequestsPath( 147 : String requestedURL, RequestedPage page) { 148 : Matcher matcher; 149 1 : switch (page) { 150 : case CHANGE: 151 1 : matcher = CHANGE_URL_PATTERN.matcher(requestedURL); 152 1 : break; 153 : case DIFF: 154 1 : matcher = DIFF_URL_PATTERN.matcher(requestedURL); 155 1 : break; 156 : case DASHBOARD: 157 : case PAGE_WITHOUT_PRELOADING: 158 : default: 159 0 : return Optional.empty(); 160 : } 161 : 162 1 : if (matcher.matches()) { 163 1 : Integer changeId = Ints.tryParse(matcher.group("changeNum")); 164 1 : if (changeId != null) { 165 1 : return Optional.of("changes/" + Url.encode(matcher.group("project")) + "~" + changeId); 166 : } 167 : } 168 1 : return Optional.empty(); 169 : } 170 : 171 : public static Optional<Integer> computeChangeNum(String requestedURL, RequestedPage page) { 172 : Matcher matcher; 173 1 : switch (page) { 174 : case CHANGE: 175 1 : matcher = CHANGE_URL_PATTERN.matcher(requestedURL); 176 1 : break; 177 : case DIFF: 178 0 : matcher = DIFF_URL_PATTERN.matcher(requestedURL); 179 0 : break; 180 : case DASHBOARD: 181 : case PAGE_WITHOUT_PRELOADING: 182 : default: 183 0 : return Optional.empty(); 184 : } 185 : 186 1 : if (matcher.matches()) { 187 1 : Integer changeNum = Ints.tryParse(matcher.group("changeNum")); 188 1 : if (changeNum != null) { 189 1 : return Optional.of(changeNum); 190 : } 191 : } 192 0 : return Optional.empty(); 193 : } 194 : 195 : public static List<String> computeDashboardQueryList(Server serverApi) throws RestApiException { 196 0 : List<String> queryList = new ArrayList<>(); 197 0 : queryList.add(SELF_DASHBOARD_HAS_UNPUBLISHED_DRAFTS_QUERY); 198 0 : if (isEnabledAttentionSet(serverApi)) { 199 0 : queryList.add(SELF_YOUR_TURN); 200 : } 201 0 : if (isEnabledAssignee(serverApi)) { 202 0 : queryList.add(SELF_DASHBOARD_ASSIGNED_QUERY); 203 : } 204 : 205 0 : queryList.addAll(SELF_DASHBOARD_QUERIES); 206 : 207 0 : return queryList; 208 : } 209 : 210 : private static boolean isEnabledAttentionSet(Server serverApi) throws RestApiException { 211 0 : return serverApi.getInfo() != null 212 0 : && serverApi.getInfo().change != null 213 0 : && serverApi.getInfo().change.enableAttentionSet != null 214 0 : && serverApi.getInfo().change.enableAttentionSet; 215 : } 216 : 217 : private static boolean isEnabledAssignee(Server serverApi) throws RestApiException { 218 0 : return serverApi.getInfo() != null 219 0 : && serverApi.getInfo().change != null 220 0 : && serverApi.getInfo().change.enableAssignee != null 221 0 : && serverApi.getInfo().change.enableAssignee; 222 : } 223 : 224 : private IndexPreloadingUtil() {} 225 : }