Skip to content

Commit 2b66742

Browse files
authored
Merge pull request #1811 from leggedrobotics/feat/1790_show_n_files_list_mission
feat: missions get command returns files count and files size
2 parents f5d73ca + 1928ecb commit 2b66742

File tree

5 files changed

+118
-20
lines changed

5 files changed

+118
-20
lines changed

backend/src/serialization.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,8 @@ export const missionEntityToDtoWithCreator = (
6262
export const missionEntityToFlatDto = (mission: Mission): FlatMissionDto => {
6363
return {
6464
...missionEntityToDtoWithCreator(mission),
65-
filesCount: mission.files?.length || 0,
66-
size:
67-
mission.files?.reduce(
68-
(accumulator, file) => accumulator + (file.size ?? 0),
69-
0,
70-
) || 0,
65+
filesCount: mission.fileCount ?? 0,
66+
size: mission.size ?? 0,
7167
};
7268
};
7369

backend/src/services/mission.service.ts

Lines changed: 87 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,12 @@ import {
3232
} from '../serialization';
3333
import { TagService } from './tag.service';
3434
import { UserService } from './user.service';
35-
import { addMissionFilters, addProjectFilters, addSort } from './utilities';
35+
import {
36+
addFileStats,
37+
addMissionFilters,
38+
addProjectFilters,
39+
addSort,
40+
} from './utilities';
3641

3742
import { SortOrder } from '@common/api/types/pagination';
3843

@@ -162,36 +167,104 @@ export class MissionService {
162167
take: number,
163168
userUuid: string,
164169
): Promise<MissionsDto> {
165-
let query = this.missionRepository
170+
let idQuery = this.missionRepository
166171
.createQueryBuilder('mission')
167-
.leftJoinAndSelect('mission.project', 'project')
168-
.leftJoinAndSelect('mission.creator', 'creator')
169-
.leftJoinAndSelect('mission.tags', 'tag')
170-
.leftJoinAndSelect('tag.tagType', 'tagType');
172+
.select('mission.uuid')
173+
.leftJoin('mission.project', 'project')
174+
.leftJoin('mission.creator', 'creator')
175+
.leftJoin('mission.tags', 'tag')
176+
.leftJoin('tag.tagType', 'tagType');
171177

172-
query = addAccessConstraintsToMissionQuery(query, userUuid);
178+
idQuery = addAccessConstraintsToMissionQuery(idQuery, userUuid);
173179

174-
query = addProjectFilters(
175-
query,
180+
idQuery = addProjectFilters(
181+
idQuery,
176182
this.projectRepository,
177183
projectUuids,
178184
projectPatterns,
179185
);
180186

181-
query = addMissionFilters(
182-
query,
187+
idQuery = addMissionFilters(
188+
idQuery,
183189
this.missionRepository,
184190
missionUuids,
185191
missionPatterns,
186192
missionMetadata,
187193
);
188194

189195
if (sortBy !== undefined) {
190-
query = addSort(query, FIND_MANY_SORT_KEYS, sortBy, sortOrder);
196+
idQuery = addSort(idQuery, FIND_MANY_SORT_KEYS, sortBy, sortOrder);
191197
}
192198

193-
query.take(take).skip(skip);
194-
const [missions, count] = await query.getManyAndCount();
199+
// Get distinct mission UUIDs
200+
idQuery.groupBy('mission.uuid');
201+
202+
// Get count before pagination
203+
const count = await idQuery.getCount();
204+
idQuery.take(take).skip(skip);
205+
206+
const missionIds = await idQuery.getRawMany();
207+
208+
if (missionIds.length === 0) {
209+
return {
210+
data: [],
211+
count,
212+
skip,
213+
take,
214+
};
215+
}
216+
217+
let dataQuery = this.missionRepository
218+
.createQueryBuilder('mission')
219+
.leftJoinAndSelect('mission.project', 'project')
220+
.leftJoinAndSelect('mission.creator', 'creator')
221+
.leftJoinAndSelect('mission.tags', 'tag')
222+
.leftJoinAndSelect('tag.tagType', 'tagType')
223+
.where('mission.uuid IN (:...missionIds)', {
224+
missionIds: missionIds.map((m) => m.mission_uuid),
225+
});
226+
227+
if (sortBy !== undefined) {
228+
dataQuery = addSort(
229+
dataQuery,
230+
FIND_MANY_SORT_KEYS,
231+
sortBy,
232+
sortOrder,
233+
);
234+
}
235+
236+
dataQuery = addFileStats(dataQuery);
237+
238+
const result = await dataQuery.getRawAndEntities();
239+
const missions = result.entities;
240+
const rawResults = result.raw;
241+
242+
// Create a map for quick lookup of file stats by mission UUID
243+
const statsMap = new Map<
244+
string,
245+
{ fileCount: number; fileSize: number }
246+
>();
247+
for (const raw of rawResults) {
248+
const missionUuid = raw.mission_uuid;
249+
if (!statsMap.has(missionUuid)) {
250+
statsMap.set(missionUuid, {
251+
fileCount: Number.parseInt(raw.fileCount) || 0,
252+
fileSize: Number.parseInt(raw.fileSize) || 0,
253+
});
254+
}
255+
}
256+
257+
// Assign file stats to missions
258+
for (const mission of missions) {
259+
const stats = statsMap.get(mission.uuid);
260+
if (stats) {
261+
mission.fileCount = stats.fileCount;
262+
mission.size = stats.fileSize;
263+
} else {
264+
mission.fileCount = 0;
265+
mission.size = 0;
266+
}
267+
}
195268

196269
return {
197270
data: missions.map((element) => missionEntityToFlatDto(element)),

backend/src/services/utilities.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,26 @@ export const addMissionCount = (
204204
return query;
205205
};
206206

207+
export const addFileStats = (
208+
query: SelectQueryBuilder<Mission>,
209+
): SelectQueryBuilder<Mission> => {
210+
query
211+
.addSelect((subQuery) => {
212+
return subQuery
213+
.select('COUNT(file.uuid)', 'count')
214+
.from('file_entity', 'file')
215+
.where('file."missionUuid" = mission.uuid');
216+
}, 'fileCount')
217+
.addSelect((subQuery) => {
218+
return subQuery
219+
.select('COALESCE(SUM(file.size), 0)', 'sum')
220+
.from('file_entity', 'file')
221+
.where('file."missionUuid" = mission.uuid');
222+
}, 'fileSize');
223+
224+
return query;
225+
};
226+
207227
export const addProjectCreatorFilter = (
208228
query: SelectQueryBuilder<any>,
209229
creatorUuid: string | undefined,

cli/kleinkram/api/deser.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ class MissionObjectKeys(str, Enum):
5454
CREATED_AT = "createdAt"
5555
UPDATED_AT = "updatedAt"
5656
TAGS = "tags"
57+
FILESIZE = "size"
58+
FILECOUNT = "filesCount"
5759

5860

5961
class ProjectObjectKeys(str, Enum):
@@ -135,6 +137,8 @@ def _parse_mission(mission: MissionObject) -> Mission:
135137
created_at = _parse_datetime(mission[MissionObjectKeys.CREATED_AT])
136138
updated_at = _parse_datetime(mission[MissionObjectKeys.UPDATED_AT])
137139
metadata = _parse_metadata(mission[MissionObjectKeys.TAGS])
140+
file_count = mission[MissionObjectKeys.FILECOUNT]
141+
filesize = mission[MissionObjectKeys.FILESIZE]
138142

139143
project_id, project_name = _get_nested_info(mission, PROJECT)
140144

@@ -146,6 +150,8 @@ def _parse_mission(mission: MissionObject) -> Mission:
146150
metadata=metadata,
147151
project_id=project_id,
148152
project_name=project_name,
153+
number_of_files=file_count,
154+
size=filesize,
149155
)
150156
except Exception as e:
151157
raise ParsingError(f"error parsing mission: {mission}") from e

common/entities/mission/mission.entity.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,7 @@ export default class Mission extends BaseEntity {
4040

4141
@OneToMany(() => Tag, (tag) => tag.mission)
4242
tags?: Tag[];
43+
44+
fileCount?: number;
45+
size?: number;
4346
}

0 commit comments

Comments
 (0)