Skip to content

Commit 48f74eb

Browse files
added endpoint for application dashboard data
1 parent 86173bc commit 48f74eb

File tree

3 files changed

+119
-35
lines changed

3 files changed

+119
-35
lines changed

src/controllers/admin-controller/application.controller.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import { ComposeAuthGuard } from "@auth/compose-auth.guard";
3030
import { ApplicationAdmin, Read } from "@auth/roles.decorator";
3131
import { RolesGuard } from "@auth/roles.guard";
3232
import { ApiAuth } from "@auth/swagger-auth-decorator";
33+
import { ApplicationDashboardResponseDto } from "@dto/applications-dashboard-responses";
3334
import { CreateApplicationDto } from "@dto/create-application.dto";
3435
import { DeleteResponseDto } from "@dto/delete-application-response.dto";
3536
import { ListAllApplicationsResponseDto } from "@dto/list-all-applications-response.dto";
@@ -100,6 +101,22 @@ export class ApplicationController {
100101
}
101102
}
102103

104+
@Read()
105+
@Get(":id/application-dashboard-data")
106+
@ApiProduces("application/json")
107+
@ApiOperation({ summary: "returns applications dashboard data" })
108+
@ApiNotFoundResponse()
109+
async countApplicationWithError(
110+
@Req() req: AuthenticatedRequest,
111+
@Param("id", new ParseIntPipe()) id: number
112+
): Promise<ApplicationDashboardResponseDto> {
113+
try {
114+
return await this.getApplicationsWithError(req, id, req.user.permissions.isGlobalAdmin);
115+
} catch (err) {
116+
throw new NotFoundException(ErrorCodes.IdDoesNotExists);
117+
}
118+
}
119+
103120
@Read()
104121
@Get(":id")
105122
@ApiOperation({ summary: "Find one Application by id" })
@@ -295,4 +312,28 @@ export class ApplicationController {
295312
const allowedApplications = req.user.permissions.getAllApplicationsWithAtLeastRead();
296313
return await this.applicationService.findFilterInformation(allowedApplications, organizationId);
297314
}
315+
316+
private async getApplicationsWithError(req: AuthenticatedRequest, organizationId: number, isGlobalAdmin: boolean) {
317+
if (isGlobalAdmin) {
318+
return {
319+
...(await this.applicationService.countApplicationsWithError(organizationId)),
320+
totalDevices: await this.applicationService.countAllDevices(organizationId),
321+
};
322+
}
323+
324+
const allFromOrg = req.user.permissions.getAllOrganizationsWithUserAdmin();
325+
326+
if (allFromOrg.some(x => x === organizationId)) {
327+
return {
328+
...(await this.applicationService.countApplicationsWithError(organizationId)),
329+
totalDevices: await this.applicationService.countAllDevices(organizationId),
330+
};
331+
}
332+
333+
const allowedApplications = req.user.permissions.getAllApplicationsWithAtLeastRead();
334+
return {
335+
...(await this.applicationService.countApplicationsWithError(organizationId, allowedApplications)),
336+
totalDevices: await this.applicationService.countAllDevices(organizationId, allowedApplications),
337+
};
338+
}
298339
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
export type ApplicationsWithErrorsResponseDto = {
2+
total: number;
3+
withError: number;
4+
};
5+
6+
export type ApplicationDashboardResponseDto = {
7+
total: number;
8+
withError: number;
9+
totalDevices: number;
10+
};

src/services/device-management/application.service.ts

