Merge "Align text and radio button in cherry pick dialog"
diff --git a/polygerrit-ui/app/elements/checks/gr-checks-tab.ts b/polygerrit-ui/app/elements/checks/gr-checks-tab.ts
index e5386f4..9376a03 100644
--- a/polygerrit-ui/app/elements/checks/gr-checks-tab.ts
+++ b/polygerrit-ui/app/elements/checks/gr-checks-tab.ts
@@ -28,18 +28,23 @@
allActions$,
allResults$,
allRuns$,
+ checksPatchsetNumber$,
someProvidersAreLoading$,
} from '../../services/checks/checks-model';
import './gr-checks-runs';
import './gr-checks-results';
import {sharedStyles} from '../../styles/shared-styles';
-import {changeNum$, currentPatchNum$} from '../../services/change/change-model';
-import {NumericChangeId, PatchSetNum} from '../../types/common';
+import {changeNum$, latestPatchNum$} from '../../services/change/change-model';
+import {NumericChangeId, PatchSetNumber} from '../../types/common';
import {
ActionTriggeredEvent,
fireActionTriggered,
} from '../../services/checks/checks-util';
-import {checkRequiredProperty} from '../../utils/common-util';
+import {
+ assertIsDefined,
+ check,
+ checkRequiredProperty,
+} from '../../utils/common-util';
import {RunSelectedEvent} from './gr-checks-runs';
import {ChecksTabState} from '../../types/events';
import {fireAlert} from '../../utils/event-util';
@@ -64,7 +69,10 @@
tabState?: ChecksTabState;
@property()
- currentPatchNum: PatchSetNum | undefined = undefined;
+ checksPatchsetNumber: PatchSetNumber | undefined = undefined;
+
+ @property()
+ latestPatchsetNumber: PatchSetNumber | undefined = undefined;
@property()
changeNum: NumericChangeId | undefined = undefined;
@@ -82,7 +90,8 @@
this.subscribe('runs', allRuns$);
this.subscribe('actions', allActions$);
this.subscribe('results', allResults$);
- this.subscribe('currentPatchNum', currentPatchNum$);
+ this.subscribe('checksPatchsetNumber', checksPatchsetNumber$);
+ this.subscribe('latestPatchsetNumber', latestPatchNum$);
this.subscribe('changeNum', changeNum$);
this.subscribe('someProvidersAreLoading', someProvidersAreLoading$);
@@ -124,7 +133,6 @@
}
render() {
- const ps = `Patchset ${this.currentPatchNum} (Latest)`;
const filteredRuns = this.runs.filter(
r =>
this.selectedRuns.length === 0 ||
@@ -134,13 +142,9 @@
<div class="header">
<div class="left">
<gr-dropdown-list
- value="${ps}"
- .items="${[
- {
- value: `${ps}`,
- text: `${ps}`,
- },
- ]}"
+ value="${this.checksPatchsetNumber}"
+ .items="${this.createPatchsetDropdownItems()}"
+ @value-change="${this.onPatchsetSelected}"
></gr-dropdown-list>
<span ?hidden="${!this.someProvidersAreLoading}">Loading...</span>
</div>
@@ -163,6 +167,24 @@
`;
}
+ private onPatchsetSelected(e: CustomEvent<{value: string}>) {
+ const patchset = Number(e.detail.value);
+ check(!isNaN(patchset), 'selected patchset must be a number');
+ this.checksService.setPatchset(patchset as PatchSetNumber);
+ }
+
+ private createPatchsetDropdownItems() {
+ return Array.from(Array(this.latestPatchsetNumber), (_, i) => {
+ assertIsDefined(this.latestPatchsetNumber, 'latestPatchsetNumber');
+ const index = this.latestPatchsetNumber - i;
+ const postfix = index === this.latestPatchsetNumber ? ' (latest)' : '';
+ return {
+ value: `${index}`,
+ text: `Patchset ${index}${postfix}`,
+ };
+ });
+ }
+
protected updated(changedProperties: PropertyValues) {
super.updated(changedProperties);
if (changedProperties.has('tabState')) {
@@ -181,10 +203,10 @@
handleActionTriggered(action: Action, run?: CheckRun) {
if (!this.changeNum) return;
- if (!this.currentPatchNum) return;
+ if (!this.checksPatchsetNumber) return;
const promise = action.callback(
this.changeNum,
- this.currentPatchNum as number,
+ this.checksPatchsetNumber,
run?.attempt,
run?.externalId,
run?.checkName,
diff --git a/polygerrit-ui/app/services/change/change-model.ts b/polygerrit-ui/app/services/change/change-model.ts
index b2bdcfe..e7472de 100644
--- a/polygerrit-ui/app/services/change/change-model.ts
+++ b/polygerrit-ui/app/services/change/change-model.ts
@@ -46,10 +46,18 @@
// Must only be used by the change service or whatever is in control of this
// model.
export function updateState(change?: ParsedChangeInfo) {
- privateState$.next({
- ...privateState$.getValue(),
- change,
- });
+ const current = privateState$.getValue();
+ // We want to make it easy for subscribers to react to change changes, so we
+ // are explicitly emitting and additional `undefined` when the change number
+ // changes. So if you are subscribed to the latestPatchsetNumber for example,
+ // then you can rely on emissions even if the old and the new change have the
+ // same latestPatchsetNumber.
+ if (change !== undefined && current.change !== undefined) {
+ if (change._number !== current.change._number) {
+ privateState$.next({...current, change: undefined});
+ }
+ }
+ privateState$.next({...current, change});
}
/**
@@ -91,9 +99,6 @@
*
* Note that this selector can emit a patchNum without the change being
* available!
- *
- * TODO: It would be good to assert/enforce somehow that currentPatchNum$ cannot
- * emit 'PARENT'.
*/
export const currentPatchNum$: Observable<
PatchSetNum | undefined
diff --git a/polygerrit-ui/app/services/change/change-service.ts b/polygerrit-ui/app/services/change/change-service.ts
index 4524813..c292fb5 100644
--- a/polygerrit-ui/app/services/change/change-service.ts
+++ b/polygerrit-ui/app/services/change/change-service.ts
@@ -16,21 +16,16 @@
*/
import {routerChangeNum$} from '../router/router-model';
import {updateState} from './change-model';
-import {tap} from 'rxjs/operators';
import {ParsedChangeInfo} from '../../types/types';
export class ChangeService {
- // TODO: In the future we will want to make restApiService.getChangeDetail()
- // calls from a switchMap() here. For now just make sure to invalidate the
- // change when no changeNum is set.
- private routerChangeNumEffect = routerChangeNum$.pipe(
- tap(changeNum => {
- if (!changeNum) updateState(undefined);
- })
- );
-
constructor() {
- this.routerChangeNumEffect.subscribe();
+ // TODO: In the future we will want to make restApiService.getChangeDetail()
+ // calls from a switchMap() here. For now just make sure to invalidate the
+ // change when no changeNum is set.
+ routerChangeNum$.subscribe(changeNum => {
+ if (!changeNum) updateState(undefined);
+ });
}
/**
diff --git a/polygerrit-ui/app/services/checks/checks-model.ts b/polygerrit-ui/app/services/checks/checks-model.ts
index d8dc688..1c5b862 100644
--- a/polygerrit-ui/app/services/checks/checks-model.ts
+++ b/polygerrit-ui/app/services/checks/checks-model.ts
@@ -26,6 +26,7 @@
RunStatus,
} from '../../api/checks';
import {distinctUntilChanged, map} from 'rxjs/operators';
+import {PatchSetNumber} from '../../types/common';
// This is a convenience type for working with results, because when working
// with a bunch of results you will typically also want to know about the run
@@ -41,29 +42,44 @@
}
interface ChecksState {
- [name: string]: ChecksProviderState;
+ patchsetNumber?: PatchSetNumber;
+ providerNameToState: {
+ [name: string]: ChecksProviderState;
+ };
}
-const initialState: ChecksState = {};
+const initialState: ChecksState = {
+ providerNameToState: {},
+};
const privateState$ = new BehaviorSubject(initialState);
// Re-exporting as Observable so that you can only subscribe, but not emit.
export const checksState$: Observable<ChecksState> = privateState$;
-export const aPluginHasRegistered$ = checksState$.pipe(
+export const checksPatchsetNumber$ = checksState$.pipe(
+ map(state => state.patchsetNumber),
+ distinctUntilChanged()
+);
+
+export const checksProviderState$ = checksState$.pipe(
+ map(state => state.providerNameToState),
+ distinctUntilChanged()
+);
+
+export const aPluginHasRegistered$ = checksProviderState$.pipe(
map(state => Object.keys(state).length > 0),
distinctUntilChanged()
);
-export const someProvidersAreLoading$ = checksState$.pipe(
+export const someProvidersAreLoading$ = checksProviderState$.pipe(
map(state => {
return Object.values(state).some(providerState => providerState.loading);
}),
distinctUntilChanged()
);
-export const allActions$ = checksState$.pipe(
+export const allActions$ = checksProviderState$.pipe(
map(state => {
return Object.values(state).reduce(
(allActions: Action[], providerState: ChecksProviderState) => [
@@ -75,7 +91,7 @@
})
);
-export const allRuns$ = checksState$.pipe(
+export const allRuns$ = checksProviderState$.pipe(
map(state => {
return Object.values(state).reduce(
(allRuns: CheckRun[], providerState: ChecksProviderState) => [
@@ -87,7 +103,7 @@
})
);
-export const checkToPluginMap$ = checksState$.pipe(
+export const checkToPluginMap$ = checksProviderState$.pipe(
map(state => {
const map = new Map<string, string>();
for (const [pluginName, providerState] of Object.entries(state)) {
@@ -99,7 +115,7 @@
})
);
-export const allResults$ = checksState$.pipe(
+export const allResults$ = checksProviderState$.pipe(
map(state => {
return Object.values(state)
.reduce(
@@ -124,7 +140,8 @@
config?: ChecksApiConfig
) {
const nextState = {...privateState$.getValue()};
- nextState[pluginName] = {
+ nextState.providerNameToState = {...nextState.providerNameToState};
+ nextState.providerNameToState[pluginName] = {
pluginName,
loading: false,
config,
@@ -200,8 +217,9 @@
export function updateStateSetLoading(pluginName: string) {
const nextState = {...privateState$.getValue()};
- nextState[pluginName] = {
- ...nextState[pluginName],
+ nextState.providerNameToState = {...nextState.providerNameToState};
+ nextState.providerNameToState[pluginName] = {
+ ...nextState.providerNameToState[pluginName],
loading: true,
};
privateState$.next(nextState);
@@ -213,11 +231,18 @@
actions: Action[] = []
) {
const nextState = {...privateState$.getValue()};
- nextState[pluginName] = {
- ...nextState[pluginName],
+ nextState.providerNameToState = {...nextState.providerNameToState};
+ nextState.providerNameToState[pluginName] = {
+ ...nextState.providerNameToState[pluginName],
loading: false,
runs: [...runs],
actions: [...actions],
};
privateState$.next(nextState);
}
+
+export function updateStateSetPatchset(patchsetNumber?: PatchSetNumber) {
+ const nextState = {...privateState$.getValue()};
+ nextState.patchsetNumber = patchsetNumber;
+ privateState$.next(nextState);
+}
diff --git a/polygerrit-ui/app/services/checks/checks-service.ts b/polygerrit-ui/app/services/checks/checks-service.ts
index d2a4775..3ce714a 100644
--- a/polygerrit-ui/app/services/checks/checks-service.ts
+++ b/polygerrit-ui/app/services/checks/checks-service.ts
@@ -18,7 +18,6 @@
import {
switchMap,
takeWhile,
- tap,
throttleTime,
withLatestFrom,
} from 'rxjs/operators';
@@ -29,12 +28,14 @@
FetchResponse,
ResponseCode,
} from '../../api/checks';
-import {change$, currentPatchNum$} from '../change/change-model';
+import {change$, changeNum$, latestPatchNum$} from '../change/change-model';
import {
updateStateSetLoading,
checkToPluginMap$,
updateStateSetProvider,
updateStateSetResults,
+ checksPatchsetNumber$,
+ updateStateSetPatchset,
} from './checks-model';
import {
BehaviorSubject,
@@ -44,18 +45,26 @@
of,
Subject,
} from 'rxjs';
+import {PatchSetNumber} from '../../types/common';
export class ChecksService {
private readonly providers: {[name: string]: ChecksProvider} = {};
private readonly reloadSubjects: {[name: string]: Subject<void>} = {};
- private changeAndPatchNum$ = change$.pipe(withLatestFrom(currentPatchNum$));
-
private checkToPluginMap = new Map<string, string>();
constructor() {
- checkToPluginMap$.subscribe(x => (this.checkToPluginMap = x));
+ checkToPluginMap$.subscribe(map => {
+ this.checkToPluginMap = map;
+ });
+ latestPatchNum$.subscribe(num => {
+ updateStateSetPatchset(num);
+ });
+ }
+
+ setPatchset(num: PatchSetNumber) {
+ updateStateSetPatchset(num);
}
reload(pluginName: string) {
@@ -82,37 +91,43 @@
updateStateSetProvider(pluginName, config);
// Both, changed numbers and and announceUpdate request should trigger.
combineLatest([
- this.changeAndPatchNum$,
+ changeNum$,
+ checksPatchsetNumber$,
this.reloadSubjects[pluginName].pipe(throttleTime(1000)),
])
.pipe(
takeWhile(_ => !!this.providers[pluginName]),
+ withLatestFrom(change$),
switchMap(
- ([[change, patchNum], _]): Observable<FetchResponse> => {
- if (!change || !patchNum || typeof patchNum !== 'number') {
+ ([[changeNum, patchNum, _], change]): Observable<FetchResponse> => {
+ if (
+ !change ||
+ !changeNum ||
+ !patchNum ||
+ typeof patchNum !== 'number'
+ ) {
return of({
responseCode: ResponseCode.OK,
runs: [],
});
}
const data: ChangeData = {
- changeNumber: change._number,
+ changeNumber: changeNum,
patchsetNumber: patchNum,
repo: change.project,
};
updateStateSetLoading(pluginName);
return from(this.providers[pluginName].fetch(data));
}
- ),
- tap(response => {
- updateStateSetResults(
- pluginName,
- response.runs ?? [],
- response.actions
- );
- })
+ )
)
- .subscribe(() => {});
+ .subscribe(response => {
+ updateStateSetResults(
+ pluginName,
+ response.runs ?? [],
+ response.actions
+ );
+ });
this.reload(pluginName);
}
}
diff --git a/polygerrit-ui/app/types/common.ts b/polygerrit-ui/app/types/common.ts
index 711b11b..1bfabf8 100644
--- a/polygerrit-ui/app/types/common.ts
+++ b/polygerrit-ui/app/types/common.ts
@@ -76,6 +76,7 @@
export type ParsedJSON = BrandType<unknown, '_parsedJSON'>;
export type PatchSetNum = BrandType<'PARENT' | 'edit' | number, '_patchSet'>;
+export type PatchSetNumber = BrandType<number, '_patchSet'>;
export const EditPatchSetNum = 'edit' as PatchSetNum;
// TODO(TS): This is not correct, it is better to have a separate ApiPatchSetNum
diff --git a/polygerrit-ui/app/utils/patch-set-util.ts b/polygerrit-ui/app/utils/patch-set-util.ts
index 40e3eef..af56798 100644
--- a/polygerrit-ui/app/utils/patch-set-util.ts
+++ b/polygerrit-ui/app/utils/patch-set-util.ts
@@ -3,11 +3,12 @@
ChangeInfo,
PatchSetNum,
EditPatchSetNum,
- BrandType,
ParentPatchSetNum,
+ PatchSetNumber,
} from '../types/common';
import {RestApiService} from '../services/gr-rest-api/gr-rest-api';
import {EditRevisionInfo, ParsedChangeInfo} from '../types/types';
+import {check} from './common-util';
/**
* @license
@@ -82,9 +83,7 @@
return patchset as PatchSetNum;
}
-export function isNumber(
- psn: PatchSetNum
-): psn is BrandType<number, '_patchSet'> {
+export function isNumber(psn: PatchSetNum): psn is PatchSetNumber {
return typeof psn === 'number';
}
@@ -250,14 +249,16 @@
export function computeLatestPatchNum(
allPatchSets?: PatchSet[]
-): PatchSetNum | undefined {
+): PatchSetNumber | undefined {
if (!allPatchSets || !allPatchSets.length) {
return undefined;
}
- if (allPatchSets[0].num === EditPatchSetNum) {
- return allPatchSets[1].num;
+ let latest = allPatchSets[0].num;
+ if (latest === EditPatchSetNum) {
+ latest = allPatchSets[1].num;
}
- return allPatchSets[0].num;
+ check(isNumber(latest), 'Latest patchset cannot be EDIT or PARENT.');
+ return latest;
}
export function computePredecessor(