blob: 31e1f65bd9dd82570677e0b451b686ffffd70885 [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';
20import '../../shared/gr-rest-api-interface/gr-rest-api-interface';
21import '../gr-access-section/gr-access-section';
22import {flush} from '@polymer/polymer/lib/legacy/polymer.dom';
23import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
24import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
25import {PolymerElement} from '@polymer/polymer/polymer-element';
26import {htmlTemplate} from './gr-repo-access_html';
27import {encodeURL, getBaseUrl, singleDecodeURL} from '../../../utils/url-util';
28import {GerritNav} from '../../core/gr-navigation/gr-navigation';
29import {toSortedPermissionsArray} from '../../../utils/access-util';
30import {customElement, property} from '@polymer/decorators';
Dmitrii Filippove3c09ae2020-07-10 11:39:50 +020031import {
Dmitrii Filippov460685c22020-08-21 11:12:49 +020032 RepoName,
33 ProjectInfo,
34 CapabilityInfoMap,
35 LabelNameToLabelTypeInfoMap,
36 ProjectAccessInput,
37 GitRef,
38 UrlEncodedRepoName,
39 ProjectAccessGroups,
40} from '../../../types/common';
41import {RestApiService} from '../../../services/services/gr-rest-api/gr-rest-api';
42import {hasOwnProperty} from '../../../utils/common-util';
43import {GrButton} from '../../shared/gr-button/gr-button';
44import {GrAccessSection} from '../gr-access-section/gr-access-section';
45import {
46 AutocompleteQuery,
47 AutocompleteSuggestion,
48} from '../../shared/gr-autocomplete/gr-autocomplete';
49import {
50 EditableLocalAccessSectionInfo,
51 PermissionAccessSection,
52 PropertyTreeNode,
53 PrimitiveValue,
54} from './gr-repo-access-interfaces';
Milutin Kristofic860fe4d2020-11-23 16:13:45 +010055import {firePageError, fireAlert} from '../../../utils/event-util';
Becky Siegel148c7b22018-01-16 15:01:58 -080056
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +010057const NOTHING_TO_SAVE = 'No changes to save.';
Becky Siegel8d7b6272018-03-28 09:38:29 -070058
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +010059const MAX_AUTOCOMPLETE_RESULTS = 50;
Becky Siegel148c7b22018-01-16 15:01:58 -080060
Dmitrii Filippov460685c22020-08-21 11:12:49 +020061export interface GrRepoAccess {
62 $: {
63 restAPI: RestApiService & Element;
64 };
65}
66
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +010067/**
68 * Fired when save is a no-op
69 *
70 * @event show-alert
71 */
Dmitrii Filippov460685c22020-08-21 11:12:49 +020072@customElement('gr-repo-access')
73export class GrRepoAccess extends GestureEventListeners(
74 LegacyElementMixin(PolymerElement)
75) {
76 static get template() {
77 return htmlTemplate;
78 }
Becky Siegel9640eb22017-12-11 15:58:57 -080079
Dmitrii Filippov460685c22020-08-21 11:12:49 +020080 @property({type: String, observer: '_repoChanged'})
81 repo?: RepoName;
Becky Siegel9640eb22017-12-11 15:58:57 -080082
Dmitrii Filippov460685c22020-08-21 11:12:49 +020083 @property({type: String})
84 path?: string;
Becky Siegel9640eb22017-12-11 15:58:57 -080085
Dmitrii Filippov460685c22020-08-21 11:12:49 +020086 @property({type: Boolean})
87 _canUpload?: boolean = false; // restAPI can return undefined
Becky Siegel9640eb22017-12-11 15:58:57 -080088
Dmitrii Filippov460685c22020-08-21 11:12:49 +020089 @property({type: String})
90 _inheritFromFilter?: RepoName;
Becky Siegel9640eb22017-12-11 15:58:57 -080091
Dmitrii Filippov460685c22020-08-21 11:12:49 +020092 @property({type: Object})
93 _query: AutocompleteQuery;
Becky Siegel6db432f2017-08-25 09:17:42 -070094
Dmitrii Filippov460685c22020-08-21 11:12:49 +020095 @property({type: Array})
96 _ownerOf?: GitRef[];
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +010097
Dmitrii Filippov460685c22020-08-21 11:12:49 +020098 @property({type: Object})
99 _capabilities?: CapabilityInfoMap;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100100
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200101 @property({type: Object})
102 _groups?: ProjectAccessGroups;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100103
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200104 @property({type: Object})
105 _inheritsFrom?: ProjectInfo | null | {};
106
107 @property({type: Object})
108 _labels?: LabelNameToLabelTypeInfoMap;
109
110 @property({type: Object})
111 _local?: EditableLocalAccessSectionInfo;
112
113 @property({type: Boolean, observer: '_handleEditingChanged'})
114 _editing = false;
115
116 @property({type: Boolean})
117 _modified = false;
118
119 @property({type: Array})
120 _sections?: PermissionAccessSection[];
121
122 @property({type: Array})
123 _weblinks?: string[];
124
125 @property({type: Boolean})
126 _loading = true;
127
128 private _originalInheritsFrom?: ProjectInfo | null;
129
130 constructor() {
131 super();
132 this._query = () => this._getInheritFromSuggestions();
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100133 }
Becky Siegel6db432f2017-08-25 09:17:42 -0700134
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100135 /** @override */
136 created() {
137 super.created();
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200138 this.addEventListener('access-modified', () =>
139 this._handleAccessModified()
140 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100141 }
Becky Siegel6db432f2017-08-25 09:17:42 -0700142
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100143 _handleAccessModified() {
144 this._modified = true;
145 }
Becky Siegel9640eb22017-12-11 15:58:57 -0800146
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200147 _repoChanged(repo: RepoName) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100148 this._loading = true;
Becky Siegel9640eb22017-12-11 15:58:57 -0800149
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200150 if (!repo) {
151 return Promise.resolve();
152 }
Paladox none685117922018-03-17 20:05:21 +0000153
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100154 return this._reload(repo);
155 }
Paladox none70cb10c2018-02-17 19:12:09 +0000156
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200157 _reload(repo: RepoName) {
Dhruv Srivastavab0131f92020-11-24 09:31:54 +0100158 const errFn = (response?: Response | null) => {
159 firePageError(this, response);
160 };
Paladox none70cb10c2018-02-17 19:12:09 +0000161
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100162 this._editing = false;
Paladox none70cb10c2018-02-17 19:12:09 +0000163
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100164 // Always reset sections when a project changes.
165 this._sections = [];
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200166 const sectionsPromises = this.$.restAPI
167 .getRepoAccessRights(repo, errFn)
168 .then(res => {
169 if (!res) {
170 return Promise.resolve(undefined);
171 }
Becky Siegel6af63252018-04-26 14:02:36 -0700172
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200173 // Keep a copy of the original inherit from values separate from
174 // the ones data bound to gr-autocomplete, so the original value
175 // can be restored if the user cancels.
176 this._inheritsFrom = res.inherits_from
177 ? {
178 ...res.inherits_from,
179 }
180 : null;
181 this._originalInheritsFrom = res.inherits_from
182 ? {
183 ...res.inherits_from,
184 }
185 : null;
186 // Initialize the filter value so when the user clicks edit, the
187 // current value appears. If there is no parent repo, it is
188 // initialized as an empty string.
189 this._inheritFromFilter = res.inherits_from
190 ? res.inherits_from.name
191 : ('' as RepoName);
192 // 'as EditableLocalAccessSectionInfo' is required because res.local
193 // type doesn't have index signature
194 this._local = res.local as EditableLocalAccessSectionInfo;
195 this._groups = res.groups;
196 this._weblinks = res.config_web_links || [];
197 this._canUpload = res.can_upload;
198 this._ownerOf = res.owner_of || [];
199 return toSortedPermissionsArray(this._local);
200 });
Becky Siegel6af63252018-04-26 14:02:36 -0700201
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200202 const capabilitiesPromises = this.$.restAPI
203 .getCapabilities(errFn)
204 .then(res => {
205 if (!res) {
206 return Promise.resolve(undefined);
207 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100208
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200209 return res;
210 });
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100211
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200212 const labelsPromises = this.$.restAPI.getRepo(repo, errFn).then(res => {
213 if (!res) {
214 return Promise.resolve(undefined);
215 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100216
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200217 return res.labels;
218 });
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100219
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200220 return Promise.all([
221 sectionsPromises,
222 capabilitiesPromises,
223 labelsPromises,
224 ]).then(([sections, capabilities, labels]) => {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100225 this._capabilities = capabilities;
226 this._labels = labels;
227 this._sections = sections;
228 this._loading = false;
229 });
230 }
231
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200232 _handleUpdateInheritFrom(e: CustomEvent<{value: string}>) {
233 const parentProject: ProjectInfo = {
234 id: e.detail.value as UrlEncodedRepoName,
235 name: this._inheritFromFilter,
236 };
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100237 if (!this._inheritsFrom) {
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200238 this._inheritsFrom = parentProject;
239 } else {
240 // TODO(TS): replace with
241 // this._inheritsFrom = {...this._inheritsFrom, ...parentProject};
242 const projectInfo = this._inheritsFrom as ProjectInfo;
243 projectInfo.id = parentProject.id;
244 projectInfo.name = parentProject.name;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100245 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100246 this._handleAccessModified();
247 }
248
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200249 _getInheritFromSuggestions(): Promise<AutocompleteSuggestion[]> {
250 return this.$.restAPI
251 .getRepos(this._inheritFromFilter, MAX_AUTOCOMPLETE_RESULTS)
252 .then(response => {
253 const projects: AutocompleteSuggestion[] = [];
254 if (!response) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100255 return projects;
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200256 }
257 for (const item of response) {
258 projects.push({
259 name: item.name,
260 value: item.id,
261 });
262 }
263 return projects;
264 });
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100265 }
266
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200267 _computeLoadingClass(loading: boolean) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100268 return loading ? 'loading' : '';
269 }
270
271 _handleEdit() {
272 this._editing = !this._editing;
273 }
274
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200275 _editOrCancel(editing: boolean) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100276 return editing ? 'Cancel' : 'Edit';
277 }
278
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200279 _computeWebLinkClass(weblinks?: string[]) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100280 return weblinks && weblinks.length ? 'show' : '';
281 }
282
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200283 _computeShowInherit(inheritsFrom?: RepoName) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100284 return inheritsFrom ? 'show' : '';
285 }
286
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200287 // TODO(TS): Unclear what is model here, provide a better explanation
288 _handleAddedSectionRemoved(e: CustomEvent & {model: {index: string}}) {
289 if (!this._sections) {
290 return;
291 }
292 const index = Number(e.model.index);
293 if (isNaN(index)) {
294 return;
295 }
296 this._sections = this._sections
297 .slice(0, index)
298 .concat(this._sections.slice(index + 1, this._sections.length));
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100299 }
300
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200301 _handleEditingChanged(editing: boolean, editingOld: boolean) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100302 // Ignore when editing gets set initially.
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200303 if (!editingOld || editing) {
304 return;
305 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100306 // Remove any unsaved but added refs.
307 if (this._sections) {
308 this._sections = this._sections.filter(p => !p.value.added);
309 }
310 // Restore inheritFrom.
311 if (this._inheritsFrom) {
Tao Zhou4cd35cb2020-07-22 11:28:22 +0200312 this._inheritsFrom = {...this._originalInheritsFrom};
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200313 this._inheritFromFilter =
314 'name' in this._inheritsFrom ? this._inheritsFrom.name : undefined;
315 }
316 if (!this._local) {
317 return;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100318 }
319 for (const key of Object.keys(this._local)) {
320 if (this._local[key].added) {
321 delete this._local[key];
Tao Zhou704cfe732020-03-12 08:32:46 +0100322 }
Dmitrii Filippov3fd2b102019-11-15 16:16:46 +0100323 }
324 }
325
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200326 _updateRemoveObj(addRemoveObj: {remove: PropertyTreeNode}, path: string[]) {
327 let curPos: PropertyTreeNode = addRemoveObj.remove;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100328 for (const item of path) {
329 if (!curPos[item]) {
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200330 if (item === path[path.length - 1]) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100331 if (path[path.length - 2] === 'permissions') {
332 curPos[item] = {rules: {}};
333 } else if (path.length === 1) {
334 curPos[item] = {permissions: {}};
335 } else {
336 curPos[item] = {};
337 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100338 } else {
339 curPos[item] = {};
340 }
341 }
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200342 // The last item can be a PrimitiveValue, but we don't use it
343 // All intermediate items are PropertyTreeNode
344 // TODO(TS): rewrite this loop and process the last item explicitly
345 curPos = curPos[item] as PropertyTreeNode;
346 }
347 return addRemoveObj;
348 }
349
350 _updateAddObj(
351 addRemoveObj: {add: PropertyTreeNode},
352 path: string[],
353 value: PropertyTreeNode | PrimitiveValue
354 ) {
355 let curPos: PropertyTreeNode = addRemoveObj.add;
356 for (const item of path) {
357 if (!curPos[item]) {
358 if (item === path[path.length - 1]) {
359 curPos[item] = value;
360 } else {
361 curPos[item] = {};
362 }
363 }
364 // The last item can be a PrimitiveValue, but we don't use it
365 // All intermediate items are PropertyTreeNode
366 // TODO(TS): rewrite this loop and process the last item explicitly
367 curPos = curPos[item] as PropertyTreeNode;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100368 }
369 return addRemoveObj;
370 }
371
372 /**
373 * Used to recursively remove any objects with a 'deleted' bit.
374 */
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200375 _recursivelyRemoveDeleted(obj: PropertyTreeNode) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100376 for (const k in obj) {
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200377 if (!hasOwnProperty(obj, k)) {
378 continue;
379 }
380 const node = obj[k];
381 if (typeof node === 'object') {
382 if (node.deleted) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100383 delete obj[k];
384 return;
385 }
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200386 this._recursivelyRemoveDeleted(node);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100387 }
388 }
389 }
390
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200391 _recursivelyUpdateAddRemoveObj(
392 obj: PropertyTreeNode,
393 addRemoveObj: {
394 add: PropertyTreeNode;
395 remove: PropertyTreeNode;
396 },
397 path: string[] = []
398 ) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100399 for (const k in obj) {
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200400 if (!hasOwnProperty(obj, k)) {
401 continue;
402 }
403 const node = obj[k];
404 if (typeof node === 'object') {
405 const updatedId = node.updatedId;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100406 const ref = updatedId ? updatedId : k;
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200407 if (node.deleted) {
408 this._updateRemoveObj(addRemoveObj, path.concat(k));
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100409 continue;
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200410 } else if (node.modified) {
411 this._updateRemoveObj(addRemoveObj, path.concat(k));
412 this._updateAddObj(addRemoveObj, path.concat(ref), node);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100413 /* Special case for ref changes because they need to be added and
414 removed in a different way. The new ref needs to include all
415 changes but also the initial state. To do this, instead of
416 continuing with the same recursion, just remove anything that is
417 deleted in the current state. */
418 if (updatedId && updatedId !== k) {
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200419 this._recursivelyRemoveDeleted(
420 addRemoveObj.add[updatedId] as PropertyTreeNode
421 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100422 }
423 continue;
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200424 } else if (node.added) {
425 this._updateAddObj(addRemoveObj, path.concat(ref), node);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100426 /**
427 * As add / delete both can happen in the new section,
428 * so here to make sure it will remove the deleted ones.
429 *
430 * @see Issue 11339
431 */
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200432 this._recursivelyRemoveDeleted(
433 addRemoveObj.add[k] as PropertyTreeNode
434 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100435 continue;
436 }
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200437 this._recursivelyUpdateAddRemoveObj(node, addRemoveObj, path.concat(k));
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100438 }
439 }
440 }
441
442 /**
443 * Returns an object formatted for saving or submitting access changes for
444 * review
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100445 */
446 _computeAddAndRemove() {
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200447 const addRemoveObj: {
448 add: PropertyTreeNode;
449 remove: PropertyTreeNode;
450 parent?: string | null;
451 } = {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100452 add: {},
453 remove: {},
454 };
455
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200456 const originalInheritsFromId = this._originalInheritsFrom
457 ? singleDecodeURL(this._originalInheritsFrom.id)
458 : null;
459 // TODO(TS): this._inheritsFrom as ProjectInfo might be a mistake.
460 // _inheritsFrom can be {}
461 const inheritsFromId = this._inheritsFrom
462 ? singleDecodeURL((this._inheritsFrom as ProjectInfo).id)
463 : null;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100464
465 const inheritFromChanged =
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200466 // Inherit from changed
467 (originalInheritsFromId && originalInheritsFromId !== inheritsFromId) ||
468 // Inherit from added (did not have one initially);
469 (!originalInheritsFromId && inheritsFromId);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100470
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200471 if (!this._local) {
472 return addRemoveObj;
473 }
474
475 this._recursivelyUpdateAddRemoveObj(
476 (this._local as unknown) as PropertyTreeNode,
477 addRemoveObj
478 );
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100479
480 if (inheritFromChanged) {
481 addRemoveObj.parent = inheritsFromId;
482 }
483 return addRemoveObj;
484 }
485
486 _handleCreateSection() {
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200487 if (!this._local) {
488 return;
489 }
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100490 let newRef = 'refs/for/*';
491 // Avoid using an already used key for the placeholder, since it
492 // immediately gets added to an object.
493 while (this._local[newRef]) {
494 newRef = `${newRef}*`;
495 }
496 const section = {permissions: {}, added: true};
497 this.push('_sections', {id: newRef, value: section});
498 this.set(['_local', newRef], section);
499 flush();
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200500 // Template already instantiated at this point
501 (this.root!.querySelector(
502 'gr-access-section:last-of-type'
503 ) as GrAccessSection).editReference();
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100504 }
505
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200506 _getObjforSave(): ProjectAccessInput | undefined {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100507 const addRemoveObj = this._computeAddAndRemove();
508 // If there are no changes, don't actually save.
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200509 if (
510 !Object.keys(addRemoveObj.add).length &&
511 !Object.keys(addRemoveObj.remove).length &&
512 !addRemoveObj.parent
513 ) {
Milutin Kristofic860fe4d2020-11-23 16:13:45 +0100514 fireAlert(this, NOTHING_TO_SAVE);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100515 return;
516 }
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200517 const obj: ProjectAccessInput = ({
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100518 add: addRemoveObj.add,
519 remove: addRemoveObj.remove,
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200520 } as unknown) as ProjectAccessInput;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100521 if (addRemoveObj.parent) {
522 obj.parent = addRemoveObj.parent;
523 }
524 return obj;
525 }
526
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200527 _handleSave(e: Event) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100528 const obj = this._getObjforSave();
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200529 if (!obj) {
530 return;
531 }
532 const button = e && (e.target as GrButton);
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100533 if (button) {
534 button.loading = true;
535 }
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200536 const repo = this.repo;
537 if (!repo) {
538 return Promise.resolve();
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100539 }
540 return this.$.restAPI
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200541 .setRepoAccessRights(repo, obj)
542 .then(() => {
543 this._reload(repo);
544 })
545 .finally(() => {
546 this._modified = false;
547 if (button) {
548 button.loading = false;
549 }
550 });
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100551 }
552
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200553 _handleSaveForReview(e: Event) {
554 const obj = this._getObjforSave();
555 if (!obj) {
556 return;
557 }
558 const button = e && (e.target as GrButton);
559 if (button) {
560 button.loading = true;
561 }
562 if (!this.repo) {
563 return;
564 }
565 return this.$.restAPI
566 .setRepoAccessRightsForReview(this.repo, obj)
567 .then(change => {
568 GerritNav.navigateToChange(change);
569 })
570 .finally(() => {
571 this._modified = false;
572 if (button) {
573 button.loading = false;
574 }
575 });
576 }
577
578 _computeSaveReviewBtnClass(canUpload?: boolean) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100579 return !canUpload ? 'invisible' : '';
580 }
581
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200582 _computeSaveBtnClass(ownerOf?: GitRef[]) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100583 return ownerOf && ownerOf.length === 0 ? 'invisible' : '';
584 }
585
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200586 _computeMainClass(
587 ownerOf: GitRef[] | undefined,
588 canUpload: boolean,
589 editing: boolean
590 ) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100591 const classList = [];
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200592 if ((ownerOf && ownerOf.length > 0) || canUpload) {
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100593 classList.push('admin');
594 }
595 if (editing) {
596 classList.push('editing');
597 }
598 return classList.join(' ');
599 }
600
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200601 _computeParentHref(repoName: RepoName) {
602 return getBaseUrl() + `/admin/repos/${encodeURL(repoName, true)},access`;
Dmitrii Filippovdaf0ec92020-03-17 11:27:28 +0100603 }
604}
605
Dmitrii Filippov460685c22020-08-21 11:12:49 +0200606declare global {
607 interface HTMLElementTagNameMap {
608 'gr-repo-access': GrRepoAccess;
609 }
610}