Adrià Vilanova Martínez | f19ea43 | 2024-01-23 20:20:52 +0100 | [diff] [blame] | 1 | // Copyright 2019 The Chromium Authors |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | import qs from 'qs'; |
| 6 | |
| 7 | |
| 8 | /** |
| 9 | * With lists a and b, get the elements that are in a but not in b. |
| 10 | * result = a - b |
| 11 | * @param {Array} listA |
| 12 | * @param {Array} listB |
| 13 | * @param {function?} equals |
| 14 | * @return {Array} |
| 15 | */ |
| 16 | export function arrayDifference(listA, listB, equals = undefined) { |
| 17 | if (!equals) { |
| 18 | equals = (a, b) => (a === b); |
| 19 | } |
| 20 | listA = listA || []; |
| 21 | listB = listB || []; |
| 22 | return listA.filter((a) => { |
| 23 | return !listB.find((b) => (equals(a, b))); |
| 24 | }); |
| 25 | } |
| 26 | |
| 27 | /** |
| 28 | * Check to see if a Set contains any of a list of values. |
| 29 | * |
| 30 | * @param {Set} set the Set to check for values in. |
| 31 | * @param {Iterable} values checks if any of these values are included. |
| 32 | * @return {boolean} whether the Set has any of the values or not. |
| 33 | */ |
| 34 | export function setHasAny(set, values) { |
| 35 | for (const value of values) { |
| 36 | if (set.has(value)) { |
| 37 | return true; |
| 38 | } |
| 39 | } |
| 40 | return false; |
| 41 | } |
| 42 | |
| 43 | /** |
| 44 | * Capitalize the first letter of a given string. |
| 45 | * @param {string} str |
| 46 | * @return {string} |
| 47 | */ |
| 48 | export function capitalizeFirst(str) { |
| 49 | return `${str.charAt(0).toUpperCase()}${str.substring(1)}`; |
| 50 | } |
| 51 | |
| 52 | /** |
| 53 | * Check if a string has a prefix, ignoring case. |
| 54 | * @param {string} str |
| 55 | * @param {string} prefix |
| 56 | * @return {boolean} |
| 57 | */ |
| 58 | export function hasPrefix(str, prefix) { |
| 59 | return str.toLowerCase().startsWith(prefix.toLowerCase()); |
| 60 | } |
| 61 | |
| 62 | /** |
| 63 | * Returns a string without specified prefix |
| 64 | * @param {string} str |
| 65 | * @param {string} prefix |
| 66 | * @return {string} |
| 67 | */ |
| 68 | export function removePrefix(str, prefix) { |
| 69 | return str.substr(prefix.length); |
| 70 | } |
| 71 | |
| 72 | // TODO(zhangtiff): Make this more grammatically correct for |
| 73 | // more than two items. |
| 74 | export function arrayToEnglish(arr) { |
| 75 | if (!arr) return ''; |
| 76 | return arr.join(' and '); |
| 77 | } |
| 78 | |
| 79 | export function pluralize(count, singular, pluralArg) { |
| 80 | const plural = pluralArg || singular + 's'; |
| 81 | return count === 1 ? singular : plural; |
| 82 | } |
| 83 | |
| 84 | export function objectToMap(obj = {}) { |
| 85 | const map = new Map(); |
| 86 | Object.keys(obj).forEach((key) => { |
| 87 | map.set(key, obj[key]); |
| 88 | }); |
| 89 | return map; |
| 90 | } |
| 91 | |
| 92 | /** |
| 93 | * Given an Object, extract a list of values from it, based on some |
| 94 | * specified keys. |
| 95 | * |
| 96 | * @param {Object} obj the Object to read values from. |
| 97 | * @param {Array} keys the Object keys to fetch values for. |
| 98 | * @return {Array} Object values matching the given keys. |
| 99 | */ |
| 100 | export function objectValuesForKeys(obj, keys = []) { |
| 101 | return keys.map((key) => ((key in obj) ? obj[key] : undefined)); |
| 102 | } |
| 103 | |
| 104 | /** |
| 105 | * Checks to see if object has no keys |
| 106 | * @param {Object} obj |
| 107 | * @return {boolean} |
| 108 | */ |
| 109 | export function isEmptyObject(obj) { |
| 110 | return Object.keys(obj).length === 0; |
| 111 | } |
| 112 | |
| 113 | /** |
| 114 | * Checks if two strings are equal, case-insensitive |
| 115 | * @param {string} a |
| 116 | * @param {string} b |
| 117 | * @return {boolean} |
| 118 | */ |
| 119 | export function equalsIgnoreCase(a, b) { |
| 120 | if (a == b) return true; |
| 121 | if (!a || !b) return false; |
| 122 | return a.toLowerCase() === b.toLowerCase(); |
| 123 | } |
| 124 | |
| 125 | export function immutableSplice(arr, index, count, ...addedItems) { |
| 126 | if (!arr) return ''; |
| 127 | |
| 128 | return [...arr.slice(0, index), ...addedItems, ...arr.slice(index + count)]; |
| 129 | } |
| 130 | |
| 131 | /** |
| 132 | * Computes a new URL for a page based on an exiting path and set of query |
| 133 | * params. |
| 134 | * |
| 135 | * @param {string} baseUrl the base URL without query params. |
| 136 | * @param {Object} oldParams original query params before changes. |
| 137 | * @param {Object} newParams query parameters to override existing ones. |
| 138 | * @param {Array} deletedParams list of keys to be cleared. |
| 139 | * @return {string} the new URL with the updated params. |
| 140 | */ |
| 141 | export function urlWithNewParams(baseUrl = '', |
| 142 | oldParams = {}, newParams = {}, deletedParams = []) { |
| 143 | const params = {...oldParams, ...newParams}; |
| 144 | deletedParams.forEach((name) => { |
| 145 | delete params[name]; |
| 146 | }); |
| 147 | |
| 148 | const queryString = qs.stringify(params); |
| 149 | |
| 150 | return `${baseUrl}${queryString ? '?' : ''}${queryString}`; |
| 151 | } |
| 152 | |
| 153 | /** |
| 154 | * Finds out whether a user is a member of a given project based on |
| 155 | * project membership info. |
| 156 | * |
| 157 | * @param {Object} userRef reference to a given user. Expects an id. |
| 158 | * @param {string} projectName name of the project being searched for. |
| 159 | * @param {Map} usersProjects all known user project memberships where |
| 160 | * keys are userId and values are Objects with expected values |
| 161 | * for {ownerOf, memberOf, contributorTo}. |
| 162 | * @return {boolean} whether the user is a member of the project or not. |
| 163 | */ |
| 164 | export function userIsMember(userRef, projectName, usersProjects = new Map()) { |
| 165 | // TODO(crbug.com/monorail/5968): Find a better place to place this function |
| 166 | if (!userRef || !userRef.userId || !projectName) return false; |
| 167 | const userProjects = usersProjects.get(userRef.userId); |
| 168 | if (!userProjects) return false; |
| 169 | const {ownerOf = [], memberOf = [], contributorTo = []} = userProjects; |
| 170 | return ownerOf.includes(projectName) || |
| 171 | memberOf.includes(projectName) || |
| 172 | contributorTo.includes(projectName); |
| 173 | } |
| 174 | |
| 175 | /** |
| 176 | * Creates a function that checks two objects are not equal |
| 177 | * based on a set of property keys |
| 178 | * |
| 179 | * @param {Set<string>} props |
| 180 | * @return {function(): boolean} |
| 181 | */ |
| 182 | export function createObjectComparisonFunc(props) { |
| 183 | /** |
| 184 | * Computes whether set of properties have changed |
| 185 | * @param {Object<string, string>} newVal |
| 186 | * @param {Object<string, string>} oldVal |
| 187 | * @return {boolean} |
| 188 | */ |
| 189 | return function(newVal, oldVal) { |
| 190 | if (oldVal === undefined && newVal === undefined) { |
| 191 | return false; |
| 192 | } else if (oldVal === undefined || newVal === undefined) { |
| 193 | return true; |
| 194 | } else if (oldVal === null && newVal === null) { |
| 195 | return false; |
| 196 | } else if (oldVal === null || newVal === null) { |
| 197 | return true; |
| 198 | } |
| 199 | |
| 200 | return Array.from(props) |
| 201 | .some((propName) => newVal[propName] !== oldVal[propName]); |
| 202 | }; |
| 203 | } |
| 204 | |
| 205 | /** |
| 206 | * Calculates whether to wait for memberDefaultQuery to exist prior |
| 207 | * to fetching IssueList. Logged in users may use a default query. |
| 208 | * @param {Object} queryParams |
| 209 | * @return {boolean} |
| 210 | */ |
| 211 | export const shouldWaitForDefaultQuery = (queryParams) => { |
| 212 | return !queryParams.hasOwnProperty('q'); |
| 213 | }; |
Adrià Vilanova Martínez | f19ea43 | 2024-01-23 20:20:52 +0100 | [diff] [blame] | 214 | |
| 215 | // constant value for required redirect project |
| 216 | const redirectProjects = Object.freeze(['pigweed', 'git', 'gerrit', 'skia', 'fuchsia']); |
| 217 | |
| 218 | /** |
| 219 | * Generate the url link for issue in project. |
| 220 | * @param {string} projectName Name of the project. |
| 221 | * @param {string} subUrl the sub URL without query params. |
| 222 | * @param {Object} params the query params. |
| 223 | * @return {string} the new URL |
| 224 | */ |
| 225 | export function generateProjectIssueURL(projectName, subPath, params = {}) { |
| 226 | const queryString = window.location.search; |
| 227 | const urlParams = new URLSearchParams(queryString); |
| 228 | const noRedirect = urlParams.has('no_tracker_redirect'); |
| 229 | let baseUrl = ''; |
| 230 | if (!noRedirect && redirectProjects.includes(projectName)) { |
| 231 | // Full url path will trigger backend service call to handle redirect. |
| 232 | baseUrl = 'https://bugs.chromium.org/p/' + projectName + '/issues' + subPath; |
| 233 | return urlWithNewParams(baseUrl, params, {}, undefined) |
| 234 | } else { |
| 235 | baseUrl = '/p/' + projectName + '/issues' + subPath; |
| 236 | const noRedirectParam = noRedirect ? {'no_tracker_redirect' : 1} : undefined |
| 237 | return urlWithNewParams(baseUrl, params, noRedirectParam, undefined) |
| 238 | } |
| 239 | } |