Allow users to select which changes to cherry pick
When cherry pickinging an entire topic to another branch, there are abandoned/unwanted changes with the same topic name. Allow users to select which changes to cherry pick by showing a checkbox next to each change. Also show the status of each change(Abandoned, Merged, New) to help users select which change to select/remove. Change-Id: I453bf77aa9a6c4ff7c0568c70dad1ee7ba78bc3b
This commit is contained in:
@@ -742,11 +742,11 @@ suite('gr-change-actions tests', () => {
|
||||
const changes = [
|
||||
{
|
||||
change_id: '12345678901234', topic: 'T', subject: 'random',
|
||||
project: 'A',
|
||||
project: 'A', status: 'MERGED',
|
||||
},
|
||||
{
|
||||
change_id: '23456', topic: 'T', subject: 'a'.repeat(100),
|
||||
project: 'B',
|
||||
project: 'B', status: 'NEW',
|
||||
},
|
||||
];
|
||||
setup(done => {
|
||||
@@ -769,8 +769,8 @@ suite('gr-change-actions tests', () => {
|
||||
flush(() => {
|
||||
const changesTable = dialog.shadowRoot.querySelector('table');
|
||||
const headers = Array.from(changesTable.querySelectorAll('th'));
|
||||
const expectedHeadings = ['Change', 'Subject', 'Project',
|
||||
'Status', ''];
|
||||
const expectedHeadings = ['', 'Change', 'Status', 'Subject',
|
||||
'Project', 'Progress', ''];
|
||||
const headings = headers.map(header => header.innerText);
|
||||
assert.equal(headings.length, expectedHeadings.length);
|
||||
for (let i = 0; i < headings.length; i++) {
|
||||
@@ -779,7 +779,7 @@ suite('gr-change-actions tests', () => {
|
||||
const changeRows = changesTable.querySelectorAll('tbody > tr');
|
||||
const change = Array.from(changeRows[0].querySelectorAll('td'))
|
||||
.map(e => e.innerText);
|
||||
const expectedChange = ['1234567890', 'random', 'A',
|
||||
const expectedChange = ['', '1234567890', 'MERGED', 'random', 'A',
|
||||
'NOT STARTED', ''];
|
||||
for (let i = 0; i < change.length; i++) {
|
||||
assert.equal(change[i].trim(), expectedChange[i]);
|
||||
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
RepoName,
|
||||
BranchName,
|
||||
CommitId,
|
||||
ChangeInfoId,
|
||||
} from '../../../types/common';
|
||||
import {ReportingService} from '../../../services/gr-reporting/gr-reporting';
|
||||
import {customElement, property, observe} from '@polymer/decorators';
|
||||
@@ -40,6 +41,7 @@ import {
|
||||
} from '../../shared/gr-autocomplete/gr-autocomplete';
|
||||
import {HttpMethod, ChangeStatus} from '../../../constants/constants';
|
||||
import {hasOwnProperty} from '../../../utils/common-util';
|
||||
import {dom, EventApi} from '@polymer/polymer/lib/legacy/polymer.dom';
|
||||
|
||||
const SUGGESTIONS_LIMIT = 15;
|
||||
const CHANGE_SUBJECT_LIMIT = 50;
|
||||
@@ -146,6 +148,8 @@ export class GrConfirmCherrypickDialog extends GestureEventListeners(
|
||||
@property({type: Object})
|
||||
reporting: ReportingService;
|
||||
|
||||
private selectedChangeIds = new Set<ChangeInfoId>();
|
||||
|
||||
private restApiService = appContext.restApiService;
|
||||
|
||||
constructor() {
|
||||
@@ -161,6 +165,7 @@ export class GrConfirmCherrypickDialog extends GestureEventListeners(
|
||||
const projects: {[projectName: string]: boolean} = {};
|
||||
this._duplicateProjectChanges = false;
|
||||
changes.forEach(change => {
|
||||
this.selectedChangeIds.add(change.id);
|
||||
if (projects[change.project]) {
|
||||
this._duplicateProjectChanges = true;
|
||||
}
|
||||
@@ -178,6 +183,19 @@ export class GrConfirmCherrypickDialog extends GestureEventListeners(
|
||||
);
|
||||
}
|
||||
|
||||
_isChangeSelected(changeId: ChangeInfoId) {
|
||||
return this.selectedChangeIds.has(changeId);
|
||||
}
|
||||
|
||||
_toggleChangeSelected(e: Event) {
|
||||
const changeId = ((dom(e) as EventApi).localTarget as HTMLElement).dataset[
|
||||
'item'
|
||||
]! as ChangeInfoId;
|
||||
if (this.selectedChangeIds.has(changeId))
|
||||
this.selectedChangeIds.delete(changeId);
|
||||
else this.selectedChangeIds.add(changeId);
|
||||
}
|
||||
|
||||
_computeTopicErrorMessage(duplicateProjectChanges: boolean) {
|
||||
if (duplicateProjectChanges) {
|
||||
return 'Two changes cannot be of the same project';
|
||||
@@ -293,8 +311,16 @@ export class GrConfirmCherrypickDialog extends GestureEventListeners(
|
||||
}
|
||||
|
||||
_handleCherryPickTopic() {
|
||||
const topic = this._generateRandomCherryPickTopic(this.changes[0]);
|
||||
this.changes.forEach(change => {
|
||||
const changes = this.changes.filter(change =>
|
||||
this.selectedChangeIds.has(change.id)
|
||||
);
|
||||
if (!changes.length) {
|
||||
const errorSpan = this.shadowRoot?.querySelector('.error-message');
|
||||
errorSpan!.innerHTML = 'No change selected';
|
||||
return;
|
||||
}
|
||||
const topic = this._generateRandomCherryPickTopic(changes[0]);
|
||||
changes.forEach(change => {
|
||||
this.updateStatus(change, {status: ProgressStatus.RUNNING});
|
||||
const payload = {
|
||||
destination: this.branch,
|
||||
|
||||
@@ -179,10 +179,12 @@ export const htmlTemplate = html`
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>Change</th>
|
||||
<th>Status</th>
|
||||
<th>Subject</th>
|
||||
<th>Project</th>
|
||||
<th>Status</th>
|
||||
<th>Progress</th>
|
||||
<!-- Error Message -->
|
||||
<th></th>
|
||||
</tr>
|
||||
@@ -190,7 +192,16 @@ export const htmlTemplate = html`
|
||||
<tbody>
|
||||
<template is="dom-repeat" items="[[changes]]">
|
||||
<tr>
|
||||
<td>
|
||||
<input
|
||||
type="checkbox"
|
||||
data-item$="[[item.id]]"
|
||||
on-change="_toggleChangeSelected"
|
||||
checked="[[_isChangeSelected(item.id)]]"
|
||||
/>
|
||||
</td>
|
||||
<td><span> [[_getChangeId(item)]] </span></td>
|
||||
<td><span> [[item.status]] </span></td>
|
||||
<td>
|
||||
<span> [[_getTrimmedChangeSubject(item.subject)]] </span>
|
||||
</td>
|
||||
|
||||
@@ -88,6 +88,7 @@ suite('gr-confirm-cherrypick-dialog tests', () => {
|
||||
suite('cherry pick topic', () => {
|
||||
const changes = [
|
||||
{
|
||||
id: '1234',
|
||||
change_id: '12345678901234', topic: 'T', subject: 'random',
|
||||
project: 'A',
|
||||
_number: 1,
|
||||
@@ -97,6 +98,7 @@ suite('gr-confirm-cherrypick-dialog tests', () => {
|
||||
current_revision: 'a',
|
||||
},
|
||||
{
|
||||
id: '5678',
|
||||
change_id: '23456', topic: 'T', subject: 'a'.repeat(100),
|
||||
project: 'B',
|
||||
_number: 2,
|
||||
@@ -109,6 +111,7 @@ suite('gr-confirm-cherrypick-dialog tests', () => {
|
||||
setup(() => {
|
||||
element.updateChanges(changes);
|
||||
element._cherryPickType = CHERRY_PICK_TYPES.TOPIC;
|
||||
flush();
|
||||
});
|
||||
|
||||
test('cherry pick topic submit', done => {
|
||||
@@ -129,6 +132,38 @@ suite('gr-confirm-cherrypick-dialog tests', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('deselecting a change removes it from being cherry picked', () => {
|
||||
element.branch = 'master';
|
||||
const executeChangeActionStub = sinon.stub(element.restApiService,
|
||||
'executeChangeAction').returns(Promise.resolve([]));
|
||||
const checkboxes = element.shadowRoot.querySelectorAll(
|
||||
'input[type="checkbox"]');
|
||||
assert.equal(checkboxes.length, 2);
|
||||
assert.isTrue(checkboxes[0].checked);
|
||||
MockInteractions.tap(checkboxes[0]);
|
||||
MockInteractions.tap(element.shadowRoot.
|
||||
querySelector('gr-dialog').$.confirm);
|
||||
flush();
|
||||
assert.equal(executeChangeActionStub.callCount, 1);
|
||||
});
|
||||
|
||||
test('deselecting all change shows error message', () => {
|
||||
element.branch = 'master';
|
||||
const executeChangeActionStub = sinon.stub(element.restApiService,
|
||||
'executeChangeAction').returns(Promise.resolve([]));
|
||||
const checkboxes = element.shadowRoot.querySelectorAll(
|
||||
'input[type="checkbox"]');
|
||||
assert.equal(checkboxes.length, 2);
|
||||
MockInteractions.tap(checkboxes[0]);
|
||||
MockInteractions.tap(checkboxes[1]);
|
||||
MockInteractions.tap(element.shadowRoot.
|
||||
querySelector('gr-dialog').$.confirm);
|
||||
flush();
|
||||
assert.equal(executeChangeActionStub.callCount, 0);
|
||||
assert.equal(element.shadowRoot.querySelector('.error-message').innerText
|
||||
, 'No change selected');
|
||||
});
|
||||
|
||||
test('_computeStatusClass', () => {
|
||||
assert.equal(element._computeStatusClass({id: 1}, {1: {status: 'RUNNING'},
|
||||
}), '');
|
||||
|
||||
Reference in New Issue
Block a user