Move status and revert loading from change view into models Release-Notes: skip Google-Bug-Id: b/247042673 Change-Id: I1f372b2ae62ba8fa1d978c726c29ede8754a5d2f
diff --git a/polygerrit-ui/app/models/change/change-model.ts b/polygerrit-ui/app/models/change/change-model.ts index 8be2c51..4863852 100644 --- a/polygerrit-ui/app/models/change/change-model.ts +++ b/polygerrit-ui/app/models/change/change-model.ts
@@ -48,6 +48,7 @@ createEditUrl, } from '../views/change'; import {NavigationService} from '../../elements/core/gr-navigation/gr-navigation'; +import {getRevertCreatedChangeIds} from '../../utils/message-util'; export enum LoadingStatus { NOT_LOADED = 'NOT_LOADED', @@ -332,6 +333,12 @@ ([change, account]) => isOwner(change, account) ); + public readonly messages$ = select(this.change$, change => change?.messages); + + public readonly revertingChangeIds$ = select(this.messages$, messages => + getRevertCreatedChangeIds(messages ?? []) + ); + // For usage in `combineLatest` we need `startWith` such that reload$ has an // initial value. readonly reload$: Observable<unknown> = fromEvent(document, 'reload').pipe(
diff --git a/polygerrit-ui/app/models/change/related-changes-model.ts b/polygerrit-ui/app/models/change/related-changes-model.ts index 02d9f88..901999c 100644 --- a/polygerrit-ui/app/models/change/related-changes-model.ts +++ b/polygerrit-ui/app/models/change/related-changes-model.ts
@@ -13,10 +13,11 @@ import {Model} from '../model'; import {define} from '../dependency'; import {ChangeModel} from './change-model'; -import {combineLatest, from, of} from 'rxjs'; -import {switchMap} from 'rxjs/operators'; +import {combineLatest, forkJoin, from, of} from 'rxjs'; +import {map, switchMap} from 'rxjs/operators'; import {ConfigModel} from '../config/config-model'; import {ChangeStatus} from '../../api/rest-api'; +import {isDefined} from '../../types/types'; export interface RelatedChangesState { /** `undefined` means "not yet loaded". */ @@ -25,6 +26,7 @@ cherryPicks?: ChangeInfo[]; conflictingChanges?: ChangeInfo[]; sameTopicChanges?: ChangeInfo[]; + revertingChanges: ChangeInfo[]; } const initialState: RelatedChangesState = { @@ -33,6 +35,7 @@ cherryPicks: undefined, conflictingChanges: undefined, sameTopicChanges: undefined, + revertingChanges: [], }; export const relatedChangesModelToken = define<RelatedChangesModel>( @@ -66,6 +69,32 @@ ); /** + * Emits all changes that have reverted the current change, based on + * information from parsed change messages. Abandoned changes are not + * included. + */ + public readonly revertingChanges$ = select( + this.state$, + state => state.revertingChanges + ); + + /** + * Emits one reverting change (if there is any) from revertingChanges$. + * It prefers MERGED changes. Otherwise the choice is random. + */ + public readonly revertingChange$ = select( + this.revertingChanges$, + revertingChanges => { + if (revertingChanges.length === 0) return undefined; + const submittedRevert = revertingChanges.find( + c => c.status === ChangeStatus.MERGED + ); + if (submittedRevert) return submittedRevert; + return revertingChanges[0]; + } + ); + + /** * Determines whether the change has a parent change. If there * is a relation chain, and the change id is not the last item of the * relation chain, then there is a parent. @@ -93,6 +122,7 @@ this.loadCherryPicks(), this.loadConflictingChanges(), this.loadSameTopicChanges(), + this.loadRevertingChanges(), ]; } @@ -197,4 +227,26 @@ this.updateState({sameTopicChanges}); }); } + + private loadRevertingChanges() { + return combineLatest([ + this.changeModel.reload$, + this.changeModel.revertingChangeIds$, + ]) + .pipe( + switchMap(([_, changeIds]) => { + if (!changeIds?.length) return of([]); + return forkJoin( + changeIds.map(changeId => + from(this.restApiService.getChange(changeId)) + ) + ); + }), + map(changes => changes.filter(isDefined)), + map(changes => changes.filter(c => c.status !== ChangeStatus.ABANDONED)) + ) + .subscribe(revertingChanges => { + this.updateState({revertingChanges}); + }); + } }
diff --git a/polygerrit-ui/app/models/change/related-changes-model_test.ts b/polygerrit-ui/app/models/change/related-changes-model_test.ts index 31c0026..295f284 100644 --- a/polygerrit-ui/app/models/change/related-changes-model_test.ts +++ b/polygerrit-ui/app/models/change/related-changes-model_test.ts
@@ -22,8 +22,10 @@ createRelatedChangesInfo, createRelatedChangeAndCommitInfo, createChange, + createChangeMessage, } from '../../test/test-data-generators'; -import {ChangeStatus, TopicName} from '../../api/rest-api'; +import {ChangeStatus, ReviewInputTag, TopicName} from '../../api/rest-api'; +import {MessageTag} from '../../constants/constants'; suite('related-changes-model tests', () => { let model: RelatedChangesModel; @@ -235,4 +237,50 @@ assert.isTrue(getChangesWithSameTopicStub.calledOnce); }); }); + + suite('loadRevertingChanges', async () => { + let getChangeStub: SinonStub; + + setup(() => { + getChangeStub = stubRestApi('getChange').callsFake(() => + Promise.resolve(createChange()) + ); + }); + + test('revertingChanges initially empty', async () => { + await waitUntilObserved( + model.revertingChanges$, + revertingChanges => revertingChanges.length === 0 + ); + assert.isFalse(getChangeStub.called); + }); + + test('revertingChanges empty when change does not contain a revert message', async () => { + changeModel.updateStateChange(createParsedChange()); + await waitUntilObserved( + model.revertingChanges$, + revertingChanges => revertingChanges.length === 0 + ); + assert.isFalse(getChangeStub.called); + }); + + test('revertingChanges emits', async () => { + changeModel.updateStateChange({ + ...createParsedChange(), + messages: [ + { + ...createChangeMessage(), + message: 'Created a revert of this change as 123', + tag: MessageTag.TAG_REVERT as ReviewInputTag, + }, + ], + }); + + await waitUntilObserved( + model.revertingChanges$, + revertingChanges => revertingChanges?.length === 1 + ); + assert.isTrue(getChangeStub.calledOnce); + }); + }); });