blob: 13d0e50dca3098a3d8d9c3d47ed4cdeffbfcbfbe [file] [log] [blame]
/**
* @license
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import '@polymer/iron-autogrow-textarea/iron-autogrow-textarea';
import '../../../styles/gr-form-styles';
import '../../../styles/shared-styles';
import '../../shared/gr-button/gr-button';
import '../../shared/gr-select/gr-select';
import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-rule-editor_html';
import {encodeURL, getBaseUrl} from '../../../utils/url-util';
import {AccessPermissionId} from '../../../utils/access-util';
import {property, customElement, observe} from '@polymer/decorators';
import {fireEvent} from '../../../utils/event-util';
/**
* Fired when the rule has been modified or removed.
*
* @event access-modified
*/
/**
* Fired when a rule that was previously added was removed.
*
* @event added-rule-removed
*/
const PRIORITY_OPTIONS = ['BATCH', 'INTERACTIVE'];
const Action = {
ALLOW: 'ALLOW',
DENY: 'DENY',
BLOCK: 'BLOCK',
};
const DROPDOWN_OPTIONS = [Action.ALLOW, Action.DENY, Action.BLOCK];
const ForcePushOptions = {
ALLOW: [
{name: 'Allow pushing (but not force pushing)', value: false},
{name: 'Allow pushing with or without force', value: true},
],
BLOCK: [
{name: 'Block pushing with or without force', value: false},
{name: 'Block force pushing', value: true},
],
};
const FORCE_EDIT_OPTIONS = [
{
name: 'No Force Edit',
value: false,
},
{
name: 'Force Edit',
value: true,
},
];
interface Rule {
value: RuleValue;
}
interface RuleValue {
min?: number;
max?: number;
force?: boolean;
action?: string;
added?: boolean;
modified?: boolean;
deleted?: boolean;
}
interface RuleLabel {
values: RuleLabelValue[];
}
interface RuleLabelValue {
value: number;
text: string;
}
declare global {
interface HTMLElementTagNameMap {
'gr-rule-editor': GrRuleEditor;
}
}
@customElement('gr-rule-editor')
export class GrRuleEditor extends GestureEventListeners(
LegacyElementMixin(PolymerElement)
) {
static get template() {
return htmlTemplate;
}
@property({type: Boolean})
hasRange?: boolean;
@property({type: Object})
label?: RuleLabel;
@property({type: Boolean, observer: '_handleEditingChanged'})
editing = false;
@property({type: String})
groupId?: string;
@property({type: String})
groupName?: string;
// This is required value for this component
@property({type: String})
permission!: AccessPermissionId;
@property({type: Object, notify: true})
rule?: Rule;
@property({type: String})
section?: string;
@property({type: Boolean})
_deleted = false;
@property({type: Object})
_originalRuleValues?: RuleValue;
/** @override */
created() {
super.created();
this.addEventListener('access-saved', () => this._handleAccessSaved());
}
/** @override */
ready() {
super.ready();
// Called on ready rather than the observer because when new rules are
// added, the observer is triggered prior to being ready.
if (!this.rule) {
return;
} // Check needed for test purposes.
this._setupValues(this.rule);
}
/** @override */
attached() {
super.attached();
// Check needed for test purposes.
if (!this._originalRuleValues && this.rule) {
// Observer _handleValueChange is called after the ready()
// method finishes. Original values must be set later to
// avoid set .modified flag to true
this._setOriginalRuleValues(this.rule.value);
}
}
_setupValues(rule: Rule) {
if (!rule.value) {
this._setDefaultRuleValues();
}
}
_computeForce(permission: AccessPermissionId, action: string) {
if (AccessPermissionId.PUSH === permission && action !== Action.DENY) {
return true;
}
return AccessPermissionId.EDIT_TOPIC_NAME === permission;
}
_computeForceClass(permission: AccessPermissionId, action: string) {
return this._computeForce(permission, action) ? 'force' : '';
}
_computeGroupPath(group: string) {
return `${getBaseUrl()}/admin/groups/${encodeURL(group, true)}`;
}
_handleAccessSaved() {
if (!this.rule) return;
// Set a new 'original' value to keep track of after the value has been
// saved.
this._setOriginalRuleValues(this.rule.value);
}
_handleEditingChanged(editing: boolean, editingOld: boolean) {
// Ignore when editing gets set initially.
if (!editingOld) {
return;
}
// Restore original values if no longer editing.
if (!editing) {
this._handleUndoChange();
}
}
_computeSectionClass(editing: boolean, deleted: boolean) {
const classList = [];
if (editing) {
classList.push('editing');
}
if (deleted) {
classList.push('deleted');
}
return classList.join(' ');
}
_computeForceOptions(permission: string, action: string) {
if (permission === AccessPermissionId.PUSH) {
if (action === Action.ALLOW) {
return ForcePushOptions.ALLOW;
} else if (action === Action.BLOCK) {
return ForcePushOptions.BLOCK;
} else {
return [];
}
} else if (permission === AccessPermissionId.EDIT_TOPIC_NAME) {
return FORCE_EDIT_OPTIONS;
}
return [];
}
_getDefaultRuleValues(permission: AccessPermissionId, label?: RuleLabel) {
const ruleAction = Action.ALLOW;
const value: RuleValue = {};
if (permission === AccessPermissionId.PRIORITY) {
value.action = PRIORITY_OPTIONS[0];
return value;
} else if (label) {
value.min = label.values[0].value;
value.max = label.values[label.values.length - 1].value;
} else if (this._computeForce(permission, ruleAction)) {
value.force = this._computeForceOptions(permission, ruleAction)[0].value;
}
value.action = DROPDOWN_OPTIONS[0];
return value;
}
_setDefaultRuleValues() {
this.set(
'rule.value',
this._getDefaultRuleValues(this.permission, this.label)
);
}
_computeOptions(permission: string) {
if (permission === 'priority') {
return PRIORITY_OPTIONS;
}
return DROPDOWN_OPTIONS;
}
_handleRemoveRule() {
if (!this.rule) return;
if (this.rule.value.added) {
fireEvent(this, 'added-rule-removed');
}
this._deleted = true;
this.rule.value.deleted = true;
fireEvent(this, 'access-modified');
}
_handleUndoRemove() {
if (!this.rule) return;
this._deleted = false;
delete this.rule.value.deleted;
}
_handleUndoChange() {
if (!this.rule) return;
// gr-permission will take care of removing rules that were added but
// unsaved. We need to keep the added bit for the filter.
if (this.rule.value.added) {
return;
}
this.set('rule.value', {...this._originalRuleValues});
this._deleted = false;
delete this.rule.value.deleted;
delete this.rule.value.modified;
}
@observe('rule.value.*')
_handleValueChange() {
if (!this._originalRuleValues || !this.rule) {
return;
}
this.rule.value.modified = true;
// Allows overall access page to know a change has been made.
fireEvent(this, 'access-modified');
}
_setOriginalRuleValues(value: RuleValue) {
this._originalRuleValues = {...value};
}
}