blob: 80c58ca8e8f5e60897a559952d8841fb8fc6cb78 [file] [log] [blame]
Dave Borowitz8cdc76b2018-03-26 10:04:27 -04001/**
2 * @license
3 * Copyright (C) 2017 The Android Open Source Project
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
Dmitrii Filippov460685c22020-08-21 11:12:49 +020017import '../../../styles/gr-menu-page-styles';
18import '../../../styles/gr-subpage-styles';
19import '../../../styles/shared-styles';
Dmitrii Filippov460685c22020-08-21 11:12:49 +020020import '../gr-access-section/gr-access-section';
21import {flush} from '@polymer/polymer/lib/legacy/polymer.dom';
Dmitrii Filippov460685c22020-08-21 11:12:49 +020022import {PolymerElement} from '@polymer/polymer/polymer-element';
23import {htmlTemplate} from './gr-repo-access_html';
24import {encodeURL, getBaseUrl, singleDecodeURL} from '../../../utils/url-util';
25import {GerritNav} from '../../core/gr-navigation/gr-navigation';
26import {toSortedPermissionsArray} from '../../../utils/access-util';
27import {customElement, property} from '@polymer/decorators';
Dmitrii Filippove3c09ae2020-07-10 11:39:50 +020028import {
Dmitrii Filippov460685c22020-08-21 11:12:49 +020029 RepoName,
30 ProjectInfo,
31 CapabilityInfoMap,
32 LabelNameToLabelTypeInfoMap,
33 ProjectAccessInput,
34 GitRef,
35 UrlEncodedRepoName,
36 ProjectAccessGroups,
37} from '../../../types/common';
Dmitrii Filippov460685c22020-08-21 11:12:49 +020038import {GrButton} from '../../shared/gr-button/gr-button';
39import {GrAccessSection} from '../gr-access-section/gr-access-section';
40import {
41 AutocompleteQuery,
42 AutocompleteSuggestion,
43} from '../../shared/gr-autocomplete/gr-autocomplete';
44import {
45 EditableLocalAccessSectionInfo,
46 PermissionAccessSection,
47 PropertyTreeNode,
48 PrimitiveValue,
49} from './gr-repo-access-interfaces';
Milutin Kristofic860fe4d2020-11-23 16:13:45 +010050import {firePageError, fireAlert} from '../../../utils/event-util';
Ben Rohlfs43935a42020-12-01 19:14:09 +010051import {appContext} from '../../../services/app-context';
Dhruv Srivastavad4880e32021-01-29 13:42:58 +010052import {WebLinkInfo} from '../../../types/diff';
Becky Siegel148c7b22018-01-16 15:01:58 -080053
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +010054const NOTHING_TO_SAVE = 'No changes to save.';
Becky Siegel8d7b6272018-03-28 09:38:29 -070055
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +010056const MAX_AUTOCOMPLETE_RESULTS = 50;
Becky Siegel148c7b22018-01-16 15:01:58 -080057
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +010058/**
59 * Fired when save is a no-op
60 *
61 * @event show-alert
62 */
Dmitrii Filippov460685c22020-08-21 11:12:49 +020063@customElement('gr-repo-access')
Ben Rohlfs27c856da2021-03-12 14:09:10 +010064export class GrRepoAccess extends PolymerElement {
Dmitrii Filippov460685c22020-08-21 11:12:49 +020065 static get template() {
66 return htmlTemplate;
67 }
Becky Siegel9640eb22017-12-11 15:58:57 -080068
Dmitrii Filippov460685c22020-08-21 11:12:49 +020069 @property({type: String, observer: '_repoChanged'})
70 repo?: RepoName;
Becky Siegel9640eb22017-12-11 15:58:57 -080071
Dmitrii Filippov460685c22020-08-21 11:12:49 +020072 @property({type: String})
73 path?: string;
Becky Siegel9640eb22017-12-11 15:58:57 -080074
Dmitrii Filippov460685c22020-08-21 11:12:49 +020075 @property({type: Boolean})
76 _canUpload?: boolean = false; // restAPI can return undefined
Becky Siegel9640eb22017-12-11 15:58:57 -080077
Dmitrii Filippov460685c22020-08-21 11:12:49 +020078 @property({type: String})
79 _inheritFromFilter?: RepoName;
Becky Siegel9640eb22017-12-11 15:58:57 -080080
Dmitrii Filippov460685c22020-08-21 11:12:49 +020081 @property({type: Object})
82 _query: AutocompleteQuery;
Becky Siegel6db432f2017-08-25 09:17:42 -070083
Dmitrii Filippov460685c22020-08-21 11:12:49 +020084 @property({type: Array})
85 _ownerOf?: GitRef[];
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +010086
Dmitrii Filippov460685c22020-08-21 11:12:49 +020087 @property({type: Object})
88 _capabilities?: CapabilityInfoMap;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +010089
Dmitrii Filippov460685c22020-08-21 11:12:49 +020090 @property({type: Object})
91 _groups?: ProjectAccessGroups;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +010092
Dmitrii Filippov460685c22020-08-21 11:12:49 +020093 @property({type: Object})
94 _inheritsFrom?: ProjectInfo | null | {};
95
96 @property({type: Object})
97 _labels?: LabelNameToLabelTypeInfoMap;
98
99 @property({type: Object})
100 _local?: EditableLocalAccessSectionInfo;
101
102 @property({type: Boolean, observer: '_handleEditingChanged'})
103 _editing = false;
104
105 @property({type: Boolean})
106 _modified = false;
107
108 @property({type: Array})
109 _sections?: PermissionAccessSection[];
110
111 @property({type: Array})
Dhruv Srivastavad4880e32021-01-29 13:42:58 +0100112 _weblinks?: WebLinkInfo[];
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200113
114 @property({type: Boolean})
115 _loading = true;
116
Dhruv Srivastava7e454cd2021-02-22 15:06:58 +0100117 private originalInheritsFrom?: ProjectInfo | null;
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200118
Dhruv Srivastava9f65d912021-02-22 10:36:19 +0100119 private readonly restApiService = appContext.restApiService;
Ben Rohlfs43935a42020-12-01 19:14:09 +0100120
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200121 constructor() {
122 super();
123 this._query = () => this._getInheritFromSuggestions();
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200124 this.addEventListener('access-modified', () =>
125 this._handleAccessModified()
126 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100127 }
Becky Siegel6db432f2017-08-25 09:17:42 -0700128
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100129 _handleAccessModified() {
130 this._modified = true;
131 }
Becky Siegel9640eb22017-12-11 15:58:57 -0800132
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200133 _repoChanged(repo: RepoName) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100134 this._loading = true;
Becky Siegel9640eb22017-12-11 15:58:57 -0800135
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200136 if (!repo) {
137 return Promise.resolve();
138 }
Paladox none685117922018-03-17 20:05:21 +0000139
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100140 return this._reload(repo);
141 }
Paladox none70cb10c2018-02-17 19:12:09 +0000142
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200143 _reload(repo: RepoName) {
Dhruv Srivastavab0131f92020-11-24 09:31:54 +0100144 const errFn = (response?: Response | null) => {
Ben Rohlfsa76c82f2021-01-22 22:22:32 +0100145 firePageError(response);
Dhruv Srivastavab0131f92020-11-24 09:31:54 +0100146 };
Paladox none70cb10c2018-02-17 19:12:09 +0000147
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100148 this._editing = false;
Paladox none70cb10c2018-02-17 19:12:09 +0000149
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100150 // Always reset sections when a project changes.
151 this._sections = [];
Ben Rohlfs43935a42020-12-01 19:14:09 +0100152 const sectionsPromises = this.restApiService
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200153 .getRepoAccessRights(repo, errFn)
154 .then(res => {
155 if (!res) {
156 return Promise.resolve(undefined);
157 }
Becky Siegel6af63252018-04-26 14:02:36 -0700158
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200159 // Keep a copy of the original inherit from values separate from
160 // the ones data bound to gr-autocomplete, so the original value
161 // can be restored if the user cancels.
162 this._inheritsFrom = res.inherits_from
163 ? {
164 ...res.inherits_from,
165 }
166 : null;
Dhruv Srivastava7e454cd2021-02-22 15:06:58 +0100167 this.originalInheritsFrom = res.inherits_from
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200168 ? {
169 ...res.inherits_from,
170 }
171 : null;
172 // Initialize the filter value so when the user clicks edit, the
173 // current value appears. If there is no parent repo, it is
174 // initialized as an empty string.
175 this._inheritFromFilter = res.inherits_from
176 ? res.inherits_from.name
177 : ('' as RepoName);
178 // 'as EditableLocalAccessSectionInfo' is required because res.local
179 // type doesn't have index signature
180 this._local = res.local as EditableLocalAccessSectionInfo;
181 this._groups = res.groups;
182 this._weblinks = res.config_web_links || [];
183 this._canUpload = res.can_upload;
184 this._ownerOf = res.owner_of || [];
185 return toSortedPermissionsArray(this._local);
186 });
Becky Siegel6af63252018-04-26 14:02:36 -0700187
Ben Rohlfs43935a42020-12-01 19:14:09 +0100188 const capabilitiesPromises = this.restApiService
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200189 .getCapabilities(errFn)
190 .then(res => {
191 if (!res) {
192 return Promise.resolve(undefined);
193 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100194
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200195 return res;
196 });
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100197
Ben Rohlfs43935a42020-12-01 19:14:09 +0100198 const labelsPromises = this.restApiService
199 .getRepo(repo, errFn)
200 .then(res => {
201 if (!res) {
202 return Promise.resolve(undefined);
203 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100204
Ben Rohlfs43935a42020-12-01 19:14:09 +0100205 return res.labels;
206 });
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100207
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200208 return Promise.all([
209 sectionsPromises,
210 capabilitiesPromises,
211 labelsPromises,
212 ]).then(([sections, capabilities, labels]) => {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100213 this._capabilities = capabilities;
214 this._labels = labels;
215 this._sections = sections;
216 this._loading = false;
217 });
218 }
219
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200220 _handleUpdateInheritFrom(e: CustomEvent<{value: string}>) {
221 const parentProject: ProjectInfo = {
222 id: e.detail.value as UrlEncodedRepoName,
223 name: this._inheritFromFilter,
224 };
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100225 if (!this._inheritsFrom) {
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200226 this._inheritsFrom = parentProject;
227 } else {
228 // TODO(TS): replace with
229 // this._inheritsFrom = {...this._inheritsFrom, ...parentProject};
230 const projectInfo = this._inheritsFrom as ProjectInfo;
231 projectInfo.id = parentProject.id;
232 projectInfo.name = parentProject.name;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100233 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100234 this._handleAccessModified();
235 }
236
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200237 _getInheritFromSuggestions(): Promise<AutocompleteSuggestion[]> {
Ben Rohlfs43935a42020-12-01 19:14:09 +0100238 return this.restApiService
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200239 .getRepos(this._inheritFromFilter, MAX_AUTOCOMPLETE_RESULTS)
240 .then(response => {
241 const projects: AutocompleteSuggestion[] = [];
242 if (!response) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100243 return projects;
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200244 }
245 for (const item of response) {
246 projects.push({
247 name: item.name,
248 value: item.id,
249 });
250 }
251 return projects;
252 });
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100253 }
254
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200255 _computeLoadingClass(loading: boolean) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100256 return loading ? 'loading' : '';
257 }
258
259 _handleEdit() {
260 this._editing = !this._editing;
261 }
262
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200263 _editOrCancel(editing: boolean) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100264 return editing ? 'Cancel' : 'Edit';
265 }
266
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200267 _computeWebLinkClass(weblinks?: string[]) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100268 return weblinks && weblinks.length ? 'show' : '';
269 }
270
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200271 _computeShowInherit(inheritsFrom?: RepoName) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100272 return inheritsFrom ? 'show' : '';
273 }
274
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200275 // TODO(TS): Unclear what is model here, provide a better explanation
276 _handleAddedSectionRemoved(e: CustomEvent & {model: {index: string}}) {
277 if (!this._sections) {
278 return;
279 }
280 const index = Number(e.model.index);
281 if (isNaN(index)) {
282 return;
283 }
284 this._sections = this._sections
285 .slice(0, index)
286 .concat(this._sections.slice(index + 1, this._sections.length));
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100287 }
288
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200289 _handleEditingChanged(editing: boolean, editingOld: boolean) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100290 // Ignore when editing gets set initially.
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200291 if (!editingOld || editing) {
292 return;
293 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100294 // Remove any unsaved but added refs.
295 if (this._sections) {
296 this._sections = this._sections.filter(p => !p.value.added);
297 }
298 // Restore inheritFrom.
299 if (this._inheritsFrom) {
Dmitrii Filippovaab98252021-04-06 19:04:11 +0200300 // Can't assign this._inheritsFrom = {...this.originalInheritsFrom}
301 // directly, because this._inheritsFrom is declared as
302 // '...|null|undefined` and typescript reports error when trying
303 // to access .name property (because 'name' in null and 'name' in undefined
304 // lead to runtime error)
305 // After migrating to Typescript v4.2 the code below can be rewritten as
306 // const copy = {...this.originalInheritsFrom};
307 const copy: ProjectInfo | {} = this.originalInheritsFrom
308 ? {...this.originalInheritsFrom}
309 : {};
310 this._inheritsFrom = copy;
311 this._inheritFromFilter = 'name' in copy ? copy.name : undefined;
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200312 }
313 if (!this._local) {
314 return;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100315 }
316 for (const key of Object.keys(this._local)) {
317 if (this._local[key].added) {
318 delete this._local[key];
Tao Zhou704cfe732020-03-12 08:32:46 +0100319 }
Dmitrii Filippov3fd2b102019-11-15 16:16:46 +0100320 }
321 }
322
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200323 _updateRemoveObj(addRemoveObj: {remove: PropertyTreeNode}, path: string[]) {
324 let curPos: PropertyTreeNode = addRemoveObj.remove;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100325 for (const item of path) {
326 if (!curPos[item]) {
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200327 if (item === path[path.length - 1]) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100328 if (path[path.length - 2] === 'permissions') {
329 curPos[item] = {rules: {}};
330 } else if (path.length === 1) {
331 curPos[item] = {permissions: {}};
332 } else {
333 curPos[item] = {};
334 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100335 } else {
336 curPos[item] = {};
337 }
338 }
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200339 // The last item can be a PrimitiveValue, but we don't use it
340 // All intermediate items are PropertyTreeNode
341 // TODO(TS): rewrite this loop and process the last item explicitly
342 curPos = curPos[item] as PropertyTreeNode;
343 }
344 return addRemoveObj;
345 }
346
347 _updateAddObj(
348 addRemoveObj: {add: PropertyTreeNode},
349 path: string[],
350 value: PropertyTreeNode | PrimitiveValue
351 ) {
352 let curPos: PropertyTreeNode = addRemoveObj.add;
353 for (const item of path) {
354 if (!curPos[item]) {
355 if (item === path[path.length - 1]) {
356 curPos[item] = value;
357 } else {
358 curPos[item] = {};
359 }
360 }
361 // The last item can be a PrimitiveValue, but we don't use it
362 // All intermediate items are PropertyTreeNode
363 // TODO(TS): rewrite this loop and process the last item explicitly
364 curPos = curPos[item] as PropertyTreeNode;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100365 }
366 return addRemoveObj;
367 }
368
369 /**
370 * Used to recursively remove any objects with a 'deleted' bit.
371 */
Ben Rohlfs7b71b112021-02-12 10:36:08 +0100372 _recursivelyRemoveDeleted(obj?: PropertyTreeNode) {
373 if (!obj) return;
374 for (const k of Object.keys(obj)) {
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200375 const node = obj[k];
376 if (typeof node === 'object') {
377 if (node.deleted) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100378 delete obj[k];
379 return;
380 }
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200381 this._recursivelyRemoveDeleted(node);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100382 }
383 }
384 }
385
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200386 _recursivelyUpdateAddRemoveObj(
Ben Rohlfs7b71b112021-02-12 10:36:08 +0100387 obj: PropertyTreeNode | undefined,
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200388 addRemoveObj: {
389 add: PropertyTreeNode;
390 remove: PropertyTreeNode;
391 },
392 path: string[] = []
393 ) {
Ben Rohlfs7b71b112021-02-12 10:36:08 +0100394 if (!obj) return;
395 for (const k of Object.keys(obj)) {
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200396 const node = obj[k];
397 if (typeof node === 'object') {
398 const updatedId = node.updatedId;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100399 const ref = updatedId ? updatedId : k;
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200400 if (node.deleted) {
401 this._updateRemoveObj(addRemoveObj, path.concat(k));
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100402 continue;
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200403 } else if (node.modified) {
404 this._updateRemoveObj(addRemoveObj, path.concat(k));
405 this._updateAddObj(addRemoveObj, path.concat(ref), node);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100406 /* Special case for ref changes because they need to be added and
407 removed in a different way. The new ref needs to include all
408 changes but also the initial state. To do this, instead of
409 continuing with the same recursion, just remove anything that is
410 deleted in the current state. */
411 if (updatedId && updatedId !== k) {
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200412 this._recursivelyRemoveDeleted(
413 addRemoveObj.add[updatedId] as PropertyTreeNode
414 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100415 }
416 continue;
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200417 } else if (node.added) {
418 this._updateAddObj(addRemoveObj, path.concat(ref), node);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100419 /**
420 * As add / delete both can happen in the new section,
421 * so here to make sure it will remove the deleted ones.
422 *
423 * @see Issue 11339
424 */
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200425 this._recursivelyRemoveDeleted(
426 addRemoveObj.add[k] as PropertyTreeNode
427 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100428 continue;
429 }
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200430 this._recursivelyUpdateAddRemoveObj(node, addRemoveObj, path.concat(k));
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100431 }
432 }
433 }
434
435 /**
436 * Returns an object formatted for saving or submitting access changes for
437 * review
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100438 */
439 _computeAddAndRemove() {
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200440 const addRemoveObj: {
441 add: PropertyTreeNode;
442 remove: PropertyTreeNode;
443 parent?: string | null;
444 } = {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100445 add: {},
446 remove: {},
447 };
448
Dhruv Srivastava7e454cd2021-02-22 15:06:58 +0100449 const originalInheritsFromId = this.originalInheritsFrom
450 ? singleDecodeURL(this.originalInheritsFrom.id)
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200451 : null;
452 // TODO(TS): this._inheritsFrom as ProjectInfo might be a mistake.
453 // _inheritsFrom can be {}
454 const inheritsFromId = this._inheritsFrom
455 ? singleDecodeURL((this._inheritsFrom as ProjectInfo).id)
456 : null;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100457
458 const inheritFromChanged =
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200459 // Inherit from changed
460 (originalInheritsFromId && originalInheritsFromId !== inheritsFromId) ||
461 // Inherit from added (did not have one initially);
462 (!originalInheritsFromId && inheritsFromId);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100463
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200464 if (!this._local) {
465 return addRemoveObj;
466 }
467
468 this._recursivelyUpdateAddRemoveObj(
469 (this._local as unknown) as PropertyTreeNode,
470 addRemoveObj
471 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100472
473 if (inheritFromChanged) {
474 addRemoveObj.parent = inheritsFromId;
475 }
476 return addRemoveObj;
477 }
478
479 _handleCreateSection() {
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200480 if (!this._local) {
481 return;
482 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100483 let newRef = 'refs/for/*';
484 // Avoid using an already used key for the placeholder, since it
485 // immediately gets added to an object.
486 while (this._local[newRef]) {
487 newRef = `${newRef}*`;
488 }
489 const section = {permissions: {}, added: true};
490 this.push('_sections', {id: newRef, value: section});
491 this.set(['_local', newRef], section);
492 flush();
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200493 // Template already instantiated at this point
494 (this.root!.querySelector(
495 'gr-access-section:last-of-type'
496 ) as GrAccessSection).editReference();
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100497 }
498
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200499 _getObjforSave(): ProjectAccessInput | undefined {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100500 const addRemoveObj = this._computeAddAndRemove();
501 // If there are no changes, don't actually save.
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200502 if (
503 !Object.keys(addRemoveObj.add).length &&
504 !Object.keys(addRemoveObj.remove).length &&
505 !addRemoveObj.parent
506 ) {
Milutin Kristofic860fe4d2020-11-23 16:13:45 +0100507 fireAlert(this, NOTHING_TO_SAVE);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100508 return;
509 }
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200510 const obj: ProjectAccessInput = ({
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100511 add: addRemoveObj.add,
512 remove: addRemoveObj.remove,
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200513 } as unknown) as ProjectAccessInput;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100514 if (addRemoveObj.parent) {
515 obj.parent = addRemoveObj.parent;
516 }
517 return obj;
518 }
519
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200520 _handleSave(e: Event) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100521 const obj = this._getObjforSave();
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200522 if (!obj) {
523 return;
524 }
525 const button = e && (e.target as GrButton);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100526 if (button) {
527 button.loading = true;
528 }
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200529 const repo = this.repo;
530 if (!repo) {
531 return Promise.resolve();
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100532 }
Ben Rohlfs43935a42020-12-01 19:14:09 +0100533 return this.restApiService
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200534 .setRepoAccessRights(repo, obj)
535 .then(() => {
536 this._reload(repo);
537 })
538 .finally(() => {
539 this._modified = false;
540 if (button) {
541 button.loading = false;
542 }
543 });
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100544 }
545
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200546 _handleSaveForReview(e: Event) {
547 const obj = this._getObjforSave();
548 if (!obj) {
549 return;
550 }
551 const button = e && (e.target as GrButton);
552 if (button) {
553 button.loading = true;
554 }
555 if (!this.repo) {
556 return;
557 }
Ben Rohlfs43935a42020-12-01 19:14:09 +0100558 return this.restApiService
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200559 .setRepoAccessRightsForReview(this.repo, obj)
560 .then(change => {
561 GerritNav.navigateToChange(change);
562 })
563 .finally(() => {
564 this._modified = false;
565 if (button) {
566 button.loading = false;
567 }
568 });
569 }
570
571 _computeSaveReviewBtnClass(canUpload?: boolean) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100572 return !canUpload ? 'invisible' : '';
573 }
574
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200575 _computeSaveBtnClass(ownerOf?: GitRef[]) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100576 return ownerOf && ownerOf.length === 0 ? 'invisible' : '';
577 }
578
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200579 _computeMainClass(
580 ownerOf: GitRef[] | undefined,
581 canUpload: boolean,
582 editing: boolean
583 ) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100584 const classList = [];
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200585 if ((ownerOf && ownerOf.length > 0) || canUpload) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100586 classList.push('admin');
587 }
588 if (editing) {
589 classList.push('editing');
590 }
591 return classList.join(' ');
592 }
593
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200594 _computeParentHref(repoName: RepoName) {
595 return getBaseUrl() + `/admin/repos/${encodeURL(repoName, true)},access`;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100596 }
597}
598
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200599declare global {
600 interface HTMLElementTagNameMap {
601 'gr-repo-access': GrRepoAccess;
602 }
603}