summaryrefslogtreecommitdiffstats
path: root/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.ts
diff options
context:
space:
mode:
Diffstat (limited to 'polygerrit-ui/app/elements/admin/gr-permission/gr-permission.ts')
-rw-r--r--polygerrit-ui/app/elements/admin/gr-permission/gr-permission.ts388
1 files changed, 281 insertions, 107 deletions
diff --git a/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.ts b/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.ts
index f2b84c2213..ff3df997f3 100644
--- a/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.ts
+++ b/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.ts
@@ -16,27 +16,21 @@
*/
import '@polymer/paper-toggle-button/paper-toggle-button';
-import '../../../styles/gr-form-styles';
-import '../../../styles/gr-menu-page-styles';
-import '../../../styles/shared-styles';
import '../../shared/gr-autocomplete/gr-autocomplete';
import '../../shared/gr-button/gr-button';
import '../gr-rule-editor/gr-rule-editor';
-import {flush} from '@polymer/polymer/lib/legacy/polymer.dom';
-import {PolymerElement} from '@polymer/polymer/polymer-element';
-import {htmlTemplate} from './gr-permission_html';
+import {css, html, LitElement, PropertyValues} from 'lit';
import {
toSortedPermissionsArray,
PermissionArrayItem,
PermissionArray,
+ AccessPermissionId,
} from '../../../utils/access-util';
-import {customElement, property, observe} from '@polymer/decorators';
+import {customElement, property, query, state} from 'lit/decorators';
import {
LabelNameToLabelTypeInfoMap,
LabelTypeInfoValues,
GroupInfo,
- ProjectAccessGroups,
- GroupId,
GitRef,
RepoName,
} from '../../../types/common';
@@ -51,9 +45,14 @@ import {
EditablePermissionRuleInfo,
EditableProjectAccessGroups,
} from '../gr-repo-access/gr-repo-access-interfaces';
-import {PolymerDomRepeatEvent} from '../../../types/types';
-import {appContext} from '../../../services/app-context';
-import {fireEvent} from '../../../utils/event-util';
+import {getAppContext} from '../../../services/app-context';
+import {fire, fireEvent} from '../../../utils/event-util';
+import {sharedStyles} from '../../../styles/shared-styles';
+import {paperStyles} from '../../../styles/gr-paper-styles';
+import {formStyles} from '../../../styles/gr-form-styles';
+import {menuPageStyles} from '../../../styles/gr-menu-page-styles';
+import {when} from 'lit/directives/when';
+import {ValueChangedEvent} from '../../../types/events';
const MAX_AUTOCOMPLETE_RESULTS = 20;
@@ -61,12 +60,6 @@ const RANGE_NAMES = ['QUERY LIMIT', 'BATCH CHANGES LIMIT'];
type GroupsWithRulesMap = {[ruleId: string]: boolean};
-export interface GrPermission {
- $: {
- groupAutocomplete: GrAutocomplete;
- };
-}
-
interface ComputedLabelValue {
value: number;
text: string;
@@ -93,11 +86,7 @@ interface GroupSuggestion {
* @event added-permission-removed
*/
@customElement('gr-permission')
-export class GrPermission extends PolymerElement {
- static get template() {
- return htmlTemplate;
- }
-
+export class GrPermission extends LitElement {
@property({type: String})
repo?: RepoName;
@@ -107,7 +96,7 @@ export class GrPermission extends PolymerElement {
@property({type: String})
name?: string;
- @property({type: Object, observer: '_sortPermission', notify: true})
+ @property({type: Object})
permission?: PermissionArrayItem<EditablePermissionInfo>;
@property({type: Object})
@@ -116,76 +105,243 @@ export class GrPermission extends PolymerElement {
@property({type: String})
section?: GitRef;
- @property({type: Boolean, observer: '_handleEditingChanged'})
+ @property({type: Boolean})
editing = false;
- @property({type: Object, computed: '_computeLabel(permission, labels)'})
- _label?: ComputedLabel;
+ @state()
+ private label?: ComputedLabel;
- @property({type: String})
- _groupFilter?: string;
+ @state()
+ private groupFilter?: string;
- @property({type: Object})
- _query: AutocompleteQuery;
+ @state()
+ private query: AutocompleteQuery;
- @property({type: Array})
- _rules?: PermissionArray<EditablePermissionRuleInfo>;
+ @state()
+ rules?: PermissionArray<EditablePermissionRuleInfo | undefined>;
- @property({type: Object})
- _groupsWithRules?: GroupsWithRulesMap;
+ @state()
+ groupsWithRules?: GroupsWithRulesMap;
- @property({type: Boolean})
- _deleted = false;
+ @state()
+ deleted = false;
- @property({type: Boolean})
- _originalExclusiveValue?: boolean;
+ @state()
+ originalExclusiveValue?: boolean;
+
+ @query('#groupAutocomplete')
+ private groupAutocomplete!: GrAutocomplete;
- private readonly restApiService = appContext.restApiService;
+ private readonly restApiService = getAppContext().restApiService;
constructor() {
super();
- this._query = () => this._getGroupSuggestions();
- this.addEventListener('access-saved', () => this._handleAccessSaved());
+ this.query = () => this.getGroupSuggestions();
+ this.addEventListener('access-saved', () => this.handleAccessSaved());
}
- override ready() {
- super.ready();
- this._setupValues();
+ override connectedCallback() {
+ super.connectedCallback();
+ this.setupValues();
}
- _setupValues() {
+ override willUpdate(changedProperties: PropertyValues<GrPermission>): void {
+ if (changedProperties.has('editing')) {
+ this.handleEditingChanged(changedProperties.get('editing'));
+ }
+ if (
+ changedProperties.has('permission') ||
+ changedProperties.has('labels')
+ ) {
+ this.label = this.computeLabel();
+ }
+ if (changedProperties.has('permission')) {
+ this.sortPermission(this.permission);
+ }
+ }
+
+ static override styles = [
+ sharedStyles,
+ paperStyles,
+ formStyles,
+ menuPageStyles,
+ css`
+ :host {
+ display: block;
+ margin-bottom: var(--spacing-m);
+ }
+ .header {
+ align-items: baseline;
+ display: flex;
+ justify-content: space-between;
+ margin: var(--spacing-s) var(--spacing-m);
+ }
+ .rules {
+ background: var(--table-header-background-color);
+ border: 1px solid var(--border-color);
+ border-bottom: 0;
+ }
+ .editing .rules {
+ border-bottom: 1px solid var(--border-color);
+ }
+ .title {
+ margin-bottom: var(--spacing-s);
+ }
+ #addRule,
+ #removeBtn {
+ display: none;
+ }
+ .right {
+ display: flex;
+ align-items: center;
+ }
+ .editing #removeBtn {
+ display: block;
+ margin-left: var(--spacing-xl);
+ }
+ .editing #addRule {
+ display: block;
+ padding: var(--spacing-m);
+ }
+ #deletedContainer,
+ .deleted #mainContainer {
+ display: none;
+ }
+ .deleted #deletedContainer {
+ align-items: baseline;
+ border: 1px solid var(--border-color);
+ display: flex;
+ justify-content: space-between;
+ padding: var(--spacing-m);
+ }
+ #mainContainer {
+ display: block;
+ }
+ `,
+ ];
+
+ override render() {
+ if (!this.section || !this.permission) {
+ return;
+ }
+ return html`
+ <section
+ id="permission"
+ class="gr-form-styles ${this.computeSectionClass(
+ this.editing,
+ this.deleted
+ )}"
+ >
+ <div id="mainContainer">
+ <div class="header">
+ <span class="title">${this.name}</span>
+ <div class="right">
+ ${when(
+ !this.permissionIsOwnerOrGlobal(
+ this.permission.id ?? '',
+ this.section
+ ),
+ () => html`
+ <paper-toggle-button
+ id="exclusiveToggle"
+ ?checked=${this.permission?.value.exclusive}
+ ?disabled=${!this.editing}
+ @change=${this.handleValueChange}
+ @click=${this.onTapExclusiveToggle}
+ ></paper-toggle-button
+ >${this.computeExclusiveLabel(this.permission?.value)}
+ `
+ )}
+ <gr-button
+ link=""
+ id="removeBtn"
+ @click=${this.handleRemovePermission}
+ >Remove</gr-button
+ >
+ </div>
+ </div>
+ <!-- end header -->
+ <div class="rules">
+ ${this.rules?.map(
+ (rule, index) => html`
+ <gr-rule-editor
+ .hasRange=${this.computeHasRange(this.name)}
+ .label=${this.label}
+ .editing=${this.editing}
+ .groupId=${rule.id}
+ .groupName=${this.computeGroupName(this.groups, rule.id)}
+ .permission=${this.permission!.id as AccessPermissionId}
+ .rule=${rule}
+ .section=${this.section}
+ @rule-changed=${(e: CustomEvent) =>
+ this.handleRuleChanged(e, index)}
+ @added-rule-removed=${(_: Event) =>
+ this.handleAddedRuleRemoved(index)}
+ ></gr-rule-editor>
+ `
+ )}
+ <div id="addRule">
+ <gr-autocomplete
+ id="groupAutocomplete"
+ .text=${this.groupFilter ?? ''}
+ @text-changed=${(e: ValueChangedEvent) =>
+ (this.groupFilter = e.detail.value)}
+ .query=${this.query}
+ placeholder="Add group"
+ @commit=${this.handleAddRuleItem}
+ >
+ </gr-autocomplete>
+ </div>
+ <!-- end addRule -->
+ </div>
+ <!-- end rules -->
+ </div>
+ <!-- end mainContainer -->
+ <div id="deletedContainer">
+ <span>${this.name} was deleted</span>
+ <gr-button link="" id="undoRemoveBtn" @click=${this.handleUndoRemove}
+ >Undo</gr-button
+ >
+ </div>
+ <!-- end deletedContainer -->
+ </section>
+ `;
+ }
+
+ setupValues() {
if (!this.permission) {
return;
}
- this._originalExclusiveValue = !!this.permission.value.exclusive;
- flush();
+ this.originalExclusiveValue = !!this.permission.value.exclusive;
+ this.requestUpdate();
}
- _handleAccessSaved() {
+ private handleAccessSaved() {
// Set a new 'original' value to keep track of after the value has been
// saved.
- this._setupValues();
+ this.setupValues();
}
- _permissionIsOwnerOrGlobal(permissionId: string, section: string) {
+ private permissionIsOwnerOrGlobal(permissionId: string, section: string) {
return permissionId === 'owner' || section === 'GLOBAL_CAPABILITIES';
}
- _handleEditingChanged(editing: boolean, editingOld: boolean) {
+ private handleEditingChanged(editingOld: boolean) {
// Ignore when editing gets set initially.
if (!editingOld) {
return;
}
- if (!this.permission || !this._rules) {
+ if (!this.permission || !this.rules) {
return;
}
// Restore original values if no longer editing.
- if (!editing) {
- this._deleted = false;
+ if (!this.editing) {
+ this.deleted = false;
delete this.permission.value.deleted;
- this._groupFilter = '';
- this._rules = this._rules.filter(rule => !rule.value.added);
+ this.groupFilter = '';
+ this.rules = this.rules.filter(rule => !rule.value!.added);
+ this.handleRulesChanged();
for (const key of Object.keys(this.permission.value.rules)) {
if (this.permission.value.rules[key].added) {
delete this.permission.value.rules[key];
@@ -193,58 +349,58 @@ export class GrPermission extends PolymerElement {
}
// Restore exclusive bit to original.
- this.set(
- ['permission', 'value', 'exclusive'],
- this._originalExclusiveValue
- );
+ this.permission.value.exclusive = this.originalExclusiveValue;
+ fire(this, 'permission-changed', {value: this.permission});
+ this.requestUpdate();
}
}
- _handleAddedRuleRemoved(e: PolymerDomRepeatEvent) {
- if (!this._rules) {
+ private handleAddedRuleRemoved(index: number) {
+ if (!this.rules) {
return;
}
- const index = e.model.index;
- this._rules = this._rules
+ this.rules = this.rules
.slice(0, index)
- .concat(this._rules.slice(index + 1, this._rules.length));
+ .concat(this.rules.slice(index + 1, this.rules.length));
+ this.handleRulesChanged();
}
- _handleValueChange() {
+ handleValueChange(e: Event) {
if (!this.permission) {
return;
}
this.permission.value.modified = true;
+ this.permission.value.exclusive = (e.target as HTMLInputElement).checked;
// Allows overall access page to know a change has been made.
fireEvent(this, 'access-modified');
}
- _handleRemovePermission() {
+ handleRemovePermission() {
if (!this.permission) {
return;
}
if (this.permission.value.added) {
fireEvent(this, 'added-permission-removed');
}
- this._deleted = true;
+ this.deleted = true;
this.permission.value.deleted = true;
fireEvent(this, 'access-modified');
}
- @observe('_rules.splices')
- _handleRulesChanged() {
- if (!this._rules) {
+ private handleRulesChanged() {
+ if (!this.rules) {
return;
}
// Update the groups to exclude in the autocomplete.
- this._groupsWithRules = this._computeGroupsWithRules(this._rules);
+ this.groupsWithRules = this.computeGroupsWithRules(this.rules);
}
- _sortPermission(permission: PermissionArrayItem<EditablePermissionInfo>) {
- this._rules = toSortedPermissionsArray(permission.value.rules);
+ sortPermission(permission?: PermissionArrayItem<EditablePermissionInfo>) {
+ this.rules = toSortedPermissionsArray(permission?.value.rules);
+ this.handleRulesChanged();
}
- _computeSectionClass(editing: boolean, deleted: boolean) {
+ computeSectionClass(editing: boolean, deleted: boolean) {
const classList = [];
if (editing) {
classList.push('editing');
@@ -255,18 +411,16 @@ export class GrPermission extends PolymerElement {
return classList.join(' ');
}
- _handleUndoRemove() {
+ handleUndoRemove() {
if (!this.permission) {
return;
}
- this._deleted = false;
+ this.deleted = false;
delete this.permission.value.deleted;
}
- _computeLabel(
- permission?: PermissionArrayItem<EditablePermissionInfo>,
- labels?: LabelNameToLabelTypeInfoMap
- ): ComputedLabel | undefined {
+ computeLabel(): ComputedLabel | undefined {
+ const {permission, labels} = this;
if (
!labels ||
!permission ||
@@ -285,11 +439,11 @@ export class GrPermission extends PolymerElement {
}
return {
name: labelName,
- values: this._computeLabelValues(labels[labelName].values),
+ values: this.computeLabelValues(labels[labelName].values),
};
}
- _computeLabelValues(values: LabelTypeInfoValues): ComputedLabelValue[] {
+ computeLabelValues(values: LabelTypeInfoValues): ComputedLabelValue[] {
const valuesArr: ComputedLabelValue[] = [];
const keys = Object.keys(values).sort((a, b) => Number(a) - Number(b));
@@ -305,8 +459,8 @@ export class GrPermission extends PolymerElement {
return valuesArr;
}
- _computeGroupsWithRules(
- rules: PermissionArray<EditablePermissionRuleInfo>
+ computeGroupsWithRules(
+ rules: PermissionArray<EditablePermissionRuleInfo | undefined>
): GroupsWithRulesMap {
const groups: GroupsWithRulesMap = {};
for (const rule of rules) {
@@ -315,16 +469,19 @@ export class GrPermission extends PolymerElement {
return groups;
}
- _computeGroupName(groups: ProjectAccessGroups, groupId: GroupId) {
+ computeGroupName(
+ groups: EditableProjectAccessGroups | undefined,
+ groupId: GitRef
+ ) {
return groups && groups[groupId] && groups[groupId].name
? groups[groupId].name
: groupId;
}
- _getGroupSuggestions(): Promise<AutocompleteSuggestion[]> {
+ getGroupSuggestions(): Promise<AutocompleteSuggestion[]> {
return this.restApiService
.getSuggestedGroups(
- this._groupFilter || '',
+ this.groupFilter || '',
this.repo,
MAX_AUTOCOMPLETE_RESULTS
)
@@ -337,7 +494,7 @@ export class GrPermission extends PolymerElement {
return groups
.filter(
group =>
- this._groupsWithRules && !this._groupsWithRules[group.value.id]
+ this.groupsWithRules && !this.groupsWithRules[group.value.id]
)
.map((group: GroupSuggestion) => {
const autocompleteSuggestion: AutocompleteSuggestion = {
@@ -353,8 +510,8 @@ export class GrPermission extends PolymerElement {
* Handles adding a skeleton item to the dom-repeat.
* gr-rule-editor handles setting the default values.
*/
- _handleAddRuleItem(e: AutocompleteCommitEvent) {
- if (!this.permission || !this._rules) {
+ async handleAddRuleItem(e: AutocompleteCommitEvent) {
+ if (!this.permission || !this.rules) {
return;
}
@@ -371,33 +528,35 @@ export class GrPermission extends PolymerElement {
// Purposely don't recompute sorted array so that the newly added rule
// is the last item of the array.
- this.push('_rules', {
- id: groupId,
+ this.rules.push({
+ id: groupId as GitRef,
+ value: undefined,
});
+ // Wait for new rule to get value populated via gr-rule-editor, and then
+ // add to permission values as well, so that the change gets propagated
+ // back to the section. Since the rule is inside a dom-repeat, a flush
+ // is needed.
+ this.requestUpdate();
+ await this.updateComplete;
// Add the new group name to the groups object so the name renders
// correctly.
if (this.groups && !this.groups[groupId]) {
- this.groups[groupId] = {name: this.$.groupAutocomplete.text};
+ this.groups[groupId] = {name: this.groupAutocomplete.text};
}
// Clear the text of the auto-complete box, so that the user can add the
// next group.
- this.$.groupAutocomplete.text = '';
+ this.groupAutocomplete.text = '';
- // Wait for new rule to get value populated via gr-rule-editor, and then
- // add to permission values as well, so that the change gets propagated
- // back to the section. Since the rule is inside a dom-repeat, a flush
- // is needed.
- flush();
- const value = this._rules[this._rules.length - 1].value;
- value.added = true;
- // See comment above for why we cannot use "this.set(...)" here.
- this.permission.value.rules[groupId] = value;
+ const value = this.rules[this.rules.length - 1].value;
+ value!.added = true;
+ this.permission.value.rules[groupId] = value!;
fireEvent(this, 'access-modified');
+ this.requestUpdate();
}
- _computeHasRange(name: string) {
+ computeHasRange(name?: string) {
if (!name) {
return false;
}
@@ -405,15 +564,30 @@ export class GrPermission extends PolymerElement {
return RANGE_NAMES.includes(name.toUpperCase());
}
+ private computeExclusiveLabel(permission?: EditablePermissionInfo) {
+ return permission?.exclusive ? 'Exclusive' : 'Not Exclusive';
+ }
+
/**
* Work around a issue on iOS when clicking turns into double tap
*/
- _onTapExclusiveToggle(e: Event) {
+ private onTapExclusiveToggle(e: Event) {
e.preventDefault();
}
+
+ private handleRuleChanged(e: CustomEvent, index: number) {
+ this.rules!.splice(index, e.detail.value);
+ this.handleRulesChanged();
+ this.requestUpdate();
+ }
}
declare global {
+ interface HTMLElementEventMap {
+ 'permission-changed': ValueChangedEvent<
+ PermissionArrayItem<EditablePermissionInfo>
+ >;
+ }
interface HTMLElementTagNameMap {
'gr-permission': GrPermission;
}