Ben Rohlfs | 5ab68c4 | 2022-05-30 13:05:58 +0200 | [diff] [blame] | 1 | /** |
| 2 | * @license |
| 3 | * Copyright 2022 Google LLC |
| 4 | * SPDX-License-Identifier: Apache-2.0 |
| 5 | */ |
Ben Rohlfs | b0f90c2 | 2022-06-20 08:55:57 +0200 | [diff] [blame] | 6 | import { |
| 7 | BasePatchSetNum, |
| 8 | FileInfo, |
| 9 | FileNameToFileInfoMap, |
| 10 | PARENT, |
| 11 | PatchRange, |
| 12 | PatchSetNumber, |
| 13 | RevisionPatchSetNum, |
| 14 | } from '../../types/common'; |
Ben Rohlfs | 3a03ddb | 2022-10-13 12:12:52 +0200 | [diff] [blame] | 15 | import {combineLatest, of, from} from 'rxjs'; |
Ben Rohlfs | 5ab68c4 | 2022-05-30 13:05:58 +0200 | [diff] [blame] | 16 | import {switchMap, map} from 'rxjs/operators'; |
| 17 | import {RestApiService} from '../../services/gr-rest-api/gr-rest-api'; |
Ben Rohlfs | 5ab68c4 | 2022-05-30 13:05:58 +0200 | [diff] [blame] | 18 | import {select} from '../../utils/observable-util'; |
Ben Rohlfs | b7ef7f7 | 2022-06-10 12:17:28 +0200 | [diff] [blame] | 19 | import {FileInfoStatus, SpecialFilePath} from '../../constants/constants'; |
Ben Rohlfs | 5ab68c4 | 2022-05-30 13:05:58 +0200 | [diff] [blame] | 20 | import {specialFilePathCompare} from '../../utils/path-list-util'; |
Dhruv Srivastava | b26f09b | 2023-09-28 10:04:19 +0200 | [diff] [blame] | 21 | import {Model} from '../base/model'; |
Ben Rohlfs | 5ab68c4 | 2022-05-30 13:05:58 +0200 | [diff] [blame] | 22 | import {define} from '../dependency'; |
| 23 | import {ChangeModel} from './change-model'; |
Ben Rohlfs | b7ef7f7 | 2022-06-10 12:17:28 +0200 | [diff] [blame] | 24 | import {CommentsModel} from '../comments/comments-model'; |
Ben Rohlfs | fef9444 | 2023-04-20 13:02:33 +0200 | [diff] [blame] | 25 | import {Timing} from '../../constants/reporting'; |
| 26 | import {ReportingService} from '../../services/gr-reporting/gr-reporting'; |
Ben Rohlfs | 5ab68c4 | 2022-05-30 13:05:58 +0200 | [diff] [blame] | 27 | |
Ben Rohlfs | 425a0e9 | 2022-12-20 21:36:19 +0100 | [diff] [blame] | 28 | export type FileNameToNormalizedFileInfoMap = { |
| 29 | [name: string]: NormalizedFileInfo; |
| 30 | }; |
Ben Rohlfs | 5ab68c4 | 2022-05-30 13:05:58 +0200 | [diff] [blame] | 31 | export interface NormalizedFileInfo extends FileInfo { |
| 32 | __path: string; |
Ben Rohlfs | b7ef7f7 | 2022-06-10 12:17:28 +0200 | [diff] [blame] | 33 | // Compared to `FileInfo` these four props are required here. |
| 34 | lines_inserted: number; |
| 35 | lines_deleted: number; |
| 36 | size_delta: number; // in bytes |
| 37 | size: number; // in bytes |
| 38 | } |
| 39 | |
| 40 | export function normalize(file: FileInfo, path: string): NormalizedFileInfo { |
| 41 | return { |
| 42 | __path: path, |
| 43 | // These 4 props are required in NormalizedFileInfo, but optional in |
| 44 | // FileInfo. So let's set a default value, if not already set. |
| 45 | lines_inserted: 0, |
| 46 | lines_deleted: 0, |
| 47 | size_delta: 0, |
| 48 | size: 0, |
| 49 | ...file, |
| 50 | }; |
Ben Rohlfs | 5ab68c4 | 2022-05-30 13:05:58 +0200 | [diff] [blame] | 51 | } |
| 52 | |
| 53 | function mapToList(map?: FileNameToFileInfoMap): NormalizedFileInfo[] { |
| 54 | const list: NormalizedFileInfo[] = []; |
| 55 | for (const [key, value] of Object.entries(map ?? {})) { |
Ben Rohlfs | b7ef7f7 | 2022-06-10 12:17:28 +0200 | [diff] [blame] | 56 | list.push(normalize(value, key)); |
Ben Rohlfs | 5ab68c4 | 2022-05-30 13:05:58 +0200 | [diff] [blame] | 57 | } |
| 58 | list.sort((f1, f2) => specialFilePathCompare(f1.__path, f2.__path)); |
| 59 | return list; |
| 60 | } |
| 61 | |
Ben Rohlfs | b7ef7f7 | 2022-06-10 12:17:28 +0200 | [diff] [blame] | 62 | export function addUnmodified( |
| 63 | files: NormalizedFileInfo[], |
| 64 | commentedPaths: string[] |
| 65 | ) { |
| 66 | const combined = [...files]; |
| 67 | for (const commentedPath of commentedPaths) { |
| 68 | if (commentedPath === SpecialFilePath.PATCHSET_LEVEL_COMMENTS) continue; |
| 69 | if (files.some(f => f.__path === commentedPath)) continue; |
| 70 | if ( |
| 71 | files.some( |
| 72 | f => f.status === FileInfoStatus.RENAMED && f.old_path === commentedPath |
| 73 | ) |
| 74 | ) { |
| 75 | continue; |
| 76 | } |
| 77 | combined.push( |
| 78 | normalize({status: FileInfoStatus.UNMODIFIED}, commentedPath) |
| 79 | ); |
| 80 | } |
| 81 | combined.sort((f1, f2) => specialFilePathCompare(f1.__path, f2.__path)); |
| 82 | return combined; |
| 83 | } |
| 84 | |
Ben Rohlfs | 5ab68c4 | 2022-05-30 13:05:58 +0200 | [diff] [blame] | 85 | export interface FilesState { |
| 86 | // TODO: Move reviewed files from change model into here. Change model is |
| 87 | // already large and complex, so the files model is a better fit. |
| 88 | |
Ben Rohlfs | b0f90c2 | 2022-06-20 08:55:57 +0200 | [diff] [blame] | 89 | /** |
| 90 | * Basic file and diff information of all files for the currently chosen |
| 91 | * patch range. |
| 92 | */ |
Ben Rohlfs | 5ab68c4 | 2022-05-30 13:05:58 +0200 | [diff] [blame] | 93 | files: NormalizedFileInfo[]; |
Ben Rohlfs | b0f90c2 | 2022-06-20 08:55:57 +0200 | [diff] [blame] | 94 | |
| 95 | /** |
| 96 | * Basic file and diff information of all files for the left chosen patchset |
| 97 | * compared against its base (aka parent). |
| 98 | * |
| 99 | * Empty if the left chosen patchset is PARENT. |
| 100 | */ |
| 101 | filesLeftBase: NormalizedFileInfo[]; |
| 102 | |
| 103 | /** |
| 104 | * Basic file and diff information of all files for the right chosen patchset |
| 105 | * compared against its base (aka parent). |
| 106 | * |
| 107 | * Empty if the left chosen patchset is PARENT. |
| 108 | */ |
| 109 | filesRightBase: NormalizedFileInfo[]; |
Ben Rohlfs | 5ab68c4 | 2022-05-30 13:05:58 +0200 | [diff] [blame] | 110 | } |
| 111 | |
| 112 | const initialState: FilesState = { |
| 113 | files: [], |
Ben Rohlfs | b0f90c2 | 2022-06-20 08:55:57 +0200 | [diff] [blame] | 114 | filesLeftBase: [], |
| 115 | filesRightBase: [], |
Ben Rohlfs | 5ab68c4 | 2022-05-30 13:05:58 +0200 | [diff] [blame] | 116 | }; |
| 117 | |
| 118 | export const filesModelToken = define<FilesModel>('files-model'); |
| 119 | |
Chris Poucet | 8c61078 | 2022-10-24 12:45:46 +0200 | [diff] [blame] | 120 | export class FilesModel extends Model<FilesState> { |
Ben Rohlfs | 5ab68c4 | 2022-05-30 13:05:58 +0200 | [diff] [blame] | 121 | public readonly files$ = select(this.state$, state => state.files); |
| 122 | |
Ben Rohlfs | 425a0e9 | 2022-12-20 21:36:19 +0100 | [diff] [blame] | 123 | /** |
| 124 | * `files$` only includes the files that were modified. Here we also include |
| 125 | * all unmodified files that have comments with |
| 126 | * `status: FileInfoStatus.UNMODIFIED`. |
| 127 | */ |
| 128 | public readonly filesIncludingUnmodified$ = select( |
Ben Rohlfs | b7ef7f7 | 2022-06-10 12:17:28 +0200 | [diff] [blame] | 129 | combineLatest([this.files$, this.commentsModel.commentedPaths$]), |
| 130 | ([files, commentedPaths]) => addUnmodified(files, commentedPaths) |
| 131 | ); |
| 132 | |
Ben Rohlfs | ea6c9fc | 2022-06-20 14:55:35 +0200 | [diff] [blame] | 133 | public readonly filesLeftBase$ = select( |
| 134 | this.state$, |
| 135 | state => state.filesLeftBase |
| 136 | ); |
| 137 | |
| 138 | public readonly filesRightBase$ = select( |
| 139 | this.state$, |
| 140 | state => state.filesRightBase |
| 141 | ); |
| 142 | |
Ben Rohlfs | 5ab68c4 | 2022-05-30 13:05:58 +0200 | [diff] [blame] | 143 | constructor( |
| 144 | readonly changeModel: ChangeModel, |
Ben Rohlfs | b7ef7f7 | 2022-06-10 12:17:28 +0200 | [diff] [blame] | 145 | readonly commentsModel: CommentsModel, |
Ben Rohlfs | fef9444 | 2023-04-20 13:02:33 +0200 | [diff] [blame] | 146 | readonly restApiService: RestApiService, |
| 147 | private readonly reporting: ReportingService |
Ben Rohlfs | 5ab68c4 | 2022-05-30 13:05:58 +0200 | [diff] [blame] | 148 | ) { |
| 149 | super(initialState); |
| 150 | this.subscriptions = [ |
Ben Rohlfs | fef9444 | 2023-04-20 13:02:33 +0200 | [diff] [blame] | 151 | this.reportChangeDataStart(), |
| 152 | this.reportChangeDataEnd(), |
Ben Rohlfs | b0f90c2 | 2022-06-20 08:55:57 +0200 | [diff] [blame] | 153 | this.subscribeToFiles( |
| 154 | (psLeft, psRight) => { |
| 155 | return {basePatchNum: psLeft, patchNum: psRight}; |
| 156 | }, |
| 157 | files => { |
| 158 | return {files: [...files]}; |
| 159 | } |
| 160 | ), |
| 161 | this.subscribeToFiles( |
| 162 | (psLeft, _) => { |
Frank Borden | 1c3479b | 2023-03-29 15:59:46 +0200 | [diff] [blame] | 163 | if (psLeft === PARENT || (psLeft as PatchSetNumber) <= 0) |
| 164 | return undefined; |
Ben Rohlfs | b0f90c2 | 2022-06-20 08:55:57 +0200 | [diff] [blame] | 165 | return {basePatchNum: PARENT, patchNum: psLeft as PatchSetNumber}; |
| 166 | }, |
| 167 | files => { |
| 168 | return {filesLeftBase: [...files]}; |
| 169 | } |
| 170 | ), |
| 171 | this.subscribeToFiles( |
| 172 | (psLeft, psRight) => { |
Frank Borden | 1c3479b | 2023-03-29 15:59:46 +0200 | [diff] [blame] | 173 | if (psLeft === PARENT || (psLeft as PatchSetNumber) <= 0) |
| 174 | return undefined; |
Ben Rohlfs | b0f90c2 | 2022-06-20 08:55:57 +0200 | [diff] [blame] | 175 | return {basePatchNum: PARENT, patchNum: psRight as PatchSetNumber}; |
| 176 | }, |
| 177 | files => { |
| 178 | return {filesRightBase: [...files]}; |
| 179 | } |
| 180 | ), |
Ben Rohlfs | 5ab68c4 | 2022-05-30 13:05:58 +0200 | [diff] [blame] | 181 | ]; |
| 182 | } |
| 183 | |
Ben Rohlfs | fef9444 | 2023-04-20 13:02:33 +0200 | [diff] [blame] | 184 | private reportChangeDataStart() { |
| 185 | return combineLatest([this.changeModel.loading$]).subscribe( |
| 186 | ([changeLoading]) => { |
| 187 | if (changeLoading) { |
| 188 | this.reporting.time(Timing.CHANGE_DATA); |
| 189 | } |
| 190 | } |
| 191 | ); |
| 192 | } |
| 193 | |
| 194 | private reportChangeDataEnd() { |
| 195 | return combineLatest([this.changeModel.loading$, this.files$]).subscribe( |
| 196 | ([changeLoading, files]) => { |
| 197 | if (!changeLoading && files.length > 0) { |
| 198 | this.reporting.timeEnd(Timing.CHANGE_DATA); |
| 199 | } |
| 200 | } |
| 201 | ); |
| 202 | } |
| 203 | |
Ben Rohlfs | b0f90c2 | 2022-06-20 08:55:57 +0200 | [diff] [blame] | 204 | private subscribeToFiles( |
| 205 | rangeChooser: ( |
| 206 | basePatchNum: BasePatchSetNum, |
| 207 | patchNum: RevisionPatchSetNum |
| 208 | ) => PatchRange | undefined, |
| 209 | filesToState: (files: NormalizedFileInfo[]) => Partial<FilesState> |
| 210 | ) { |
| 211 | return combineLatest([ |
Ben Rohlfs | b0f90c2 | 2022-06-20 08:55:57 +0200 | [diff] [blame] | 212 | this.changeModel.changeNum$, |
| 213 | this.changeModel.basePatchNum$, |
| 214 | this.changeModel.patchNum$, |
| 215 | ]) |
| 216 | .pipe( |
Ben Rohlfs | d49e833 | 2023-04-20 22:40:04 +0200 | [diff] [blame] | 217 | switchMap(([changeNum, basePatchNum, patchNum]) => { |
Ben Rohlfs | b0f90c2 | 2022-06-20 08:55:57 +0200 | [diff] [blame] | 218 | if (!changeNum || !patchNum) return of({}); |
| 219 | const range = rangeChooser(basePatchNum, patchNum); |
| 220 | if (!range) return of({}); |
| 221 | return from( |
| 222 | this.restApiService.getChangeOrEditFiles(changeNum, range) |
| 223 | ); |
| 224 | }), |
| 225 | map(mapToList), |
| 226 | map(filesToState) |
| 227 | ) |
| 228 | .subscribe(state => { |
Ben Rohlfs | 5fa8293 | 2022-09-21 09:29:31 +0200 | [diff] [blame] | 229 | this.updateState(state); |
Ben Rohlfs | b0f90c2 | 2022-06-20 08:55:57 +0200 | [diff] [blame] | 230 | }); |
| 231 | } |
Ben Rohlfs | 5ab68c4 | 2022-05-30 13:05:58 +0200 | [diff] [blame] | 232 | } |