Remove old implementation of gr-related-changes-list
Change-Id: Ic42f9af760042cd0e080a29b09c3d21d20bd161c (cherry picked from commit f819216d75fc9d171b1154acaa48e2dbb96bb35b)
This commit is contained in:
@@ -38,7 +38,6 @@ import '../gr-file-list-header/gr-file-list-header';
|
||||
import '../gr-included-in-dialog/gr-included-in-dialog';
|
||||
import '../gr-messages-list/gr-messages-list';
|
||||
import '../gr-related-changes-list/gr-related-changes-list';
|
||||
import '../gr-related-changes-list-experimental/gr-related-changes-list-experimental';
|
||||
import '../../diff/gr-apply-fix-dialog/gr-apply-fix-dialog';
|
||||
import '../gr-reply-dialog/gr-reply-dialog';
|
||||
import '../gr-thread-list/gr-thread-list';
|
||||
@@ -52,10 +51,7 @@ import {
|
||||
} from '../../../mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin';
|
||||
import {GrEditConstants} from '../../edit/gr-edit-constants';
|
||||
import {pluralize} from '../../../utils/string-util';
|
||||
import {
|
||||
getComputedStyleValue,
|
||||
windowLocationReload,
|
||||
} from '../../../utils/dom-util';
|
||||
import {windowLocationReload} from '../../../utils/dom-util';
|
||||
import {GerritNav} from '../../core/gr-navigation/gr-navigation';
|
||||
import {getPluginEndpoints} from '../../shared/gr-js-api-interface/gr-plugin-endpoints';
|
||||
import {getPluginLoader} from '../../shared/gr-js-api-interface/gr-plugin-loader';
|
||||
@@ -172,7 +168,6 @@ import {GerritView} from '../../../services/router/router-model';
|
||||
import {takeUntil} from 'rxjs/operators';
|
||||
import {aPluginHasRegistered$} from '../../../services/checks/checks-model';
|
||||
import {Subject} from 'rxjs';
|
||||
import {GrRelatedChangesListExperimental} from '../gr-related-changes-list-experimental/gr-related-changes-list-experimental';
|
||||
import {debounce, DelayedTask} from '../../../utils/async-util';
|
||||
import {Timing} from '../../../constants/reporting';
|
||||
|
||||
@@ -187,17 +182,6 @@ const MIN_LINES_FOR_COMMIT_COLLAPSE = 30;
|
||||
const REVIEWERS_REGEX = /^(R|CC)=/gm;
|
||||
const MIN_CHECK_INTERVAL_SECS = 0;
|
||||
|
||||
// These are the same as the breakpoint set in CSS. Make sure both are changed
|
||||
// together.
|
||||
const BREAKPOINT_RELATED_SMALL = '50em';
|
||||
const BREAKPOINT_RELATED_MED = '75em';
|
||||
|
||||
// In the event that the related changes medium width calculation is too close
|
||||
// to zero, provide some height.
|
||||
const MINIMUM_RELATED_MAX_HEIGHT = 100;
|
||||
|
||||
const SMALL_RELATED_HEIGHT = 400;
|
||||
|
||||
const REPLY_REFIT_DEBOUNCE_INTERVAL_MS = 500;
|
||||
|
||||
const TRAILING_WHITESPACE_REGEX = /[ \t]+$/gm;
|
||||
@@ -467,9 +451,6 @@ export class GrChangeView extends KeyboardShortcutMixin(PolymerElement) {
|
||||
})
|
||||
_commitCollapsible?: boolean;
|
||||
|
||||
@property({type: Boolean})
|
||||
_relatedChangesCollapsed = true;
|
||||
|
||||
@property({type: Number})
|
||||
_updateCheckTimerHandle?: number | null;
|
||||
|
||||
@@ -479,9 +460,6 @@ export class GrChangeView extends KeyboardShortcutMixin(PolymerElement) {
|
||||
})
|
||||
_editMode?: boolean;
|
||||
|
||||
@property({type: Boolean, observer: '_updateToggleContainerClass'})
|
||||
_showRelatedToggle = false;
|
||||
|
||||
@property({
|
||||
type: Boolean,
|
||||
computed: '_isParentCurrent(_currentRevisionActions)',
|
||||
@@ -1302,7 +1280,6 @@ export class GrChangeView extends KeyboardShortcutMixin(PolymerElement) {
|
||||
|
||||
this._initialLoadComplete = false;
|
||||
this._changeNum = value.changeNum;
|
||||
this.getRelatedChangesList()?.clear();
|
||||
this._reload(true).then(() => {
|
||||
this._performPostLoadTasks();
|
||||
});
|
||||
@@ -2161,7 +2138,6 @@ export class GrChangeView extends KeyboardShortcutMixin(PolymerElement) {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
this._loading = true;
|
||||
this._relatedChangesCollapsed = true;
|
||||
this.reporting.time(Timing.CHANGE_RELOAD);
|
||||
this.reporting.time(Timing.CHANGE_DATA);
|
||||
|
||||
@@ -2259,30 +2235,25 @@ export class GrChangeView extends KeyboardShortcutMixin(PolymerElement) {
|
||||
if (isLocationChange) {
|
||||
this._editingCommitMessage = false;
|
||||
const relatedChangesLoaded = coreDataPromise.then(() => {
|
||||
this.getRelatedChangesList()?.reload();
|
||||
if (this._isNewChangeSummaryUiEnabled) {
|
||||
let relatedChangesPromise:
|
||||
| Promise<RelatedChangesInfo | undefined>
|
||||
| undefined;
|
||||
const patchNum = this._computeLatestPatchNum(this._allPatchSets);
|
||||
if (this._change && patchNum) {
|
||||
relatedChangesPromise = this.restApiService
|
||||
.getRelatedChanges(this._change._number, patchNum)
|
||||
.then(response => {
|
||||
if (this._change && response) {
|
||||
this.hasParent = this._calculateHasParent(
|
||||
this._change.change_id,
|
||||
response.changes
|
||||
);
|
||||
}
|
||||
return response;
|
||||
});
|
||||
}
|
||||
// TODO: use returned Promise
|
||||
this.getRelatedChangesListExperimental()?.reload(
|
||||
relatedChangesPromise
|
||||
);
|
||||
let relatedChangesPromise:
|
||||
| Promise<RelatedChangesInfo | undefined>
|
||||
| undefined;
|
||||
const patchNum = this._computeLatestPatchNum(this._allPatchSets);
|
||||
if (this._change && patchNum) {
|
||||
relatedChangesPromise = this.restApiService
|
||||
.getRelatedChanges(this._change._number, patchNum)
|
||||
.then(response => {
|
||||
if (this._change && response) {
|
||||
this.hasParent = this._calculateHasParent(
|
||||
this._change.change_id,
|
||||
response.changes
|
||||
);
|
||||
}
|
||||
return response;
|
||||
});
|
||||
}
|
||||
// TODO: use returned Promise
|
||||
this.getRelatedChangesList()?.reload(relatedChangesPromise);
|
||||
});
|
||||
allDataPromises.push(relatedChangesLoaded);
|
||||
}
|
||||
@@ -2383,15 +2354,6 @@ export class GrChangeView extends KeyboardShortcutMixin(PolymerElement) {
|
||||
return collapsible && collapsed;
|
||||
}
|
||||
|
||||
_computeRelatedChangesClass(collapsed: boolean) {
|
||||
return collapsed ? 'collapsed' : '';
|
||||
}
|
||||
|
||||
_computeCollapseText(collapsed: boolean) {
|
||||
// Symbols are up and down triangles.
|
||||
return collapsed ? '\u25bc Show more' : '\u25b2 Show less';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the text to be copied when
|
||||
* click the copy icon next to change subject
|
||||
@@ -2411,13 +2373,6 @@ export class GrChangeView extends KeyboardShortcutMixin(PolymerElement) {
|
||||
}
|
||||
}
|
||||
|
||||
_toggleRelatedChangesCollapsed() {
|
||||
this._relatedChangesCollapsed = !this._relatedChangesCollapsed;
|
||||
if (this._relatedChangesCollapsed) {
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
_computeCommitCollapsible(commitMessage?: string) {
|
||||
if (!commitMessage) {
|
||||
return false;
|
||||
@@ -2428,124 +2383,6 @@ export class GrChangeView extends KeyboardShortcutMixin(PolymerElement) {
|
||||
return commitMessage.split('\n').length >= MIN_LINES;
|
||||
}
|
||||
|
||||
_getOffsetHeight(element: HTMLElement) {
|
||||
return element.offsetHeight;
|
||||
}
|
||||
|
||||
_getScrollHeight(element: HTMLElement) {
|
||||
return element.scrollHeight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the line height of an element to the nearest integer.
|
||||
*/
|
||||
_getLineHeight(element: Element) {
|
||||
const lineHeightStr = getComputedStyle(element).lineHeight;
|
||||
return Math.round(Number(lineHeightStr.slice(0, lineHeightStr.length - 2)));
|
||||
}
|
||||
|
||||
/**
|
||||
* New max height for the related changes section, shorter than the existing
|
||||
* change info height.
|
||||
*/
|
||||
_updateRelatedChangeMaxHeight() {
|
||||
// Takes into account approximate height for the expand button and
|
||||
// bottom margin.
|
||||
const EXTRA_HEIGHT = 30;
|
||||
let newHeight;
|
||||
|
||||
if (window.matchMedia(`(max-width: ${BREAKPOINT_RELATED_SMALL})`).matches) {
|
||||
// In a small (mobile) view, give the relation chain some space.
|
||||
newHeight = SMALL_RELATED_HEIGHT;
|
||||
} else if (
|
||||
window.matchMedia(`(max-width: ${BREAKPOINT_RELATED_MED})`).matches
|
||||
) {
|
||||
// Since related changes are below the commit message, but still next to
|
||||
// metadata, the height should be the height of the metadata minus the
|
||||
// height of the commit message to reduce jank. However, if that doesn't
|
||||
// result in enough space, instead use the MINIMUM_RELATED_MAX_HEIGHT.
|
||||
// Note: extraHeight is to take into account margin/padding.
|
||||
const medRelatedHeight = Math.max(
|
||||
this._getOffsetHeight(this.$.mainChangeInfo) -
|
||||
this._getOffsetHeight(this.$.commitMessage) -
|
||||
2 * EXTRA_HEIGHT,
|
||||
MINIMUM_RELATED_MAX_HEIGHT
|
||||
);
|
||||
newHeight = medRelatedHeight;
|
||||
} else {
|
||||
if (this._commitCollapsible) {
|
||||
// Make sure the content is lined up if both areas have buttons. If
|
||||
// the commit message is not collapsed, instead use the change info
|
||||
// height.
|
||||
newHeight = this._getOffsetHeight(this.$.commitMessage);
|
||||
} else {
|
||||
newHeight =
|
||||
this._getOffsetHeight(this.$.commitAndRelated) - EXTRA_HEIGHT;
|
||||
}
|
||||
}
|
||||
const stylesToUpdate: {[key: string]: string} = {};
|
||||
|
||||
const relatedChanges = this.getRelatedChangesList();
|
||||
// Get the line height of related changes, and convert it to the nearest
|
||||
// integer.
|
||||
const DEFAULT_LINE_HEIGHT = 20;
|
||||
const lineHeight = relatedChanges
|
||||
? this._getLineHeight(relatedChanges)
|
||||
: DEFAULT_LINE_HEIGHT;
|
||||
|
||||
// Figure out a new height that is divisible by the rounded line height.
|
||||
const remainder = newHeight % lineHeight;
|
||||
newHeight = newHeight - remainder;
|
||||
|
||||
stylesToUpdate['--relation-chain-max-height'] = `${newHeight}px`;
|
||||
|
||||
// Update the max-height of the relation chain to this new height.
|
||||
if (this._commitCollapsible) {
|
||||
stylesToUpdate['--related-change-btn-top-padding'] = `${remainder}px`;
|
||||
}
|
||||
|
||||
this.updateStyles(stylesToUpdate);
|
||||
}
|
||||
|
||||
_computeShowRelatedToggle() {
|
||||
// Make sure the max height has been applied, since there is now content
|
||||
// to populate.
|
||||
if (!getComputedStyleValue('--relation-chain-max-height', this)) {
|
||||
this._updateRelatedChangeMaxHeight();
|
||||
}
|
||||
// Prevents showMore from showing when click on related change, since the
|
||||
// line height would be positive, but related changes height is 0.
|
||||
const relatedChanges = this.getRelatedChangesList();
|
||||
if (relatedChanges) {
|
||||
if (!this._getScrollHeight(relatedChanges)) {
|
||||
return (this._showRelatedToggle = false);
|
||||
}
|
||||
|
||||
if (
|
||||
this._getScrollHeight(relatedChanges) >
|
||||
this._getOffsetHeight(relatedChanges) +
|
||||
this._getLineHeight(relatedChanges)
|
||||
) {
|
||||
return (this._showRelatedToggle = true);
|
||||
}
|
||||
}
|
||||
return (this._showRelatedToggle = false);
|
||||
}
|
||||
|
||||
_updateToggleContainerClass(showRelatedToggle: boolean) {
|
||||
const relatedChangesToggle = this.shadowRoot!.querySelector<HTMLDivElement>(
|
||||
'#relatedChangesToggle'
|
||||
);
|
||||
if (!relatedChangesToggle) {
|
||||
return;
|
||||
}
|
||||
if (showRelatedToggle) {
|
||||
relatedChangesToggle.classList.add('showToggle');
|
||||
} else {
|
||||
relatedChangesToggle.classList.remove('showToggle');
|
||||
}
|
||||
}
|
||||
|
||||
_startUpdateCheckTimer() {
|
||||
if (
|
||||
!this._serverConfig ||
|
||||
@@ -2822,12 +2659,6 @@ export class GrChangeView extends KeyboardShortcutMixin(PolymerElement) {
|
||||
'#relatedChanges'
|
||||
);
|
||||
}
|
||||
|
||||
getRelatedChangesListExperimental() {
|
||||
return this.shadowRoot!.querySelector<GrRelatedChangesListExperimental>(
|
||||
'#relatedChangesExperimental'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
||||
@@ -165,12 +165,6 @@ export const htmlTemplate = html`
|
||||
height: 0;
|
||||
margin-bottom: var(--spacing-l);
|
||||
}
|
||||
#relatedChanges.collapsed {
|
||||
margin-bottom: var(--spacing-l);
|
||||
max-height: var(--relation-chain-max-height, 2em);
|
||||
overflow: hidden;
|
||||
position: relative; /* for arrowToCurrentChange to have position:absolute and be hidden */
|
||||
}
|
||||
.emptySpace {
|
||||
flex-grow: 1;
|
||||
}
|
||||
@@ -185,19 +179,9 @@ export const htmlTemplate = html`
|
||||
display: flex;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
#relatedChangesToggle {
|
||||
display: none;
|
||||
}
|
||||
#relatedChangesToggle.showToggle {
|
||||
display: flex;
|
||||
}
|
||||
.collapseToggleContainer gr-button {
|
||||
display: block;
|
||||
}
|
||||
#relatedChangesToggle {
|
||||
margin-left: var(--spacing-l);
|
||||
padding-top: var(--related-change-btn-top-padding, 0);
|
||||
}
|
||||
.showOnEdit {
|
||||
display: none;
|
||||
}
|
||||
@@ -243,8 +227,6 @@ export const htmlTemplate = html`
|
||||
/* temporary for old checks status */
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
/* NOTE: If you update this breakpoint, also update the
|
||||
BREAKPOINT_RELATED_MED in the JS */
|
||||
@media screen and (max-width: 75em) {
|
||||
.relatedChanges {
|
||||
padding: 0;
|
||||
@@ -266,8 +248,6 @@ export const htmlTemplate = html`
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
/* NOTE: If you update this breakpoint, also update the
|
||||
BREAKPOINT_RELATED_SMALL in the JS */
|
||||
@media screen and (max-width: 50em) {
|
||||
.mobile {
|
||||
display: block;
|
||||
@@ -533,36 +513,11 @@ export const htmlTemplate = html`
|
||||
</gr-endpoint-decorator>
|
||||
</div>
|
||||
<div class="relatedChanges">
|
||||
<template is="dom-if" if="[[_isNewChangeSummaryUiEnabled]]">
|
||||
<gr-related-changes-list-experimental
|
||||
change="[[_change]]"
|
||||
id="relatedChangesExperimental"
|
||||
patch-num="[[_computeLatestPatchNum(_allPatchSets)]]"
|
||||
></gr-related-changes-list-experimental>
|
||||
</template>
|
||||
<template is="dom-if" if="[[!_isNewChangeSummaryUiEnabled]]">
|
||||
<gr-related-changes-list
|
||||
id="relatedChanges"
|
||||
class$="[[_computeRelatedChangesClass(_relatedChangesCollapsed)]]"
|
||||
change="[[_change]]"
|
||||
mergeable="[[_mergeable]]"
|
||||
has-parent="{{hasParent}}"
|
||||
on-update="_updateRelatedChangeMaxHeight"
|
||||
patch-num="[[_computeLatestPatchNum(_allPatchSets)]]"
|
||||
on-new-section-loaded="_computeShowRelatedToggle"
|
||||
>
|
||||
</gr-related-changes-list>
|
||||
<div id="relatedChangesToggle" class="collapseToggleContainer">
|
||||
<gr-button
|
||||
link=""
|
||||
id="relatedChangesToggleButton"
|
||||
class="collapseToggleButton"
|
||||
on-click="_toggleRelatedChangesCollapsed"
|
||||
>
|
||||
[[_computeCollapseText(_relatedChangesCollapsed)]]
|
||||
</gr-button>
|
||||
</div>
|
||||
</template>
|
||||
<gr-related-changes-list
|
||||
change="[[_change]]"
|
||||
id="relatedChanges"
|
||||
patch-num="[[_computeLatestPatchNum(_allPatchSets)]]"
|
||||
></gr-related-changes-list>
|
||||
</div>
|
||||
<div class="emptySpace"></div>
|
||||
</div>
|
||||
|
||||
@@ -29,7 +29,6 @@ import {
|
||||
} from '../../../constants/constants';
|
||||
import {GrEditConstants} from '../../edit/gr-edit-constants';
|
||||
import {_testOnly_resetEndpoints} from '../../shared/gr-js-api-interface/gr-plugin-endpoints';
|
||||
import {getComputedStyleValue} from '../../../utils/dom-util';
|
||||
import {GerritNav} from '../../core/gr-navigation/gr-navigation';
|
||||
import {getPluginLoader} from '../../shared/gr-js-api-interface/gr-plugin-loader';
|
||||
import {_testOnly_initGerritPluginApi} from '../../shared/gr-js-api-interface/gr-gerrit';
|
||||
@@ -38,7 +37,6 @@ import {EventType, PluginApi} from '../../../api/plugin';
|
||||
import 'lodash/lodash';
|
||||
import {
|
||||
stubRestApi,
|
||||
SinonSpyMember,
|
||||
TestKeyboardShortcutBinder,
|
||||
} from '../../../test/test-utils';
|
||||
import {Shortcut} from '../../../mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin';
|
||||
@@ -46,7 +44,6 @@ import {
|
||||
createAppElementChangeViewParams,
|
||||
createApproval,
|
||||
createChange,
|
||||
createChangeConfig,
|
||||
createChangeMessages,
|
||||
createCommit,
|
||||
createMergeable,
|
||||
@@ -406,9 +403,6 @@ suite('gr-change-view tests', () => {
|
||||
});
|
||||
});
|
||||
|
||||
const getCustomCssValue = (cssParam: string) =>
|
||||
getComputedStyleValue(cssParam, element);
|
||||
|
||||
test('_handleMessageAnchorTap', () => {
|
||||
element._changeNum = 1 as NumericChangeId;
|
||||
element._patchRange = {
|
||||
@@ -1516,10 +1510,6 @@ suite('gr-change-view tests', () => {
|
||||
.stub(element, '_reloadPatchNumDependentResources')
|
||||
.callsFake(() => Promise.resolve([undefined, undefined, undefined]));
|
||||
flush();
|
||||
const relatedChanges = element.shadowRoot!.querySelector(
|
||||
'#relatedChanges'
|
||||
) as GrRelatedChangesList;
|
||||
const relatedClearSpy = sinon.spy(relatedChanges, 'clear');
|
||||
const collapseStub = sinon.stub(element.$.fileList, 'collapseAllDiffs');
|
||||
|
||||
const value: AppElementChangeViewParams = {
|
||||
@@ -1529,7 +1519,6 @@ suite('gr-change-view tests', () => {
|
||||
};
|
||||
element._paramsChanged(value);
|
||||
assert.isTrue(reloadStub.calledOnce);
|
||||
assert.isTrue(relatedClearSpy.calledOnce);
|
||||
|
||||
element._initialLoadComplete = true;
|
||||
|
||||
@@ -1538,7 +1527,6 @@ suite('gr-change-view tests', () => {
|
||||
element._paramsChanged(value);
|
||||
assert.isFalse(reloadStub.calledTwice);
|
||||
assert.isTrue(reloadPatchDependentStub.calledOnce);
|
||||
assert.isTrue(relatedClearSpy.calledOnce);
|
||||
assert.isTrue(collapseStub.calledTwice);
|
||||
});
|
||||
|
||||
@@ -1551,10 +1539,6 @@ suite('gr-change-view tests', () => {
|
||||
element.$.commentAPI,
|
||||
'reloadPortedComments'
|
||||
);
|
||||
const relatedChanges = element.shadowRoot!.querySelector(
|
||||
'#relatedChanges'
|
||||
) as GrRelatedChangesList;
|
||||
sinon.spy(relatedChanges, 'clear');
|
||||
sinon.stub(element.$.fileList, 'collapseAllDiffs');
|
||||
|
||||
const value: AppElementChangeViewParams = {
|
||||
@@ -2180,322 +2164,6 @@ suite('gr-change-view tests', () => {
|
||||
});
|
||||
});
|
||||
|
||||
suite('related changes expand/collapse', () => {
|
||||
let updateHeightSpy: SinonSpyMember<
|
||||
typeof element._updateRelatedChangeMaxHeight
|
||||
>;
|
||||
setup(() => {
|
||||
updateHeightSpy = sinon.spy(element, '_updateRelatedChangeMaxHeight');
|
||||
});
|
||||
|
||||
test('relatedChangesToggle shown height greater than changeInfo height', () => {
|
||||
const relatedChangesToggle = element.shadowRoot!.querySelector(
|
||||
'#relatedChangesToggle'
|
||||
);
|
||||
assert.isFalse(relatedChangesToggle!.classList.contains('showToggle'));
|
||||
sinon.stub(element, '_getOffsetHeight').callsFake(() => 50);
|
||||
sinon.stub(element, '_getScrollHeight').callsFake(() => 60);
|
||||
sinon.stub(element, '_getLineHeight').callsFake(() => 5);
|
||||
sinon
|
||||
.stub(window, 'matchMedia')
|
||||
.callsFake(() => ({matches: true} as MediaQueryList));
|
||||
const relatedChanges = element.shadowRoot!.querySelector(
|
||||
'#relatedChanges'
|
||||
) as GrRelatedChangesList;
|
||||
relatedChanges.dispatchEvent(new CustomEvent('new-section-loaded'));
|
||||
assert.isTrue(relatedChangesToggle!.classList.contains('showToggle'));
|
||||
assert.equal(updateHeightSpy.callCount, 1);
|
||||
});
|
||||
|
||||
test('relatedChangesToggle hidden height less than changeInfo height', () => {
|
||||
const relatedChangesToggle = element.shadowRoot!.querySelector(
|
||||
'#relatedChangesToggle'
|
||||
);
|
||||
assert.isFalse(relatedChangesToggle!.classList.contains('showToggle'));
|
||||
sinon.stub(element, '_getOffsetHeight').callsFake(() => 50);
|
||||
sinon.stub(element, '_getScrollHeight').callsFake(() => 40);
|
||||
sinon.stub(element, '_getLineHeight').callsFake(() => 5);
|
||||
sinon
|
||||
.stub(window, 'matchMedia')
|
||||
.callsFake(() => ({matches: true} as MediaQueryList));
|
||||
const relatedChanges = element.shadowRoot!.querySelector(
|
||||
'#relatedChanges'
|
||||
) as GrRelatedChangesList;
|
||||
relatedChanges.dispatchEvent(new CustomEvent('new-section-loaded'));
|
||||
assert.isFalse(relatedChangesToggle!.classList.contains('showToggle'));
|
||||
assert.equal(updateHeightSpy.callCount, 1);
|
||||
});
|
||||
|
||||
test('relatedChangesToggle functions', () => {
|
||||
sinon.stub(element, '_getOffsetHeight').callsFake(() => 50);
|
||||
sinon
|
||||
.stub(window, 'matchMedia')
|
||||
.callsFake(() => ({matches: false} as MediaQueryList));
|
||||
assert.isTrue(element._relatedChangesCollapsed);
|
||||
const relatedChangesToggleButton = element.shadowRoot!.querySelector(
|
||||
'#relatedChangesToggleButton'
|
||||
);
|
||||
const relatedChanges = element.shadowRoot!.querySelector(
|
||||
'#relatedChanges'
|
||||
) as GrRelatedChangesList;
|
||||
assert.isTrue(relatedChanges.classList.contains('collapsed'));
|
||||
tap(relatedChangesToggleButton!);
|
||||
assert.isFalse(element._relatedChangesCollapsed);
|
||||
assert.isFalse(relatedChanges.classList.contains('collapsed'));
|
||||
});
|
||||
|
||||
test('_updateRelatedChangeMaxHeight without commit toggle', () => {
|
||||
sinon.stub(element, '_getOffsetHeight').callsFake(() => 50);
|
||||
sinon.stub(element, '_getLineHeight').callsFake(() => 12);
|
||||
sinon
|
||||
.stub(window, 'matchMedia')
|
||||
.callsFake(() => ({matches: false} as MediaQueryList));
|
||||
|
||||
// 50 (existing height) - 30 (extra height) = 20 (adjusted height).
|
||||
// 20 (max existing height) % 12 (line height) = 6 (remainder).
|
||||
// 20 (adjusted height) - 8 (remainder) = 12 (max height to set).
|
||||
|
||||
element._updateRelatedChangeMaxHeight();
|
||||
assert.equal(getCustomCssValue('--relation-chain-max-height'), '12px');
|
||||
assert.equal(getCustomCssValue('--related-change-btn-top-padding'), '');
|
||||
});
|
||||
|
||||
test('_updateRelatedChangeMaxHeight with commit toggle', () => {
|
||||
element._latestCommitMessage = _.times(31, String).join('\n');
|
||||
sinon.stub(element, '_getOffsetHeight').callsFake(() => 50);
|
||||
sinon.stub(element, '_getLineHeight').callsFake(() => 12);
|
||||
sinon
|
||||
.stub(window, 'matchMedia')
|
||||
.callsFake(() => ({matches: false} as MediaQueryList));
|
||||
|
||||
// 50 (existing height) % 12 (line height) = 2 (remainder).
|
||||
// 50 (existing height) - 2 (remainder) = 48 (max height to set).
|
||||
|
||||
element._updateRelatedChangeMaxHeight();
|
||||
assert.equal(getCustomCssValue('--relation-chain-max-height'), '48px');
|
||||
assert.equal(
|
||||
getCustomCssValue('--related-change-btn-top-padding'),
|
||||
'2px'
|
||||
);
|
||||
});
|
||||
|
||||
test('_updateRelatedChangeMaxHeight in small screen mode', () => {
|
||||
element._latestCommitMessage = _.times(31, String).join('\n');
|
||||
sinon.stub(element, '_getOffsetHeight').callsFake(() => 50);
|
||||
sinon.stub(element, '_getLineHeight').callsFake(() => 12);
|
||||
sinon
|
||||
.stub(window, 'matchMedia')
|
||||
.callsFake(() => ({matches: true} as MediaQueryList));
|
||||
|
||||
element._updateRelatedChangeMaxHeight();
|
||||
|
||||
// 400 (new height) % 12 (line height) = 4 (remainder).
|
||||
// 400 (new height) - 4 (remainder) = 396.
|
||||
|
||||
assert.equal(getCustomCssValue('--relation-chain-max-height'), '396px');
|
||||
});
|
||||
|
||||
test('_updateRelatedChangeMaxHeight in medium screen mode', () => {
|
||||
element._latestCommitMessage = _.times(31, String).join('\n');
|
||||
sinon.stub(element, '_getOffsetHeight').callsFake(() => 50);
|
||||
sinon.stub(element, '_getLineHeight').callsFake(() => 12);
|
||||
const matchMediaStub = sinon.stub(window, 'matchMedia').callsFake(() => {
|
||||
if (matchMediaStub.lastCall.args[0] === '(max-width: 75em)') {
|
||||
return {matches: true} as MediaQueryList;
|
||||
} else {
|
||||
return {matches: false} as MediaQueryList;
|
||||
}
|
||||
});
|
||||
|
||||
// 100 (new height) % 12 (line height) = 4 (remainder).
|
||||
// 100 (new height) - 4 (remainder) = 96.
|
||||
element._updateRelatedChangeMaxHeight();
|
||||
assert.equal(getCustomCssValue('--relation-chain-max-height'), '96px');
|
||||
});
|
||||
|
||||
suite('update checks', () => {
|
||||
let clock: SinonFakeTimers;
|
||||
let startUpdateCheckTimerSpy: SinonSpyMember<
|
||||
typeof element._startUpdateCheckTimer
|
||||
>;
|
||||
setup(() => {
|
||||
clock = sinon.useFakeTimers();
|
||||
startUpdateCheckTimerSpy = sinon.spy(element, '_startUpdateCheckTimer');
|
||||
element._change = {
|
||||
...createChangeViewChange(),
|
||||
revisions: createRevisions(1),
|
||||
messages: createChangeMessages(1),
|
||||
};
|
||||
});
|
||||
|
||||
test('_startUpdateCheckTimer negative delay', () => {
|
||||
const getChangeDetailStub = stubRestApi('getChangeDetail').returns(
|
||||
Promise.resolve({
|
||||
...createChangeViewChange(),
|
||||
// element has latest info
|
||||
revisions: {rev1: createRevision()},
|
||||
messages: createChangeMessages(1),
|
||||
current_revision: 'rev1' as CommitId,
|
||||
})
|
||||
);
|
||||
|
||||
element._serverConfig = {
|
||||
...createServerInfo(),
|
||||
change: {...createChangeConfig(), update_delay: -1},
|
||||
};
|
||||
|
||||
assert.isTrue(startUpdateCheckTimerSpy.called);
|
||||
assert.isFalse(getChangeDetailStub.called);
|
||||
});
|
||||
|
||||
test('_startUpdateCheckTimer up-to-date', async () => {
|
||||
const getChangeDetailStub = stubRestApi('getChangeDetail').callsFake(
|
||||
() =>
|
||||
Promise.resolve({
|
||||
...createChangeViewChange(),
|
||||
// element has latest info
|
||||
revisions: {rev1: createRevision()},
|
||||
messages: createChangeMessages(1),
|
||||
current_revision: 'rev1' as CommitId,
|
||||
})
|
||||
);
|
||||
|
||||
element._serverConfig = {
|
||||
...createServerInfo(),
|
||||
change: {...createChangeConfig(), update_delay: 12345},
|
||||
};
|
||||
clock.tick(12345 * 1000);
|
||||
await flush();
|
||||
|
||||
assert.equal(startUpdateCheckTimerSpy.callCount, 2);
|
||||
assert.isTrue(getChangeDetailStub.called);
|
||||
});
|
||||
|
||||
test('_startUpdateCheckTimer out-of-date shows an alert', async () => {
|
||||
stubRestApi('getChangeDetail').callsFake(() =>
|
||||
Promise.resolve({
|
||||
...createChange(),
|
||||
// new patchset was uploaded
|
||||
revisions: createRevisions(2),
|
||||
current_revision: getCurrentRevision(2),
|
||||
messages: createChangeMessages(1),
|
||||
})
|
||||
);
|
||||
|
||||
let alertMessage = 'alert not fired';
|
||||
element.addEventListener('show-alert', e => {
|
||||
alertMessage = e.detail.message;
|
||||
});
|
||||
element._serverConfig = {
|
||||
...createServerInfo(),
|
||||
change: {...createChangeConfig(), update_delay: 12345},
|
||||
};
|
||||
clock.tick(12345 * 1000);
|
||||
await flush();
|
||||
|
||||
assert.equal(alertMessage, 'A newer patch set has been uploaded');
|
||||
assert.equal(startUpdateCheckTimerSpy.callCount, 1);
|
||||
});
|
||||
|
||||
test('_startUpdateCheckTimer respects _loading', async () => {
|
||||
stubRestApi('getChangeDetail').callsFake(() =>
|
||||
Promise.resolve({
|
||||
...createChangeViewChange(),
|
||||
// new patchset was uploaded
|
||||
revisions: createRevisions(2),
|
||||
current_revision: getCurrentRevision(2),
|
||||
messages: createChangeMessages(1),
|
||||
})
|
||||
);
|
||||
|
||||
element._loading = true;
|
||||
element._serverConfig = {
|
||||
...createServerInfo(),
|
||||
change: {...createChangeConfig(), update_delay: 12345},
|
||||
};
|
||||
clock.tick(12345 * 1000 * 2);
|
||||
await flush();
|
||||
|
||||
// No toast, instead a second call to _startUpdateCheckTimer().
|
||||
assert.equal(startUpdateCheckTimerSpy.callCount, 2);
|
||||
});
|
||||
|
||||
test('_startUpdateCheckTimer new status shows an alert', async () => {
|
||||
stubRestApi('getChangeDetail').callsFake(() =>
|
||||
Promise.resolve({
|
||||
...createChangeViewChange(),
|
||||
// element has latest info
|
||||
revisions: {rev1: createRevision()},
|
||||
messages: createChangeMessages(1),
|
||||
current_revision: 'rev1' as CommitId,
|
||||
status: ChangeStatus.MERGED,
|
||||
})
|
||||
);
|
||||
|
||||
let alertMessage = 'alert not fired';
|
||||
element.addEventListener('show-alert', e => {
|
||||
alertMessage = e.detail.message;
|
||||
});
|
||||
element._serverConfig = {
|
||||
...createServerInfo(),
|
||||
change: {...createChangeConfig(), update_delay: 12345},
|
||||
};
|
||||
clock.tick(12345 * 1000);
|
||||
await flush();
|
||||
|
||||
assert.equal(alertMessage, 'This change has been merged');
|
||||
});
|
||||
|
||||
test('_startUpdateCheckTimer new messages shows an alert', async () => {
|
||||
stubRestApi('getChangeDetail').callsFake(() =>
|
||||
Promise.resolve({
|
||||
...createChangeViewChange(),
|
||||
revisions: {rev1: createRevision()},
|
||||
// element has new message
|
||||
messages: createChangeMessages(2),
|
||||
current_revision: 'rev1' as CommitId,
|
||||
})
|
||||
);
|
||||
|
||||
let alertMessage = 'alert not fired';
|
||||
element.addEventListener('show-alert', e => {
|
||||
alertMessage = e.detail.message;
|
||||
});
|
||||
element._serverConfig = {
|
||||
...createServerInfo(),
|
||||
change: {...createChangeConfig(), update_delay: 12345},
|
||||
};
|
||||
clock.tick(12345 * 1000);
|
||||
await flush();
|
||||
|
||||
assert.equal(alertMessage, 'There are new messages on this change');
|
||||
});
|
||||
});
|
||||
|
||||
test('canStartReview computation', () => {
|
||||
const change1: ChangeInfo = createChange();
|
||||
const change2: ChangeInfo = {
|
||||
...createChangeViewChange(),
|
||||
actions: {
|
||||
ready: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
const change3: ChangeInfo = {
|
||||
...createChangeViewChange(),
|
||||
actions: {
|
||||
ready: {
|
||||
label: 'Ready for Review',
|
||||
},
|
||||
},
|
||||
};
|
||||
assert.isFalse(element._computeCanStartReview(change1));
|
||||
assert.isTrue(element._computeCanStartReview(change2));
|
||||
assert.isFalse(element._computeCanStartReview(change3));
|
||||
});
|
||||
});
|
||||
|
||||
test('header class computation', () => {
|
||||
assert.equal(element._computeHeaderClass(), 'header');
|
||||
assert.equal(element._computeHeaderClass(true), 'header editMode');
|
||||
|
||||
@@ -1,754 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright (C) 2021 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 {html, nothing} from 'lit-html';
|
||||
import './gr-related-change';
|
||||
import '../../plugins/gr-endpoint-decorator/gr-endpoint-decorator';
|
||||
import '../../plugins/gr-endpoint-param/gr-endpoint-param';
|
||||
import '../../plugins/gr-endpoint-slot/gr-endpoint-slot';
|
||||
import {classMap} from 'lit-html/directives/class-map';
|
||||
import {GrLitElement} from '../../lit/gr-lit-element';
|
||||
import {
|
||||
customElement,
|
||||
property,
|
||||
css,
|
||||
internalProperty,
|
||||
TemplateResult,
|
||||
} from 'lit-element';
|
||||
import {sharedStyles} from '../../../styles/shared-styles';
|
||||
import {
|
||||
SubmittedTogetherInfo,
|
||||
ChangeInfo,
|
||||
RelatedChangeAndCommitInfo,
|
||||
RelatedChangesInfo,
|
||||
PatchSetNum,
|
||||
CommitId,
|
||||
} from '../../../types/common';
|
||||
import {appContext} from '../../../services/app-context';
|
||||
import {ParsedChangeInfo} from '../../../types/types';
|
||||
import {GerritNav} from '../../core/gr-navigation/gr-navigation';
|
||||
import {pluralize} from '../../../utils/string-util';
|
||||
import {
|
||||
changeIsOpen,
|
||||
getRevisionKey,
|
||||
isChangeInfo,
|
||||
} from '../../../utils/change-util';
|
||||
|
||||
/** What is the maximum number of shown changes in collapsed list? */
|
||||
const DEFALT_NUM_CHANGES_WHEN_COLLAPSED = 3;
|
||||
|
||||
export interface ChangeMarkersInList {
|
||||
showCurrentChangeArrow: boolean;
|
||||
showWhenCollapsed: boolean;
|
||||
showTopArrow: boolean;
|
||||
showBottomArrow: boolean;
|
||||
}
|
||||
|
||||
export enum Section {
|
||||
RELATED_CHANGES = 'related changes',
|
||||
SUBMITTED_TOGETHER = 'submitted together',
|
||||
SAME_TOPIC = 'same topic',
|
||||
MERGE_CONFLICTS = 'merge conflicts',
|
||||
CHERRY_PICKS = 'cherry picks',
|
||||
}
|
||||
|
||||
@customElement('gr-related-changes-list-experimental')
|
||||
export class GrRelatedChangesListExperimental extends GrLitElement {
|
||||
@property()
|
||||
change?: ParsedChangeInfo;
|
||||
|
||||
@property({type: String})
|
||||
patchNum?: PatchSetNum;
|
||||
|
||||
@property()
|
||||
mergeable?: boolean;
|
||||
|
||||
@internalProperty()
|
||||
submittedTogether?: SubmittedTogetherInfo = {
|
||||
changes: [],
|
||||
non_visible_changes: 0,
|
||||
};
|
||||
|
||||
@internalProperty()
|
||||
relatedChanges: RelatedChangeAndCommitInfo[] = [];
|
||||
|
||||
@internalProperty()
|
||||
conflictingChanges: ChangeInfo[] = [];
|
||||
|
||||
@internalProperty()
|
||||
cherryPickChanges: ChangeInfo[] = [];
|
||||
|
||||
@internalProperty()
|
||||
sameTopicChanges: ChangeInfo[] = [];
|
||||
|
||||
private readonly restApiService = appContext.restApiService;
|
||||
|
||||
static get styles() {
|
||||
return [
|
||||
sharedStyles,
|
||||
css`
|
||||
.note {
|
||||
color: var(--error-text-color);
|
||||
margin-left: 1.2em;
|
||||
}
|
||||
section {
|
||||
margin-bottom: var(--spacing-l);
|
||||
}
|
||||
gr-related-change {
|
||||
display: flex;
|
||||
}
|
||||
.marker {
|
||||
position: absolute;
|
||||
margin-left: calc(-1 * var(--spacing-s));
|
||||
}
|
||||
.arrowToCurrentChange {
|
||||
position: absolute;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
render() {
|
||||
const sectionSize = this.sectionSizeFactory(
|
||||
this.relatedChanges.length,
|
||||
this.submittedTogether?.changes.length || 0,
|
||||
this.sameTopicChanges.length,
|
||||
this.conflictingChanges.length,
|
||||
this.cherryPickChanges.length
|
||||
);
|
||||
const relatedChangesMarkersPredicate = this.markersPredicateFactory(
|
||||
this.relatedChanges.length,
|
||||
this.relatedChanges.findIndex(relatedChange =>
|
||||
this._changesEqual(relatedChange, this.change)
|
||||
),
|
||||
sectionSize(Section.RELATED_CHANGES)
|
||||
);
|
||||
const connectedRevisions = this._computeConnectedRevisions(
|
||||
this.change,
|
||||
this.patchNum,
|
||||
this.relatedChanges
|
||||
);
|
||||
let firstNonEmptySectionFound = false;
|
||||
let isFirstNonEmpty =
|
||||
!firstNonEmptySectionFound && !!this.relatedChanges.length;
|
||||
firstNonEmptySectionFound = firstNonEmptySectionFound || isFirstNonEmpty;
|
||||
const relatedChangeSection = html` <section
|
||||
id="relatedChanges"
|
||||
?hidden=${!this.relatedChanges.length}
|
||||
>
|
||||
<gr-related-collapse
|
||||
title="Relation chain"
|
||||
class="${classMap({first: isFirstNonEmpty})}"
|
||||
.length=${this.relatedChanges.length}
|
||||
.numChangesWhenCollapsed=${sectionSize(Section.RELATED_CHANGES)}
|
||||
>
|
||||
${this.relatedChanges.map(
|
||||
(change, index) =>
|
||||
html`${this.renderMarkers(
|
||||
relatedChangesMarkersPredicate(index)
|
||||
)}<gr-related-change
|
||||
class="${classMap({
|
||||
['show-when-collapsed']: relatedChangesMarkersPredicate(index)
|
||||
.showWhenCollapsed,
|
||||
})}"
|
||||
.change="${change}"
|
||||
.connectedRevisions="${connectedRevisions}"
|
||||
.href="${change?._change_number
|
||||
? GerritNav.getUrlForChangeById(
|
||||
change._change_number,
|
||||
change.project,
|
||||
change._revision_number as PatchSetNum
|
||||
)
|
||||
: ''}"
|
||||
.showChangeStatus=${true}
|
||||
>${change.commit.subject}</gr-related-change
|
||||
>`
|
||||
)}
|
||||
</gr-related-collapse>
|
||||
</section>`;
|
||||
|
||||
const submittedTogetherChanges = this.submittedTogether?.changes ?? [];
|
||||
const countNonVisibleChanges =
|
||||
this.submittedTogether?.non_visible_changes ?? 0;
|
||||
const submittedTogetherMarkersPredicate = this.markersPredicateFactory(
|
||||
submittedTogetherChanges.length,
|
||||
submittedTogetherChanges.findIndex(relatedChange =>
|
||||
this._changesEqual(relatedChange, this.change)
|
||||
),
|
||||
sectionSize(Section.SUBMITTED_TOGETHER)
|
||||
);
|
||||
isFirstNonEmpty =
|
||||
!firstNonEmptySectionFound &&
|
||||
(!!submittedTogetherChanges?.length ||
|
||||
!!this.submittedTogether?.non_visible_changes);
|
||||
firstNonEmptySectionFound = firstNonEmptySectionFound || isFirstNonEmpty;
|
||||
const submittedTogetherSection = html`<section
|
||||
id="submittedTogether"
|
||||
?hidden=${!submittedTogetherChanges?.length &&
|
||||
!this.submittedTogether?.non_visible_changes}
|
||||
>
|
||||
<gr-related-collapse
|
||||
title="Submitted together"
|
||||
class="${classMap({first: isFirstNonEmpty})}"
|
||||
.length=${submittedTogetherChanges.length}
|
||||
.numChangesWhenCollapsed=${sectionSize(Section.SUBMITTED_TOGETHER)}
|
||||
>
|
||||
${submittedTogetherChanges.map(
|
||||
(change, index) =>
|
||||
html`${this.renderMarkers(
|
||||
submittedTogetherMarkersPredicate(index)
|
||||
)}<gr-related-change
|
||||
class="${classMap({
|
||||
['show-when-collapsed']: submittedTogetherMarkersPredicate(
|
||||
index
|
||||
).showWhenCollapsed,
|
||||
})}"
|
||||
.change="${change}"
|
||||
.href="${GerritNav.getUrlForChangeById(
|
||||
change._number,
|
||||
change.project
|
||||
)}"
|
||||
.showSubmittableCheck=${true}
|
||||
>${change.project}: ${change.branch}:
|
||||
${change.subject}</gr-related-change
|
||||
>`
|
||||
)}
|
||||
</gr-related-collapse>
|
||||
<div class="note" ?hidden=${!countNonVisibleChanges}>
|
||||
(+ ${pluralize(countNonVisibleChanges, 'non-visible change')})
|
||||
</div>
|
||||
</section>`;
|
||||
|
||||
const sameTopicMarkersPredicate = this.markersPredicateFactory(
|
||||
this.sameTopicChanges.length,
|
||||
-1,
|
||||
sectionSize(Section.SAME_TOPIC)
|
||||
);
|
||||
isFirstNonEmpty =
|
||||
!firstNonEmptySectionFound && !!this.sameTopicChanges?.length;
|
||||
firstNonEmptySectionFound = firstNonEmptySectionFound || isFirstNonEmpty;
|
||||
const sameTopicSection = html`<section
|
||||
id="sameTopic"
|
||||
?hidden=${!this.sameTopicChanges?.length}
|
||||
>
|
||||
<gr-related-collapse
|
||||
title="Same topic"
|
||||
class="${classMap({first: isFirstNonEmpty})}"
|
||||
.length=${this.sameTopicChanges.length}
|
||||
.numChangesWhenCollapsed=${sectionSize(Section.SAME_TOPIC)}
|
||||
>
|
||||
${this.sameTopicChanges.map(
|
||||
(change, index) =>
|
||||
html`${this.renderMarkers(
|
||||
sameTopicMarkersPredicate(index)
|
||||
)}<gr-related-change
|
||||
class="${classMap({
|
||||
['show-when-collapsed']: sameTopicMarkersPredicate(index)
|
||||
.showWhenCollapsed,
|
||||
})}"
|
||||
.change="${change}"
|
||||
.href="${GerritNav.getUrlForChangeById(
|
||||
change._number,
|
||||
change.project
|
||||
)}"
|
||||
>${change.project}: ${change.branch}:
|
||||
${change.subject}</gr-related-change
|
||||
>`
|
||||
)}
|
||||
</gr-related-collapse>
|
||||
</section>`;
|
||||
|
||||
const mergeConflictsMarkersPredicate = this.markersPredicateFactory(
|
||||
this.conflictingChanges.length,
|
||||
-1,
|
||||
sectionSize(Section.MERGE_CONFLICTS)
|
||||
);
|
||||
isFirstNonEmpty =
|
||||
!firstNonEmptySectionFound && !!this.conflictingChanges?.length;
|
||||
firstNonEmptySectionFound = firstNonEmptySectionFound || isFirstNonEmpty;
|
||||
const mergeConflictsSection = html`<section
|
||||
id="mergeConflicts"
|
||||
?hidden=${!this.conflictingChanges?.length}
|
||||
>
|
||||
<gr-related-collapse
|
||||
title="Merge conflicts"
|
||||
class="${classMap({first: isFirstNonEmpty})}"
|
||||
.length=${this.conflictingChanges.length}
|
||||
.numChangesWhenCollapsed=${sectionSize(Section.MERGE_CONFLICTS)}
|
||||
>
|
||||
${this.conflictingChanges.map(
|
||||
(change, index) =>
|
||||
html`${this.renderMarkers(
|
||||
mergeConflictsMarkersPredicate(index)
|
||||
)}<gr-related-change
|
||||
class="${classMap({
|
||||
['show-when-collapsed']: mergeConflictsMarkersPredicate(index)
|
||||
.showWhenCollapsed,
|
||||
})}"
|
||||
.change="${change}"
|
||||
.href="${GerritNav.getUrlForChangeById(
|
||||
change._number,
|
||||
change.project
|
||||
)}"
|
||||
>${change.subject}</gr-related-change
|
||||
>`
|
||||
)}
|
||||
</gr-related-collapse>
|
||||
</section>`;
|
||||
|
||||
const cherryPicksMarkersPredicate = this.markersPredicateFactory(
|
||||
this.cherryPickChanges.length,
|
||||
-1,
|
||||
sectionSize(Section.CHERRY_PICKS)
|
||||
);
|
||||
isFirstNonEmpty =
|
||||
!firstNonEmptySectionFound && !!this.cherryPickChanges?.length;
|
||||
firstNonEmptySectionFound = firstNonEmptySectionFound || isFirstNonEmpty;
|
||||
const cherryPicksSection = html`<section
|
||||
id="cherryPicks"
|
||||
?hidden=${!this.cherryPickChanges?.length}
|
||||
>
|
||||
<gr-related-collapse
|
||||
title="Cherry picks"
|
||||
class="${classMap({first: isFirstNonEmpty})}"
|
||||
.length=${this.cherryPickChanges.length}
|
||||
.numChangesWhenCollapsed=${sectionSize(Section.CHERRY_PICKS)}
|
||||
>
|
||||
${this.cherryPickChanges.map(
|
||||
(change, index) =>
|
||||
html`${this.renderMarkers(
|
||||
cherryPicksMarkersPredicate(index)
|
||||
)}<gr-related-change
|
||||
class="${classMap({
|
||||
['show-when-collapsed']: cherryPicksMarkersPredicate(index)
|
||||
.showWhenCollapsed,
|
||||
})}"
|
||||
.change="${change}"
|
||||
.href="${GerritNav.getUrlForChangeById(
|
||||
change._number,
|
||||
change.project
|
||||
)}"
|
||||
>${change.branch}: ${change.subject}</gr-related-change
|
||||
>`
|
||||
)}
|
||||
</gr-related-collapse>
|
||||
</section>`;
|
||||
|
||||
return html`<gr-endpoint-decorator name="related-changes-section">
|
||||
<gr-endpoint-param
|
||||
name="change"
|
||||
.value=${this.change}
|
||||
></gr-endpoint-param>
|
||||
<gr-endpoint-slot name="top"></gr-endpoint-slot>
|
||||
${relatedChangeSection} ${submittedTogetherSection} ${sameTopicSection}
|
||||
${mergeConflictsSection} ${cherryPicksSection}
|
||||
<gr-endpoint-slot name="bottom"></gr-endpoint-slot>
|
||||
</gr-endpoint-decorator>`;
|
||||
}
|
||||
|
||||
sectionSizeFactory(
|
||||
relatedChangesLen: number,
|
||||
submittedTogetherLen: number,
|
||||
sameTopicLen: number,
|
||||
mergeConflictsLen: number,
|
||||
cherryPicksLen: number
|
||||
) {
|
||||
const calcDefaultSize = (length: number) =>
|
||||
Math.min(length, DEFALT_NUM_CHANGES_WHEN_COLLAPSED);
|
||||
|
||||
const sectionSizes = [
|
||||
{
|
||||
section: Section.RELATED_CHANGES,
|
||||
size: calcDefaultSize(relatedChangesLen),
|
||||
len: relatedChangesLen,
|
||||
},
|
||||
{
|
||||
section: Section.SUBMITTED_TOGETHER,
|
||||
size: calcDefaultSize(submittedTogetherLen),
|
||||
len: submittedTogetherLen,
|
||||
},
|
||||
{
|
||||
section: Section.SAME_TOPIC,
|
||||
size: calcDefaultSize(sameTopicLen),
|
||||
len: sameTopicLen,
|
||||
},
|
||||
{
|
||||
section: Section.MERGE_CONFLICTS,
|
||||
size: calcDefaultSize(mergeConflictsLen),
|
||||
len: mergeConflictsLen,
|
||||
},
|
||||
{
|
||||
section: Section.CHERRY_PICKS,
|
||||
size: calcDefaultSize(cherryPicksLen),
|
||||
len: cherryPicksLen,
|
||||
},
|
||||
];
|
||||
|
||||
const FILLER = 1; // space for header
|
||||
let totalSize = sectionSizes.reduce(
|
||||
(acc, val) => acc + val.size + (val.size !== 0 ? FILLER : 0),
|
||||
0
|
||||
);
|
||||
|
||||
const MAX_SIZE = 16;
|
||||
for (let i = 0; i < sectionSizes.length; i++) {
|
||||
if (totalSize >= MAX_SIZE) break;
|
||||
const sizeObj = sectionSizes[i];
|
||||
if (sizeObj.size === sizeObj.len) continue;
|
||||
const newSize = Math.min(
|
||||
MAX_SIZE - totalSize + sizeObj.size,
|
||||
sizeObj.len
|
||||
);
|
||||
totalSize += newSize - sizeObj.size;
|
||||
sizeObj.size = newSize;
|
||||
}
|
||||
|
||||
return (section: Section) => {
|
||||
const sizeObj = sectionSizes.find(sizeObj => sizeObj.section === section);
|
||||
if (sizeObj) return sizeObj.size;
|
||||
return DEFALT_NUM_CHANGES_WHEN_COLLAPSED;
|
||||
};
|
||||
}
|
||||
|
||||
markersPredicateFactory(
|
||||
length: number,
|
||||
highlightIndex: number,
|
||||
numChangesShownWhenCollapsed = DEFALT_NUM_CHANGES_WHEN_COLLAPSED
|
||||
): (index: number) => ChangeMarkersInList {
|
||||
const showWhenCollapsedPredicate = (index: number) => {
|
||||
if (highlightIndex === -1) return index < numChangesShownWhenCollapsed;
|
||||
if (highlightIndex === 0)
|
||||
return index <= numChangesShownWhenCollapsed - 1;
|
||||
if (highlightIndex === length - 1)
|
||||
return index >= length - numChangesShownWhenCollapsed;
|
||||
let numBeforeHighlight = Math.floor(numChangesShownWhenCollapsed / 2);
|
||||
let numAfterHighlight =
|
||||
Math.floor(numChangesShownWhenCollapsed / 2) -
|
||||
(numChangesShownWhenCollapsed % 2 ? 0 : 1);
|
||||
numBeforeHighlight += Math.max(
|
||||
highlightIndex + numAfterHighlight - length + 1,
|
||||
0
|
||||
);
|
||||
numAfterHighlight -= Math.min(0, highlightIndex - numBeforeHighlight);
|
||||
return (
|
||||
highlightIndex - numBeforeHighlight <= index &&
|
||||
index <= highlightIndex + numAfterHighlight
|
||||
);
|
||||
};
|
||||
return (index: number) => {
|
||||
return {
|
||||
showCurrentChangeArrow:
|
||||
highlightIndex !== -1 && index === highlightIndex,
|
||||
showWhenCollapsed: showWhenCollapsedPredicate(index),
|
||||
showTopArrow:
|
||||
index >= 1 &&
|
||||
index !== highlightIndex &&
|
||||
showWhenCollapsedPredicate(index) &&
|
||||
!showWhenCollapsedPredicate(index - 1),
|
||||
showBottomArrow:
|
||||
index <= length - 2 &&
|
||||
index !== highlightIndex &&
|
||||
showWhenCollapsedPredicate(index) &&
|
||||
!showWhenCollapsedPredicate(index + 1),
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
renderMarkers(changeMarkers: ChangeMarkersInList) {
|
||||
if (changeMarkers.showCurrentChangeArrow) {
|
||||
return html`<span
|
||||
role="img"
|
||||
class="arrowToCurrentChange"
|
||||
aria-label="Arrow marking current change"
|
||||
>➔</span
|
||||
>`;
|
||||
}
|
||||
if (changeMarkers.showTopArrow) {
|
||||
return html`<span
|
||||
role="img"
|
||||
class="marker"
|
||||
aria-label="Arrow marking change has collapsed ancestors"
|
||||
><iron-icon icon="gr-icons:arrowDropUp"></iron-icon
|
||||
></span> `;
|
||||
}
|
||||
if (changeMarkers.showBottomArrow) {
|
||||
return html`<span
|
||||
role="img"
|
||||
class="marker"
|
||||
aria-label="Arrow marking change has collapsed descendants"
|
||||
><iron-icon icon="gr-icons:arrowDropDown"></iron-icon
|
||||
></span> `;
|
||||
}
|
||||
return nothing;
|
||||
}
|
||||
|
||||
reload(getRelatedChanges?: Promise<RelatedChangesInfo | undefined>) {
|
||||
const change = this.change;
|
||||
if (!change) return Promise.reject(new Error('change missing'));
|
||||
if (!this.patchNum) return Promise.reject(new Error('patchNum missing'));
|
||||
if (!getRelatedChanges) {
|
||||
getRelatedChanges = this.restApiService.getRelatedChanges(
|
||||
change._number,
|
||||
this.patchNum
|
||||
);
|
||||
}
|
||||
const promises: Array<Promise<void>> = [
|
||||
getRelatedChanges.then(response => {
|
||||
if (!response) {
|
||||
throw new Error('getRelatedChanges returned undefined response');
|
||||
}
|
||||
this.relatedChanges = response?.changes ?? [];
|
||||
}),
|
||||
this.restApiService
|
||||
.getChangesSubmittedTogether(change._number)
|
||||
.then(response => {
|
||||
this.submittedTogether = response;
|
||||
}),
|
||||
this.restApiService
|
||||
.getChangeCherryPicks(change.project, change.change_id, change._number)
|
||||
.then(response => {
|
||||
this.cherryPickChanges = response || [];
|
||||
}),
|
||||
];
|
||||
|
||||
// Get conflicts if change is open and is mergeable.
|
||||
// Mergeable is output of restApiServict.getMergeable from gr-change-view
|
||||
if (changeIsOpen(change) && this.mergeable) {
|
||||
promises.push(
|
||||
this.restApiService
|
||||
.getChangeConflicts(change._number)
|
||||
.then(response => {
|
||||
this.conflictingChanges = response ?? [];
|
||||
})
|
||||
);
|
||||
}
|
||||
if (change.topic) {
|
||||
const changeTopic = change.topic;
|
||||
promises.push(
|
||||
this.restApiService.getConfig().then(config => {
|
||||
if (config && !config.change.submit_whole_topic) {
|
||||
return this.restApiService
|
||||
.getChangesWithSameTopic(changeTopic, change._number)
|
||||
.then(response => {
|
||||
if (changeTopic === this.change?.topic) {
|
||||
this.sameTopicChanges = response ?? [];
|
||||
}
|
||||
});
|
||||
}
|
||||
this.sameTopicChanges = [];
|
||||
return Promise.resolve();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
/**
|
||||
* Do the given objects describe the same change? Compares the changes by
|
||||
* their numbers.
|
||||
*/
|
||||
_changesEqual(
|
||||
a?: ChangeInfo | RelatedChangeAndCommitInfo,
|
||||
b?: ChangeInfo | ParsedChangeInfo | RelatedChangeAndCommitInfo
|
||||
) {
|
||||
const aNum = this._getChangeNumber(a);
|
||||
const bNum = this._getChangeNumber(b);
|
||||
return aNum === bNum;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the change number from either a ChangeInfo (such as those included in
|
||||
* SubmittedTogetherInfo responses) or get the change number from a
|
||||
* RelatedChangeAndCommitInfo (such as those included in a
|
||||
* RelatedChangesInfo response).
|
||||
*/
|
||||
_getChangeNumber(
|
||||
change?: ChangeInfo | ParsedChangeInfo | RelatedChangeAndCommitInfo
|
||||
) {
|
||||
// Default to 0 if change property is not defined.
|
||||
if (!change) return 0;
|
||||
|
||||
if (isChangeInfo(change)) {
|
||||
return change._number;
|
||||
}
|
||||
return change._change_number;
|
||||
}
|
||||
|
||||
/*
|
||||
* A list of commit ids connected to change to understand if other change
|
||||
* is direct or indirect ancestor / descendant.
|
||||
*/
|
||||
_computeConnectedRevisions(
|
||||
change?: ParsedChangeInfo,
|
||||
patchNum?: PatchSetNum,
|
||||
relatedChanges?: RelatedChangeAndCommitInfo[]
|
||||
) {
|
||||
if (!patchNum || !relatedChanges || !change) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const connected: CommitId[] = [];
|
||||
const changeRevision = getRevisionKey(change, patchNum);
|
||||
const commits = relatedChanges.map(c => c.commit);
|
||||
let pos = commits.length - 1;
|
||||
|
||||
while (pos >= 0) {
|
||||
const commit: CommitId = commits[pos].commit;
|
||||
connected.push(commit);
|
||||
// TODO(TS): Ensure that both (commit and changeRevision) are string and use === instead
|
||||
// eslint-disable-next-line eqeqeq
|
||||
if (commit == changeRevision) {
|
||||
break;
|
||||
}
|
||||
pos--;
|
||||
}
|
||||
while (pos >= 0) {
|
||||
for (let i = 0; i < commits[pos].parents.length; i++) {
|
||||
if (connected.includes(commits[pos].parents[i].commit)) {
|
||||
connected.push(commits[pos].commit);
|
||||
break;
|
||||
}
|
||||
}
|
||||
--pos;
|
||||
}
|
||||
return connected;
|
||||
}
|
||||
}
|
||||
|
||||
@customElement('gr-related-collapse')
|
||||
export class GrRelatedCollapse extends GrLitElement {
|
||||
@property()
|
||||
title = '';
|
||||
|
||||
@property()
|
||||
showAll = false;
|
||||
|
||||
@property()
|
||||
length = 0;
|
||||
|
||||
@property()
|
||||
numChangesWhenCollapsed = DEFALT_NUM_CHANGES_WHEN_COLLAPSED;
|
||||
|
||||
private readonly reporting = appContext.reportingService;
|
||||
|
||||
static get styles() {
|
||||
return [
|
||||
sharedStyles,
|
||||
css`
|
||||
.title {
|
||||
font-weight: var(--font-weight-bold);
|
||||
color: var(--deemphasized-text-color);
|
||||
padding-left: var(--metadata-horizontal-padding);
|
||||
}
|
||||
h4 {
|
||||
display: flex;
|
||||
align-self: flex-end;
|
||||
}
|
||||
gr-button {
|
||||
display: flex;
|
||||
}
|
||||
/* This is a hacky solution from old gr-related-change-list
|
||||
* TODO(milutin): find layout without needing it
|
||||
*/
|
||||
h4:before,
|
||||
gr-button:before,
|
||||
::slotted(gr-related-change):before {
|
||||
content: ' ';
|
||||
flex-shrink: 0;
|
||||
width: 1.2em;
|
||||
}
|
||||
.collapsed ::slotted(gr-related-change.show-when-collapsed) {
|
||||
visibility: visible;
|
||||
height: auto;
|
||||
}
|
||||
.collapsed ::slotted(.marker) {
|
||||
display: block;
|
||||
}
|
||||
.show-all ::slotted(.marker) {
|
||||
display: none;
|
||||
}
|
||||
/* keep width, so width of section and position of show all button
|
||||
* are set according to width of all (even hidden) elements
|
||||
*/
|
||||
.collapsed ::slotted(gr-related-change) {
|
||||
visibility: hidden;
|
||||
height: 0px;
|
||||
}
|
||||
::slotted(gr-related-change) {
|
||||
visibility: visible;
|
||||
height: auto;
|
||||
}
|
||||
gr-button iron-icon {
|
||||
color: inherit;
|
||||
--iron-icon-height: 18px;
|
||||
--iron-icon-width: 18px;
|
||||
}
|
||||
.container {
|
||||
justify-content: space-between;
|
||||
display: flex;
|
||||
margin-bottom: var(--spacing-s);
|
||||
}
|
||||
:host(.first) .container {
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
render() {
|
||||
const title = html`<h4 class="title">${this.title}</h4>`;
|
||||
|
||||
const collapsible = this.length > this.numChangesWhenCollapsed;
|
||||
const items = html` <div
|
||||
class="${!this.showAll && collapsible ? 'collapsed' : 'show-all'}"
|
||||
>
|
||||
<slot></slot>
|
||||
</div>`;
|
||||
|
||||
let button: TemplateResult | typeof nothing = nothing;
|
||||
if (collapsible) {
|
||||
let buttonText = 'Show less';
|
||||
let buttonIcon = 'expand-less';
|
||||
if (!this.showAll) {
|
||||
buttonText = `Show all (${this.length})`;
|
||||
buttonIcon = 'expand-more';
|
||||
}
|
||||
button = html`<gr-button link="" @click="${this.toggle}"
|
||||
>${buttonText}<iron-icon icon="gr-icons:${buttonIcon}"></iron-icon
|
||||
></gr-button>`;
|
||||
}
|
||||
|
||||
return html`<div class="container">${title}${button}</div>
|
||||
${items}`;
|
||||
}
|
||||
|
||||
private toggle(e: MouseEvent) {
|
||||
e.stopPropagation();
|
||||
this.showAll = !this.showAll;
|
||||
this.reporting.reportInteraction('toggle show all button', {
|
||||
sectionName: this.title,
|
||||
toState: this.showAll ? 'Show all' : 'Show less',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'gr-related-changes-list-experimental': GrRelatedChangesListExperimental;
|
||||
'gr-related-collapse': GrRelatedCollapse;
|
||||
}
|
||||
}
|
||||
@@ -1,630 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright (C) 2021 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 {SinonStubbedMember} from 'sinon/pkg/sinon-esm';
|
||||
import {PluginApi} from '../../../api/plugin';
|
||||
import {ChangeStatus} from '../../../constants/constants';
|
||||
import {RestApiService} from '../../../services/gr-rest-api/gr-rest-api';
|
||||
import '../../../test/common-test-setup-karma';
|
||||
import {
|
||||
createChange,
|
||||
createCommitInfoWithRequiredCommit,
|
||||
createParsedChange,
|
||||
createRelatedChangeAndCommitInfo,
|
||||
createRelatedChangesInfo,
|
||||
createRevision,
|
||||
createSubmittedTogetherInfo,
|
||||
} from '../../../test/test-data-generators';
|
||||
import {
|
||||
queryAndAssert,
|
||||
resetPlugins,
|
||||
stubRestApi,
|
||||
} from '../../../test/test-utils';
|
||||
import {
|
||||
ChangeId,
|
||||
ChangeInfo,
|
||||
CommitId,
|
||||
NumericChangeId,
|
||||
PatchSetNum,
|
||||
RelatedChangeAndCommitInfo,
|
||||
RelatedChangesInfo,
|
||||
SubmittedTogetherInfo,
|
||||
} from '../../../types/common';
|
||||
import {ParsedChangeInfo} from '../../../types/types';
|
||||
import {GrEndpointDecorator} from '../../plugins/gr-endpoint-decorator/gr-endpoint-decorator';
|
||||
import {_testOnly_initGerritPluginApi} from '../../shared/gr-js-api-interface/gr-gerrit';
|
||||
import {getPluginLoader} from '../../shared/gr-js-api-interface/gr-plugin-loader';
|
||||
import './gr-related-changes-list-experimental';
|
||||
import {
|
||||
ChangeMarkersInList,
|
||||
GrRelatedChangesListExperimental,
|
||||
GrRelatedCollapse,
|
||||
Section,
|
||||
} from './gr-related-changes-list-experimental';
|
||||
|
||||
const pluginApi = _testOnly_initGerritPluginApi();
|
||||
|
||||
const basicFixture = fixtureFromElement('gr-related-changes-list-experimental');
|
||||
|
||||
suite('gr-related-changes-list-experimental', () => {
|
||||
let element: GrRelatedChangesListExperimental;
|
||||
|
||||
setup(() => {
|
||||
element = basicFixture.instantiate();
|
||||
});
|
||||
|
||||
suite('show when collapsed', () => {
|
||||
function genBoolArray(
|
||||
instructions: Array<{
|
||||
len: number;
|
||||
v: boolean;
|
||||
}>
|
||||
) {
|
||||
return instructions
|
||||
.map(inst => Array.from({length: inst.len}, () => inst.v))
|
||||
.reduce((acc, val) => acc.concat(val), []);
|
||||
}
|
||||
|
||||
function checkShowWhenCollapsed(
|
||||
expected: boolean[],
|
||||
markersPredicate: (index: number) => ChangeMarkersInList,
|
||||
msg: string
|
||||
) {
|
||||
for (let i = 0; i < expected.length; i++) {
|
||||
assert.equal(
|
||||
markersPredicate(i).showWhenCollapsed,
|
||||
expected[i],
|
||||
`change on pos (${i}) ${msg}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
test('size 5', () => {
|
||||
const markersPredicate = element.markersPredicateFactory(10, 4, 5);
|
||||
const expectedCollapsing = genBoolArray([
|
||||
{len: 2, v: false},
|
||||
{len: 5, v: true},
|
||||
{len: 3, v: false},
|
||||
]);
|
||||
checkShowWhenCollapsed(
|
||||
expectedCollapsing,
|
||||
markersPredicate,
|
||||
'highlight 4, size 10, size 5'
|
||||
);
|
||||
|
||||
const markersPredicate2 = element.markersPredicateFactory(10, 8, 5);
|
||||
const expectedCollapsing2 = genBoolArray([
|
||||
{len: 5, v: false},
|
||||
{len: 5, v: true},
|
||||
]);
|
||||
checkShowWhenCollapsed(
|
||||
expectedCollapsing2,
|
||||
markersPredicate2,
|
||||
'highlight 8, size 10, size 5'
|
||||
);
|
||||
|
||||
const markersPredicate3 = element.markersPredicateFactory(10, 1, 5);
|
||||
const expectedCollapsing3 = genBoolArray([
|
||||
{len: 5, v: true},
|
||||
{len: 5, v: false},
|
||||
]);
|
||||
checkShowWhenCollapsed(
|
||||
expectedCollapsing3,
|
||||
markersPredicate3,
|
||||
'highlight 1, size 10, size 5'
|
||||
);
|
||||
});
|
||||
|
||||
test('size 4', () => {
|
||||
const markersPredicate = element.markersPredicateFactory(10, 4, 4);
|
||||
const expectedCollapsing = genBoolArray([
|
||||
{len: 2, v: false},
|
||||
{len: 4, v: true},
|
||||
{len: 4, v: false},
|
||||
]);
|
||||
checkShowWhenCollapsed(
|
||||
expectedCollapsing,
|
||||
markersPredicate,
|
||||
'highlight 4, len 10, size 4'
|
||||
);
|
||||
|
||||
const markersPredicate2 = element.markersPredicateFactory(10, 8, 4);
|
||||
const expectedCollapsing2 = genBoolArray([
|
||||
{len: 6, v: false},
|
||||
{len: 4, v: true},
|
||||
]);
|
||||
checkShowWhenCollapsed(
|
||||
expectedCollapsing2,
|
||||
markersPredicate2,
|
||||
'highlight 8, len 10, size 4'
|
||||
);
|
||||
|
||||
const markersPredicate3 = element.markersPredicateFactory(10, 1, 4);
|
||||
const expectedCollapsing3 = genBoolArray([
|
||||
{len: 4, v: true},
|
||||
{len: 6, v: false},
|
||||
]);
|
||||
checkShowWhenCollapsed(
|
||||
expectedCollapsing3,
|
||||
markersPredicate3,
|
||||
'highlight 1, len 10, size 4'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
suite('section size', () => {
|
||||
test('1 section', () => {
|
||||
const sectionSize = element.sectionSizeFactory(20, 0, 0, 0, 0);
|
||||
assert.equal(sectionSize(Section.RELATED_CHANGES), 15);
|
||||
const sectionSize2 = element.sectionSizeFactory(0, 0, 10, 0, 0);
|
||||
assert.equal(sectionSize2(Section.SAME_TOPIC), 10);
|
||||
});
|
||||
test('2 sections', () => {
|
||||
const sectionSize = element.sectionSizeFactory(20, 20, 0, 0, 0);
|
||||
assert.equal(sectionSize(Section.RELATED_CHANGES), 11);
|
||||
assert.equal(sectionSize(Section.SUBMITTED_TOGETHER), 3);
|
||||
const sectionSize2 = element.sectionSizeFactory(4, 0, 10, 0, 0);
|
||||
assert.equal(sectionSize2(Section.RELATED_CHANGES), 4);
|
||||
assert.equal(sectionSize2(Section.SAME_TOPIC), 10);
|
||||
});
|
||||
test('many sections', () => {
|
||||
const sectionSize = element.sectionSizeFactory(20, 20, 3, 3, 3);
|
||||
assert.equal(sectionSize(Section.RELATED_CHANGES), 3);
|
||||
assert.equal(sectionSize(Section.SUBMITTED_TOGETHER), 3);
|
||||
const sectionSize2 = element.sectionSizeFactory(4, 1, 10, 1, 1);
|
||||
assert.equal(sectionSize2(Section.RELATED_CHANGES), 4);
|
||||
assert.equal(sectionSize2(Section.SAME_TOPIC), 4);
|
||||
});
|
||||
});
|
||||
|
||||
suite('test first non-empty list', () => {
|
||||
const relatedChangeInfo: RelatedChangesInfo = {
|
||||
...createRelatedChangesInfo(),
|
||||
changes: [createRelatedChangeAndCommitInfo()],
|
||||
};
|
||||
const submittedTogether: SubmittedTogetherInfo = {
|
||||
...createSubmittedTogetherInfo(),
|
||||
changes: [createChange()],
|
||||
};
|
||||
|
||||
setup(() => {
|
||||
element.change = createParsedChange();
|
||||
element.patchNum = 1 as PatchSetNum;
|
||||
});
|
||||
|
||||
test('first list', async () => {
|
||||
stubRestApi('getRelatedChanges').returns(
|
||||
Promise.resolve(relatedChangeInfo)
|
||||
);
|
||||
await element.reload();
|
||||
const section = queryAndAssert<HTMLElement>(element, '#relatedChanges');
|
||||
const relatedChanges = queryAndAssert<GrRelatedCollapse>(
|
||||
section,
|
||||
'gr-related-collapse'
|
||||
);
|
||||
assert.isTrue(relatedChanges!.classList.contains('first'));
|
||||
});
|
||||
|
||||
test('first empty second non-empty', async () => {
|
||||
stubRestApi('getRelatedChanges').returns(
|
||||
Promise.resolve(createRelatedChangesInfo())
|
||||
);
|
||||
stubRestApi('getChangesSubmittedTogether').returns(
|
||||
Promise.resolve(submittedTogether)
|
||||
);
|
||||
await element.reload();
|
||||
const relatedChanges = queryAndAssert<GrRelatedCollapse>(
|
||||
queryAndAssert<HTMLElement>(element, '#relatedChanges'),
|
||||
'gr-related-collapse'
|
||||
);
|
||||
assert.isFalse(relatedChanges!.classList.contains('first'));
|
||||
const submittedTogetherSection = queryAndAssert<GrRelatedCollapse>(
|
||||
queryAndAssert<HTMLElement>(element, '#submittedTogether'),
|
||||
'gr-related-collapse'
|
||||
);
|
||||
assert.isTrue(submittedTogetherSection!.classList.contains('first'));
|
||||
});
|
||||
|
||||
test('first non-empty second empty third non-empty', async () => {
|
||||
stubRestApi('getRelatedChanges').returns(
|
||||
Promise.resolve(relatedChangeInfo)
|
||||
);
|
||||
stubRestApi('getChangesSubmittedTogether').returns(
|
||||
Promise.resolve(createSubmittedTogetherInfo())
|
||||
);
|
||||
stubRestApi('getChangeCherryPicks').returns(
|
||||
Promise.resolve([createChange()])
|
||||
);
|
||||
await element.reload();
|
||||
const relatedChanges = queryAndAssert<GrRelatedCollapse>(
|
||||
queryAndAssert<HTMLElement>(element, '#relatedChanges'),
|
||||
'gr-related-collapse'
|
||||
);
|
||||
assert.isTrue(relatedChanges!.classList.contains('first'));
|
||||
const submittedTogetherSection = queryAndAssert<GrRelatedCollapse>(
|
||||
queryAndAssert<HTMLElement>(element, '#submittedTogether'),
|
||||
'gr-related-collapse'
|
||||
);
|
||||
assert.isFalse(submittedTogetherSection!.classList.contains('first'));
|
||||
const cherryPicks = queryAndAssert<GrRelatedCollapse>(
|
||||
queryAndAssert<HTMLElement>(element, '#cherryPicks'),
|
||||
'gr-related-collapse'
|
||||
);
|
||||
assert.isFalse(cherryPicks!.classList.contains('first'));
|
||||
});
|
||||
});
|
||||
|
||||
test('_changesEqual', () => {
|
||||
const change1: ChangeInfo = {
|
||||
...createChange(),
|
||||
change_id: '123' as ChangeId,
|
||||
_number: 0 as NumericChangeId,
|
||||
};
|
||||
const change2: ChangeInfo = {
|
||||
...createChange(),
|
||||
change_id: '456' as ChangeId,
|
||||
_number: 1 as NumericChangeId,
|
||||
};
|
||||
const change3: ChangeInfo = {
|
||||
...createChange(),
|
||||
change_id: '123' as ChangeId,
|
||||
_number: 2 as NumericChangeId,
|
||||
};
|
||||
const change4: RelatedChangeAndCommitInfo = {
|
||||
...createRelatedChangeAndCommitInfo(),
|
||||
change_id: '123' as ChangeId,
|
||||
_change_number: 1 as NumericChangeId,
|
||||
};
|
||||
|
||||
assert.isTrue(element._changesEqual(change1, change1));
|
||||
assert.isFalse(element._changesEqual(change1, change2));
|
||||
assert.isFalse(element._changesEqual(change1, change3));
|
||||
assert.isTrue(element._changesEqual(change2, change4));
|
||||
});
|
||||
|
||||
test('_getChangeNumber', () => {
|
||||
const change1: ChangeInfo = {
|
||||
...createChange(),
|
||||
change_id: '123' as ChangeId,
|
||||
_number: 0 as NumericChangeId,
|
||||
};
|
||||
const change2: ChangeInfo = {
|
||||
...createChange(),
|
||||
change_id: '456' as ChangeId,
|
||||
_number: 1 as NumericChangeId,
|
||||
};
|
||||
assert.equal(element._getChangeNumber(change1), 0);
|
||||
assert.equal(element._getChangeNumber(change2), 1);
|
||||
});
|
||||
|
||||
suite('get conflicts tests', () => {
|
||||
let element: GrRelatedChangesListExperimental;
|
||||
let conflictsStub: SinonStubbedMember<RestApiService['getChangeConflicts']>;
|
||||
|
||||
setup(() => {
|
||||
element = basicFixture.instantiate();
|
||||
conflictsStub = stubRestApi('getChangeConflicts').returns(
|
||||
Promise.resolve(undefined)
|
||||
);
|
||||
});
|
||||
|
||||
test('request conflicts if open and mergeable', () => {
|
||||
element.patchNum = 7 as PatchSetNum;
|
||||
element.change = {
|
||||
...createParsedChange(),
|
||||
change_id: '123' as ChangeId,
|
||||
status: ChangeStatus.NEW,
|
||||
};
|
||||
element.mergeable = true;
|
||||
element.reload();
|
||||
assert.isTrue(conflictsStub.called);
|
||||
});
|
||||
|
||||
test('does not request conflicts if closed and mergeable', () => {
|
||||
element.patchNum = 7 as PatchSetNum;
|
||||
element.change = {
|
||||
...createParsedChange(),
|
||||
change_id: '123' as ChangeId,
|
||||
status: ChangeStatus.NEW,
|
||||
};
|
||||
element.reload();
|
||||
assert.isFalse(conflictsStub.called);
|
||||
});
|
||||
|
||||
test('does not request conflicts if open and not mergeable', () => {
|
||||
element.patchNum = 7 as PatchSetNum;
|
||||
element.change = {
|
||||
...createParsedChange(),
|
||||
change_id: '123' as ChangeId,
|
||||
status: ChangeStatus.NEW,
|
||||
};
|
||||
element.mergeable = false;
|
||||
element.reload();
|
||||
assert.isFalse(conflictsStub.called);
|
||||
});
|
||||
|
||||
test('doesnt request conflicts if closed and not mergeable', () => {
|
||||
element.patchNum = 7 as PatchSetNum;
|
||||
element.change = {
|
||||
...createParsedChange(),
|
||||
change_id: '123' as ChangeId,
|
||||
status: ChangeStatus.NEW,
|
||||
};
|
||||
element.mergeable = false;
|
||||
element.reload();
|
||||
assert.isFalse(conflictsStub.called);
|
||||
});
|
||||
});
|
||||
|
||||
test('connected revisions', () => {
|
||||
const change: ParsedChangeInfo = {
|
||||
...createParsedChange(),
|
||||
revisions: {
|
||||
e3c6d60783bfdec9ebae7dcfec4662360433449e: createRevision(1),
|
||||
'26e5e4c9c7ae31cbd876271cca281ce22b413997': createRevision(2),
|
||||
bf7884d695296ca0c91702ba3e2bc8df0f69a907: createRevision(7),
|
||||
b5fc49f2e67d1889d5275cac04ad3648f2ec7fe3: createRevision(5),
|
||||
d6bcee67570859ccb684873a85cf50b1f0e96fda: createRevision(6),
|
||||
cc960918a7f90388f4a9e05753d0f7b90ad44546: createRevision(3),
|
||||
'9e593f6dcc2c0785a2ad2c895a34ad2aa9a0d8b6': createRevision(4),
|
||||
},
|
||||
};
|
||||
let patchNum = 7 as PatchSetNum;
|
||||
let relatedChanges: RelatedChangeAndCommitInfo[] = [
|
||||
{
|
||||
...createRelatedChangeAndCommitInfo(),
|
||||
commit: {
|
||||
...createCommitInfoWithRequiredCommit(
|
||||
'2cebeedfb1e80f4b872d0a13ade529e70652c0c8'
|
||||
),
|
||||
parents: [
|
||||
{
|
||||
commit: '87ed20b241576b620bbaa3dfd47715ce6782b7dd' as CommitId,
|
||||
subject: 'subject1',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
...createRelatedChangeAndCommitInfo(),
|
||||
commit: {
|
||||
...createCommitInfoWithRequiredCommit(
|
||||
'87ed20b241576b620bbaa3dfd47715ce6782b7dd'
|
||||
),
|
||||
parents: [
|
||||
{
|
||||
commit: '6c71f9e86ba955a7e01e2088bce0050a90eb9fbb' as CommitId,
|
||||
subject: 'subject2',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
...createRelatedChangeAndCommitInfo(),
|
||||
commit: {
|
||||
...createCommitInfoWithRequiredCommit(
|
||||
'6c71f9e86ba955a7e01e2088bce0050a90eb9fbb'
|
||||
),
|
||||
parents: [
|
||||
{
|
||||
commit: 'b0ccb183494a8e340b8725a2dc553967d61e6dae' as CommitId,
|
||||
subject: 'subject3',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
...createRelatedChangeAndCommitInfo(),
|
||||
commit: {
|
||||
...createCommitInfoWithRequiredCommit(
|
||||
'b0ccb183494a8e340b8725a2dc553967d61e6dae'
|
||||
),
|
||||
parents: [
|
||||
{
|
||||
commit: 'bf7884d695296ca0c91702ba3e2bc8df0f69a907' as CommitId,
|
||||
subject: 'subject4',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
...createRelatedChangeAndCommitInfo(),
|
||||
commit: {
|
||||
...createCommitInfoWithRequiredCommit(
|
||||
'bf7884d695296ca0c91702ba3e2bc8df0f69a907'
|
||||
),
|
||||
parents: [
|
||||
{
|
||||
commit: '613bc4f81741a559c6667ac08d71dcc3348f73ce' as CommitId,
|
||||
subject: 'subject5',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
...createRelatedChangeAndCommitInfo(),
|
||||
commit: {
|
||||
...createCommitInfoWithRequiredCommit(
|
||||
'613bc4f81741a559c6667ac08d71dcc3348f73ce'
|
||||
),
|
||||
parents: [
|
||||
{
|
||||
commit: '455ed9cd27a16bf6991f04dcc57ef575dc4d5e75' as CommitId,
|
||||
subject: 'subject6',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
let connectedChanges = element._computeConnectedRevisions(
|
||||
change,
|
||||
patchNum,
|
||||
relatedChanges
|
||||
);
|
||||
assert.deepEqual(connectedChanges, [
|
||||
'613bc4f81741a559c6667ac08d71dcc3348f73ce',
|
||||
'bf7884d695296ca0c91702ba3e2bc8df0f69a907',
|
||||
'bf7884d695296ca0c91702ba3e2bc8df0f69a907',
|
||||
'b0ccb183494a8e340b8725a2dc553967d61e6dae',
|
||||
'6c71f9e86ba955a7e01e2088bce0050a90eb9fbb',
|
||||
'87ed20b241576b620bbaa3dfd47715ce6782b7dd',
|
||||
'2cebeedfb1e80f4b872d0a13ade529e70652c0c8',
|
||||
]);
|
||||
|
||||
patchNum = 4 as PatchSetNum;
|
||||
relatedChanges = [
|
||||
{
|
||||
...createRelatedChangeAndCommitInfo(),
|
||||
commit: {
|
||||
...createCommitInfoWithRequiredCommit(
|
||||
'2cebeedfb1e80f4b872d0a13ade529e70652c0c8'
|
||||
),
|
||||
parents: [
|
||||
{
|
||||
commit: '87ed20b241576b620bbaa3dfd47715ce6782b7dd' as CommitId,
|
||||
subject: 'My parent commit',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
...createRelatedChangeAndCommitInfo(),
|
||||
commit: {
|
||||
...createCommitInfoWithRequiredCommit(
|
||||
'87ed20b241576b620bbaa3dfd47715ce6782b7dd'
|
||||
),
|
||||
parents: [
|
||||
{
|
||||
commit: '6c71f9e86ba955a7e01e2088bce0050a90eb9fbb' as CommitId,
|
||||
subject: 'My parent commit',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
...createRelatedChangeAndCommitInfo(),
|
||||
commit: {
|
||||
...createCommitInfoWithRequiredCommit(
|
||||
'6c71f9e86ba955a7e01e2088bce0050a90eb9fbb'
|
||||
),
|
||||
parents: [
|
||||
{
|
||||
commit: 'b0ccb183494a8e340b8725a2dc553967d61e6dae' as CommitId,
|
||||
subject: 'My parent commit',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
...createRelatedChangeAndCommitInfo(),
|
||||
commit: {
|
||||
...createCommitInfoWithRequiredCommit(
|
||||
'a3e5d9d4902b915a39e2efba5577211b9b3ebe7b'
|
||||
),
|
||||
parents: [
|
||||
{
|
||||
commit: '9e593f6dcc2c0785a2ad2c895a34ad2aa9a0d8b6' as CommitId,
|
||||
subject: 'My parent commit',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
...createRelatedChangeAndCommitInfo(),
|
||||
commit: {
|
||||
...createCommitInfoWithRequiredCommit(
|
||||
'9e593f6dcc2c0785a2ad2c895a34ad2aa9a0d8b6'
|
||||
),
|
||||
parents: [
|
||||
{
|
||||
commit: 'af815dac54318826b7f1fa468acc76349ffc588e' as CommitId,
|
||||
subject: 'My parent commit',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
...createRelatedChangeAndCommitInfo(),
|
||||
commit: {
|
||||
...createCommitInfoWithRequiredCommit(
|
||||
'af815dac54318826b7f1fa468acc76349ffc588e'
|
||||
),
|
||||
parents: [
|
||||
{
|
||||
commit: '58f76e406e24cb8b0f5d64c7f5ac1e8616d0a22c' as CommitId,
|
||||
subject: 'My parent commit',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
connectedChanges = element._computeConnectedRevisions(
|
||||
change,
|
||||
patchNum,
|
||||
relatedChanges
|
||||
);
|
||||
assert.deepEqual(connectedChanges, [
|
||||
'af815dac54318826b7f1fa468acc76349ffc588e',
|
||||
'9e593f6dcc2c0785a2ad2c895a34ad2aa9a0d8b6',
|
||||
'9e593f6dcc2c0785a2ad2c895a34ad2aa9a0d8b6',
|
||||
'a3e5d9d4902b915a39e2efba5577211b9b3ebe7b',
|
||||
]);
|
||||
});
|
||||
|
||||
suite('gr-related-changes-list plugin tests', () => {
|
||||
let element: GrRelatedChangesListExperimental;
|
||||
|
||||
setup(() => {
|
||||
resetPlugins();
|
||||
element = basicFixture.instantiate();
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
resetPlugins();
|
||||
});
|
||||
|
||||
test('endpoint params', done => {
|
||||
element.change = {...createParsedChange(), labels: {}};
|
||||
interface RelatedChangesListGrEndpointDecorator
|
||||
extends GrEndpointDecorator {
|
||||
plugin: PluginApi;
|
||||
change: ParsedChangeInfo;
|
||||
}
|
||||
let hookEl: RelatedChangesListGrEndpointDecorator;
|
||||
let plugin: PluginApi;
|
||||
pluginApi.install(
|
||||
p => {
|
||||
plugin = p;
|
||||
plugin
|
||||
.hook('related-changes-section')
|
||||
.getLastAttached()
|
||||
.then(el => (hookEl = el as RelatedChangesListGrEndpointDecorator));
|
||||
},
|
||||
'0.1',
|
||||
'http://some/plugins/url1.js'
|
||||
);
|
||||
getPluginLoader().loadPlugins([]);
|
||||
flush(() => {
|
||||
assert.strictEqual(hookEl.plugin, plugin);
|
||||
assert.strictEqual(hookEl.change, element.change);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,222 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright (C) 2020 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 {html} from '@polymer/polymer/lib/utils/html-tag';
|
||||
|
||||
export const htmlTemplate = html`
|
||||
<style include="shared-styles">
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
section {
|
||||
margin-bottom: 1.4em; /* Same as line height for collapse purposes */
|
||||
}
|
||||
a {
|
||||
display: block;
|
||||
}
|
||||
.changeContainer,
|
||||
a {
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.changeContainer {
|
||||
display: flex;
|
||||
}
|
||||
.arrowToCurrentChange {
|
||||
position: absolute;
|
||||
}
|
||||
h4,
|
||||
section div {
|
||||
display: flex;
|
||||
}
|
||||
h4:before,
|
||||
section div:before {
|
||||
content: ' ';
|
||||
flex-shrink: 0;
|
||||
width: 1.2em;
|
||||
}
|
||||
.note {
|
||||
color: var(--error-text-color);
|
||||
}
|
||||
.relatedChanges a {
|
||||
display: inline-block;
|
||||
}
|
||||
.strikethrough {
|
||||
color: var(--deemphasized-text-color);
|
||||
text-decoration: line-through;
|
||||
}
|
||||
.status {
|
||||
color: var(--deemphasized-text-color);
|
||||
font-weight: var(--font-weight-bold);
|
||||
margin-left: var(--spacing-xs);
|
||||
}
|
||||
.notCurrent {
|
||||
color: var(--warning-foreground);
|
||||
}
|
||||
.indirectAncestor {
|
||||
color: var(--indirect-ancestor-text-color);
|
||||
}
|
||||
.submittableCheck {
|
||||
padding-left: var(--spacing-s);
|
||||
color: var(--positive-green-text-color);
|
||||
display: none;
|
||||
}
|
||||
.submittableCheck.submittable {
|
||||
display: inline;
|
||||
}
|
||||
.hidden,
|
||||
.mobile {
|
||||
display: none;
|
||||
}
|
||||
@media screen and (max-width: 60em) {
|
||||
.mobile {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<div>
|
||||
<gr-endpoint-decorator name="related-changes-section">
|
||||
<gr-endpoint-param name="change" value="[[change]]"></gr-endpoint-param>
|
||||
<gr-endpoint-slot name="top"></gr-endpoint-slot>
|
||||
<section
|
||||
class="relatedChanges"
|
||||
hidden$="[[!_relatedResponse.changes.length]]"
|
||||
hidden=""
|
||||
>
|
||||
<h4>Relation chain</h4>
|
||||
<template
|
||||
is="dom-repeat"
|
||||
items="[[_relatedResponse.changes]]"
|
||||
as="related"
|
||||
>
|
||||
<template is="dom-if" if="[[_changesEqual(related, change)]]">
|
||||
<span
|
||||
role="img"
|
||||
class="arrowToCurrentChange"
|
||||
aria-label="Arrow marking current change"
|
||||
>➔</span
|
||||
>
|
||||
</template>
|
||||
<div class="rightIndent changeContainer">
|
||||
<a
|
||||
href$="[[_computeChangeURL(related._change_number, related.project, related._revision_number)]]"
|
||||
class$="[[_computeLinkClass(related)]]"
|
||||
title$="[[related.commit.subject]]"
|
||||
on-click="_reportClick"
|
||||
>
|
||||
[[related.commit.subject]]
|
||||
</a>
|
||||
<span class$="[[_computeChangeStatusClass(related)]]">
|
||||
([[_computeChangeStatus(related)]])
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</section>
|
||||
<section
|
||||
id="submittedTogether"
|
||||
class$="[[_computeSubmittedTogetherClass(_submittedTogether)]]"
|
||||
>
|
||||
<h4>Submitted together</h4>
|
||||
<template
|
||||
is="dom-repeat"
|
||||
items="[[_submittedTogether.changes]]"
|
||||
as="related"
|
||||
>
|
||||
<template is="dom-if" if="[[_changesEqual(related, change)]]">
|
||||
<span
|
||||
role="img"
|
||||
class="arrowToCurrentChange"
|
||||
aria-label="Arrow marking current change"
|
||||
>➔</span
|
||||
>
|
||||
</template>
|
||||
<div class="changeContainer">
|
||||
<a
|
||||
href$="[[_computeChangeURL(related._number, related.project)]]"
|
||||
class$="[[_computeLinkClass(related)]]"
|
||||
title$="[[related.project]]: [[related.branch]]: [[related.subject]]"
|
||||
on-click="_reportClick"
|
||||
>
|
||||
[[related.project]]: [[related.branch]]: [[related.subject]]
|
||||
</a>
|
||||
<span
|
||||
tabindex="-1"
|
||||
title="Submittable"
|
||||
class$="submittableCheck [[_computeLinkClass(related)]]"
|
||||
role="img"
|
||||
aria-label="Submittable"
|
||||
>✓</span
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
<template is="dom-if" if="[[_submittedTogether.non_visible_changes]]">
|
||||
<div class="note">
|
||||
[[_computeNonVisibleChangesNote(_submittedTogether.non_visible_changes)]]
|
||||
</div>
|
||||
</template>
|
||||
</section>
|
||||
<section hidden$="[[!_sameTopic.length]]" hidden="">
|
||||
<h4>Same topic</h4>
|
||||
<template is="dom-repeat" items="[[_sameTopic]]" as="change">
|
||||
<div>
|
||||
<a
|
||||
href$="[[_computeChangeURL(change._number, change.project)]]"
|
||||
class$="[[_computeLinkClass(change)]]"
|
||||
title$="[[change.project]]: [[change.branch]]: [[change.subject]]"
|
||||
on-click="_reportClick"
|
||||
>
|
||||
[[change.project]]: [[change.branch]]: [[change.subject]]
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
</section>
|
||||
<section hidden$="[[!_conflicts.length]]" hidden="">
|
||||
<h4>Merge conflicts</h4>
|
||||
<template is="dom-repeat" items="[[_conflicts]]" as="change">
|
||||
<div>
|
||||
<a
|
||||
href$="[[_computeChangeURL(change._number, change.project)]]"
|
||||
class$="[[_computeLinkClass(change)]]"
|
||||
title$="[[change.subject]]"
|
||||
on-click="_reportClick"
|
||||
>
|
||||
[[change.subject]]
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
</section>
|
||||
<section hidden$="[[!_cherryPicks.length]]" hidden="">
|
||||
<h4>Cherry picks</h4>
|
||||
<template is="dom-repeat" items="[[_cherryPicks]]" as="change">
|
||||
<div>
|
||||
<a
|
||||
href$="[[_computeChangeURL(change._number, change.project)]]"
|
||||
class$="[[_computeLinkClass(change)]]"
|
||||
title$="[[change.branch]]: [[change.subject]]"
|
||||
on-click="_reportClick"
|
||||
>
|
||||
[[change.branch]]: [[change.subject]]
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
</section>
|
||||
<gr-endpoint-slot name="bottom"></gr-endpoint-slot>
|
||||
</gr-endpoint-decorator>
|
||||
</div>
|
||||
<div hidden$="[[!loading]]">Loading...</div>
|
||||
`;
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user