Merge "Clean up Add Patchset Description from File List header" into stable-3.4
This commit is contained in:
commit
9093a638bb
|
@ -18,24 +18,17 @@ import '../../../styles/shared-styles';
|
|||
import '../../diff/gr-diff-mode-selector/gr-diff-mode-selector';
|
||||
import '../../diff/gr-patch-range-select/gr-patch-range-select';
|
||||
import '../../edit/gr-edit-controls/gr-edit-controls';
|
||||
import '../../shared/gr-editable-label/gr-editable-label';
|
||||
import '../../shared/gr-linked-chip/gr-linked-chip';
|
||||
import '../../shared/gr-select/gr-select';
|
||||
import '../../shared/gr-button/gr-button';
|
||||
import '../../shared/gr-icons/gr-icons';
|
||||
import '../gr-commit-info/gr-commit-info';
|
||||
import {dom, EventApi} from '@polymer/polymer/lib/legacy/polymer.dom';
|
||||
import {PolymerElement} from '@polymer/polymer/polymer-element';
|
||||
import {htmlTemplate} from './gr-file-list-header_html';
|
||||
import {KeyboardShortcutMixin} from '../../../mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin';
|
||||
import {FilesExpandedState} from '../gr-file-list-constants';
|
||||
import {GerritNav} from '../../core/gr-navigation/gr-navigation';
|
||||
import {
|
||||
computeLatestPatchNum,
|
||||
getRevisionByPatchNum,
|
||||
PatchSet,
|
||||
} from '../../../utils/patch-set-util';
|
||||
import {property, computed, observe, customElement} from '@polymer/decorators';
|
||||
import {computeLatestPatchNum, PatchSet} from '../../../utils/patch-set-util';
|
||||
import {property, customElement} from '@polymer/decorators';
|
||||
import {
|
||||
AccountInfo,
|
||||
ChangeInfo,
|
||||
|
@ -51,11 +44,8 @@ import {ChangeComments} from '../../diff/gr-comment-api/gr-comment-api';
|
|||
import {GrDiffModeSelector} from '../../diff/gr-diff-mode-selector/gr-diff-mode-selector';
|
||||
import {ChangeStatus, DiffViewMode} from '../../../constants/constants';
|
||||
import {GrButton} from '../../shared/gr-button/gr-button';
|
||||
import {appContext} from '../../../services/app-context';
|
||||
import {fireEvent} from '../../../utils/event-util';
|
||||
|
||||
// Maximum length for patch set descriptions.
|
||||
const PATCH_DESC_MAX_LENGTH = 500;
|
||||
const MERGED_STATUS = 'MERGED';
|
||||
|
||||
declare global {
|
||||
|
@ -154,30 +144,9 @@ export class GrFileListHeader extends KeyboardShortcutMixin(PolymerElement) {
|
|||
@property({type: Number})
|
||||
readonly _maxFilesForBulkActions = 225;
|
||||
|
||||
@property({type: String})
|
||||
_patchsetDescription = '';
|
||||
|
||||
@property({type: Object})
|
||||
revisionInfo?: RevisionInfo;
|
||||
|
||||
private readonly restApiService = appContext.restApiService;
|
||||
|
||||
@computed('loggedIn', 'change', 'account')
|
||||
get _descriptionReadOnly(): boolean {
|
||||
if (
|
||||
this.loggedIn === undefined ||
|
||||
this.change === undefined ||
|
||||
this.account === undefined
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return !(
|
||||
this.loggedIn &&
|
||||
this.account._account_id === this.change.owner._account_id
|
||||
);
|
||||
}
|
||||
|
||||
setDiffViewMode(mode: DiffViewMode) {
|
||||
this.$.modeSelect.setMode(mode);
|
||||
}
|
||||
|
@ -202,98 +171,6 @@ export class GrFileListHeader extends KeyboardShortcutMixin(PolymerElement) {
|
|||
return classes.join(' ');
|
||||
}
|
||||
|
||||
_computeDescriptionPlaceholder(readOnly: boolean) {
|
||||
return (readOnly ? 'No' : 'Add') + ' patchset description';
|
||||
}
|
||||
|
||||
@observe('change', 'patchNum')
|
||||
_computePatchSetDescription(change: ChangeInfo, patchNum: PatchSetNum) {
|
||||
// Polymer 2: check for undefined
|
||||
if (
|
||||
change === undefined ||
|
||||
change.revisions === undefined ||
|
||||
patchNum === undefined
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const rev = getRevisionByPatchNum(
|
||||
Object.values(change.revisions),
|
||||
patchNum
|
||||
);
|
||||
this._patchsetDescription = rev?.description
|
||||
? rev.description.substring(0, PATCH_DESC_MAX_LENGTH)
|
||||
: '';
|
||||
}
|
||||
|
||||
_handleDescriptionRemoved(e: CustomEvent) {
|
||||
return this._updateDescription('', e);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param revisions The revisions object keyed by revision hashes
|
||||
* @param patchSet A revision already fetched from {revisions}
|
||||
* @return the SHA hash corresponding to the revision.
|
||||
*/
|
||||
_getPatchsetHash(
|
||||
revisions: {[revisionId: string]: RevisionInfo},
|
||||
patchSet: RevisionInfo
|
||||
) {
|
||||
for (const sha of Object.keys(revisions)) {
|
||||
if (revisions[sha] === patchSet) {
|
||||
return sha;
|
||||
}
|
||||
}
|
||||
throw new Error('patchset hash not found');
|
||||
}
|
||||
|
||||
_handleDescriptionChanged(e: CustomEvent) {
|
||||
const desc = e.detail.trim();
|
||||
this._updateDescription(desc, e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the patchset description with the rest API.
|
||||
*/
|
||||
_updateDescription(desc: string, e: CustomEvent) {
|
||||
if (
|
||||
!this.change ||
|
||||
!this.change.revisions ||
|
||||
!this.patchNum ||
|
||||
!this.changeNum
|
||||
)
|
||||
return;
|
||||
// target can be either gr-editable-label or gr-linked-chip
|
||||
const target = (dom(e) as EventApi).rootTarget as HTMLElement & {
|
||||
disabled: boolean;
|
||||
};
|
||||
if (target) {
|
||||
target.disabled = true;
|
||||
}
|
||||
const rev = getRevisionByPatchNum(
|
||||
Object.values(this.change.revisions),
|
||||
this.patchNum
|
||||
)!;
|
||||
const sha = this._getPatchsetHash(this.change.revisions, rev);
|
||||
return this.restApiService
|
||||
.setDescription(this.changeNum, this.patchNum, desc)
|
||||
.then((res: Response) => {
|
||||
if (res.ok) {
|
||||
if (target) {
|
||||
target.disabled = false;
|
||||
}
|
||||
this.set(['change', 'revisions', sha, 'description'], desc);
|
||||
this._patchsetDescription = desc;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
if (target) {
|
||||
target.disabled = false;
|
||||
}
|
||||
return;
|
||||
});
|
||||
}
|
||||
|
||||
_computePrefsButtonHidden(
|
||||
prefs: DiffPreferencesInfo,
|
||||
diffPrefsDisabled: boolean
|
||||
|
|
|
@ -53,9 +53,6 @@ export const htmlTemplate = html`
|
|||
.latestPatchContainer a {
|
||||
text-decoration: none;
|
||||
}
|
||||
gr-editable-label.descriptionLabel {
|
||||
max-width: 100%;
|
||||
}
|
||||
.mobile {
|
||||
display: none;
|
||||
}
|
||||
|
@ -89,9 +86,6 @@ export const htmlTemplate = html`
|
|||
.someExpanded #expandBtn {
|
||||
margin-right: 8px;
|
||||
}
|
||||
gr-linked-chip {
|
||||
--linked-chip-text-color: var(--primary-text-color);
|
||||
}
|
||||
.someExpanded #collapseBtn,
|
||||
.allExpanded #collapseBtn,
|
||||
.openFile .fileViewActions {
|
||||
|
@ -165,32 +159,6 @@ export const htmlTemplate = html`
|
|||
<span class="separator"></span>
|
||||
<a href$="[[changeUrl]]">Go to latest patch set</a>
|
||||
</span>
|
||||
<span class="container descriptionContainer hideOnEdit">
|
||||
<span class="separator"></span>
|
||||
<template is="dom-if" if="[[_patchsetDescription]]">
|
||||
<gr-linked-chip
|
||||
id="descriptionChip"
|
||||
text="[[_patchsetDescription]]"
|
||||
removable="[[!_descriptionReadOnly]]"
|
||||
on-remove="_handleDescriptionRemoved"
|
||||
></gr-linked-chip>
|
||||
</template>
|
||||
<template
|
||||
is="dom-if"
|
||||
if="[[_showAddPatchsetDescription(_patchsetDescription, change)]]"
|
||||
>
|
||||
<gr-editable-label
|
||||
id="descriptionLabel"
|
||||
uppercase=""
|
||||
class="descriptionLabel"
|
||||
label-text="Add patchset description"
|
||||
value="[[_patchsetDescription]]"
|
||||
placeholder="[[_computeDescriptionPlaceholder(_descriptionReadOnly)]]"
|
||||
read-only="[[_descriptionReadOnly]]"
|
||||
on-changed="_handleDescriptionChanged"
|
||||
></gr-editable-label>
|
||||
</template>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class$="rightControls [[_computeExpandedClass(filesExpanded)]]">
|
||||
|
|
|
@ -59,99 +59,6 @@ suite('gr-file-list-header tests', () => {
|
|||
assert.isFalse(element.$.diffPrefsContainer.hidden);
|
||||
});
|
||||
|
||||
test('_computeDescriptionReadOnly', () => {
|
||||
element.loggedIn = false;
|
||||
element.change = {owner: {_account_id: 1}};
|
||||
element.account = {_account_id: 1};
|
||||
assert.equal(element._descriptionReadOnly, true);
|
||||
|
||||
element.loggedIn = true;
|
||||
element.change = {owner: {_account_id: 0}};
|
||||
element.account = {_account_id: 1};
|
||||
assert.equal(element._descriptionReadOnly, true);
|
||||
|
||||
element.loggedIn = true;
|
||||
element.change = {owner: {_account_id: 1}};
|
||||
element.account = {_account_id: 1};
|
||||
assert.equal(element._descriptionReadOnly, false);
|
||||
});
|
||||
|
||||
test('_computeDescriptionPlaceholder', () => {
|
||||
assert.equal(element._computeDescriptionPlaceholder(true),
|
||||
'No patchset description');
|
||||
assert.equal(element._computeDescriptionPlaceholder(false),
|
||||
'Add patchset description');
|
||||
});
|
||||
|
||||
test('description editing', () => {
|
||||
const putDescStub = stubRestApi('setDescription')
|
||||
.returns(Promise.resolve({ok: true}));
|
||||
|
||||
element.changeNum = '42';
|
||||
element.basePatchNum = 'PARENT';
|
||||
element.patchNum = 1;
|
||||
|
||||
element.change = {
|
||||
change_id: 'Iad9dc96274af6946f3632be53b106ef80f7ba6ca',
|
||||
revisions: {
|
||||
rev1: {_number: 1, description: 'test', commit: {commit: 'rev1'}},
|
||||
},
|
||||
current_revision: 'rev1',
|
||||
status: 'NEW',
|
||||
labels: {},
|
||||
actions: {},
|
||||
owner: {_account_id: 1},
|
||||
};
|
||||
element.account = {_account_id: 1};
|
||||
element.owner = {_account_id: 1};
|
||||
element.loggedIn = true;
|
||||
|
||||
flush();
|
||||
|
||||
// The element has a description, so the account chip should be visible
|
||||
element.owner = {_account_id: 1};
|
||||
// and the description label should not exist.
|
||||
const chip = element.root.querySelector('#descriptionChip');
|
||||
let label = element.root.querySelector('#descriptionLabel');
|
||||
|
||||
assert.equal(chip.text, 'test');
|
||||
assert.isNotOk(label);
|
||||
|
||||
// Simulate tapping the remove button, but call function directly so that
|
||||
// can determine what happens after the promise is resolved.
|
||||
return element._handleDescriptionRemoved()
|
||||
.then(() => {
|
||||
// The API stub should be called with an empty string for the new
|
||||
// description.
|
||||
assert.equal(putDescStub.lastCall.args[2], '');
|
||||
assert.equal(element.change.revisions.rev1.description, '');
|
||||
|
||||
flush();
|
||||
// The editable label should now be visible and the chip hidden.
|
||||
label = element.root.querySelector('#descriptionLabel');
|
||||
assert.isOk(label);
|
||||
assert.equal(getComputedStyle(chip).display, 'none');
|
||||
assert.notEqual(getComputedStyle(label).display, 'none');
|
||||
assert.isFalse(label.readOnly);
|
||||
// Edit the label to have a new value of test2, and save.
|
||||
label.editing = true;
|
||||
label._inputText = 'test2';
|
||||
label._save();
|
||||
flush();
|
||||
// The API stub should be called with an `test2` for the new
|
||||
// description.
|
||||
assert.equal(putDescStub.callCount, 2);
|
||||
assert.equal(putDescStub.lastCall.args[2], 'test2');
|
||||
})
|
||||
.then(() => {
|
||||
flush();
|
||||
// The chip should be visible again, and the label hidden.
|
||||
assert.equal(element.change.revisions.rev1.description, 'test2');
|
||||
assert.equal(getComputedStyle(label).display, 'none');
|
||||
assert.notEqual(getComputedStyle(chip).display, 'none');
|
||||
});
|
||||
});
|
||||
|
||||
test('expandAllDiffs called when expand button clicked', () => {
|
||||
element.shownFileCount = 1;
|
||||
flush();
|
||||
|
@ -280,14 +187,10 @@ suite('gr-file-list-header tests', () => {
|
|||
flush();
|
||||
|
||||
assert.isFalse(isVisible(element.$.diffPrefsContainer));
|
||||
assert.isFalse(isVisible(element.shadowRoot
|
||||
.querySelector('.descriptionContainer')));
|
||||
|
||||
element.editMode = false;
|
||||
flush();
|
||||
|
||||
assert.isTrue(isVisible(element.shadowRoot
|
||||
.querySelector('.descriptionContainer')));
|
||||
assert.isTrue(isVisible(element.$.diffPrefsContainer));
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in New Issue