Lines changed: 68 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { ApplicationsWithErrorsResponseDto } from "@dto/applications-dashboard-responses";
12
import { CreateApplicationDto } from "@dto/create-application.dto";
23
import {
34
ListAllApplicationsResponseDto,
@@ -30,7 +31,7 @@ import { MulticastService } from "@services/chirpstack/multicast.service";
3031
import { DataTargetService } from "@services/data-targets/data-target.service";
3132
import { OrganizationService } from "@services/user-management/organization.service";
3233
import { PermissionService } from "@services/user-management/permission.service";
33-
import { DeleteResult, In, Repository } from "typeorm";
34+
import { Brackets, DeleteResult, In, Repository } from "typeorm";
3435

3536
@Injectable()
3637
export class ApplicationService {
@@ -52,6 +53,58 @@ export class ApplicationService {
5253
private chirpstackApplicationService: ApplicationChirpstackService
5354
) {}
5455

56+
async countApplicationsWithError(
57+
organizationId: number,
58+
whitelist?: number[]
59+
): Promise<ApplicationsWithErrorsResponseDto> {
60+
const queryBuilder = this.applicationRepository
61+
.createQueryBuilder("app")
62+
.leftJoin("app.iotDevices", "device")
63+
.leftJoin("app.belongsTo", "organization")
64+
.leftJoin("device.latestReceivedMessage", "latestMessage")
65+
.leftJoin("app.dataTargets", "dataTargets")
66+
.andWhere("app.belongsToId = :organizationId", { organizationId: organizationId });
67+
68+
if (whitelist && whitelist.length > 0) {
69+
queryBuilder.where("app.id IN (:...whitelist)", { whitelist });
70+
}
71+
72+
queryBuilder.andWhere(
73+
new Brackets(qb => {
74+
qb.where("dataTargets.id IS NULL").orWhere("latestMessage.sentTime < NOW() - INTERVAL '24 HOURS'");
75+
})
76+
);
77+
78+
try {
79+
const [result, total] = await queryBuilder.getManyAndCount();
80+
81+
return {
82+
withError: result.length,
83+
total: total,
84+
};
85+
} catch (error) {
86+
throw new Error("Database query failed");
87+
}
88+
}
89+
async countAllDevices(organizationId: number, whitelist?: number[]): Promise<number> {
90+
const queryBuilder = this.applicationRepository
91+
.createQueryBuilder("app")
92+
.leftJoinAndSelect("app.iotDevices", "device")
93+
.leftJoin("app.dataTargets", "dataTargets")
94+
.where("app.belongsToId = :organizationId", { organizationId });
95+
96+
if (whitelist && whitelist.length > 0) {
97+
queryBuilder.andWhere("app.id IN (:...whitelist)", { whitelist });
98+
}
99+
100+
const count = await queryBuilder.select("COUNT(device.id)", "count").getRawOne();
101+
102+
try {
103+
return count.count ? parseInt(count.count, 10) : 0;
104+
} catch (error) {
105+
throw new Error("Database query failed");
106+
}
107+
}
55108
async findAndCountInList(
56109
query?: ListAllApplicationsDto,
57110
whitelist?: number[]
@@ -61,9 +114,9 @@ export class ApplicationService {
61114
const queryBuilder = this.applicationRepository
62115
.createQueryBuilder("app")
63116
.leftJoinAndSelect("app.iotDevices", "device")
64-
.leftJoinAndSelect("app.dataTargets", "dataTarget")
65117
.leftJoinAndSelect("app.belongsTo", "organization")
66-
.leftJoinAndSelect("device.latestReceivedMessage", "latestMessage");
118+
.leftJoinAndSelect("device.latestReceivedMessage", "latestMessage")
119+
.leftJoinAndSelect("app.dataTargets", "dataTargets");
67120

68121
if (whitelist && whitelist.length > 0) {
69122
queryBuilder.where("app.id IN (:...whitelist)", { whitelist });
@@ -74,43 +127,26 @@ export class ApplicationService {
74127
}
75128

76129
if (query.status) {
77-
console.log("status : " + query.status);
78130
queryBuilder.andWhere("app.status = :status", { status: query.status });
79131
}
80132

81133
if (query.owner) {
82134
queryBuilder.andWhere("app.owner = :owner", { owner: query.owner });
83135
}
84136

85-
if (query.statusCheck) {
137+
if (query.statusCheck === "alert") {
86138
queryBuilder.andWhere(
87-
`
88-
app.id IN (
89-
SELECT
90-
app.id,
91-
CASE
92-
WHEN COUNT(DISTINCT dataTarget.id) = 0 THEN 'alert'
93-
WHEN latestMessage.sentTime < NOW() - INTERVAL '24 HOURS' THEN 'alert'
94-
ELSE 'stable'
95-
END AS statusCheck
96-
FROM application app
97-
LEFT JOIN data_target dataTarget ON dataTarget.applicationId = app.id
98-
LEFT JOIN device ON device.applicationId = app.id
99-
LEFT JOIN latestMessage ON latestMessage.deviceId = device.id
100-
WHERE app.belongsToId = :organizationId
101-
GROUP BY app.id
102-
HAVING
103-
(
104-
COUNT(DISTINCT dataTarget.id) = 0
105-
OR latestMessage.sentTime < NOW() - INTERVAL '24 HOURS'
106-
)
107-
AND (
108-
:statusCheck = 'alert'
109-
OR COUNT(DISTINCT dataTarget.id) > 0
110-
)
111-
)
112-
`,
113-
{ statusCheck: query.statusCheck, organizationId: query.organizationId }
139+
new Brackets(qb => {
140+
qb.where("dataTargets.id IS NULL").orWhere("latestMessage.sentTime < NOW() - INTERVAL '24 HOURS'");
141+
})
142+
);
143+
}
144+
145+
if (query.statusCheck === "stable") {
146+
queryBuilder.andWhere(
147+
new Brackets(qb => {
148+
qb.where("dataTargets.id IS NOT NULL").orWhere("latestMessage.sentTime > NOW() - INTERVAL '24 HOURS'");
149+
})
114150
);
115151
}
116152

@@ -145,7 +181,6 @@ export class ApplicationService {
145181
count: total,
146182
};
147183
} catch (error) {
148-
console.error("Error executing query:", error);
149184
throw new Error("Database query failed");
150185
}
151186
}
@@ -252,8 +287,6 @@ export class ApplicationService {
252287
}
253288

254289
async findFilterInformation(applicationIds: number[] | "admin", organizationId: number) {
255-
console.log(applicationIds);
256-
257290
const query = this.applicationRepository
258291
.createQueryBuilder("application")
259292
.leftJoinAndSelect("application.belongsTo", "organization")

0 commit comments

Comments
 (0)