Skip to content

Commit

Permalink
added endpoint for application dashboard data
Browse files Browse the repository at this point in the history
  • Loading branch information
Lauest-Iterator committed Feb 12, 2025
1 parent 86173bc commit 48f74eb
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 35 deletions.
41 changes: 41 additions & 0 deletions src/controllers/admin-controller/application.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { ComposeAuthGuard } from "@auth/compose-auth.guard";
import { ApplicationAdmin, Read } from "@auth/roles.decorator";
import { RolesGuard } from "@auth/roles.guard";
import { ApiAuth } from "@auth/swagger-auth-decorator";
import { ApplicationDashboardResponseDto } from "@dto/applications-dashboard-responses";
import { CreateApplicationDto } from "@dto/create-application.dto";
import { DeleteResponseDto } from "@dto/delete-application-response.dto";
import { ListAllApplicationsResponseDto } from "@dto/list-all-applications-response.dto";
Expand Down Expand Up @@ -100,6 +101,22 @@ export class ApplicationController {
}
}

@Read()
@Get(":id/application-dashboard-data")
@ApiProduces("application/json")
@ApiOperation({ summary: "returns applications dashboard data" })
@ApiNotFoundResponse()
async countApplicationWithError(
@Req() req: AuthenticatedRequest,
@Param("id", new ParseIntPipe()) id: number
): Promise<ApplicationDashboardResponseDto> {
try {
return await this.getApplicationsWithError(req, id, req.user.permissions.isGlobalAdmin);
} catch (err) {
throw new NotFoundException(ErrorCodes.IdDoesNotExists);
}
}

@Read()
@Get(":id")
@ApiOperation({ summary: "Find one Application by id" })
Expand Down Expand Up @@ -295,4 +312,28 @@ export class ApplicationController {
const allowedApplications = req.user.permissions.getAllApplicationsWithAtLeastRead();
return await this.applicationService.findFilterInformation(allowedApplications, organizationId);
}

private async getApplicationsWithError(req: AuthenticatedRequest, organizationId: number, isGlobalAdmin: boolean) {
if (isGlobalAdmin) {
return {
...(await this.applicationService.countApplicationsWithError(organizationId)),
totalDevices: await this.applicationService.countAllDevices(organizationId),
};
}

const allFromOrg = req.user.permissions.getAllOrganizationsWithUserAdmin();

if (allFromOrg.some(x => x === organizationId)) {
return {
...(await this.applicationService.countApplicationsWithError(organizationId)),
totalDevices: await this.applicationService.countAllDevices(organizationId),
};
}

const allowedApplications = req.user.permissions.getAllApplicationsWithAtLeastRead();
return {
...(await this.applicationService.countApplicationsWithError(organizationId, allowedApplications)),
totalDevices: await this.applicationService.countAllDevices(organizationId, allowedApplications),
};
}
}
10 changes: 10 additions & 0 deletions src/entities/dto/applications-dashboard-responses.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export type ApplicationsWithErrorsResponseDto = {
total: number;
withError: number;
};

export type ApplicationDashboardResponseDto = {
total: number;
withError: number;
totalDevices: number;
};
103 changes: 68 additions & 35 deletions src/services/device-management/application.service.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ApplicationsWithErrorsResponseDto } from "@dto/applications-dashboard-responses";
import { CreateApplicationDto } from "@dto/create-application.dto";
import {
ListAllApplicationsResponseDto,
Expand Down Expand Up @@ -30,7 +31,7 @@ import { MulticastService } from "@services/chirpstack/multicast.service";
import { DataTargetService } from "@services/data-targets/data-target.service";
import { OrganizationService } from "@services/user-management/organization.service";
import { PermissionService } from "@services/user-management/permission.service";
import { DeleteResult, In, Repository } from "typeorm";
import { Brackets, DeleteResult, In, Repository } from "typeorm";

@Injectable()
export class ApplicationService {
Expand All @@ -52,6 +53,58 @@ export class ApplicationService {
private chirpstackApplicationService: ApplicationChirpstackService
) {}

async countApplicationsWithError(
organizationId: number,
whitelist?: number[]
): Promise<ApplicationsWithErrorsResponseDto> {
const queryBuilder = this.applicationRepository
.createQueryBuilder("app")
.leftJoin("app.iotDevices", "device")
.leftJoin("app.belongsTo", "organization")
.leftJoin("device.latestReceivedMessage", "latestMessage")
.leftJoin("app.dataTargets", "dataTargets")
.andWhere("app.belongsToId = :organizationId", { organizationId: organizationId });

if (whitelist && whitelist.length > 0) {
queryBuilder.where("app.id IN (:...whitelist)", { whitelist });
}

queryBuilder.andWhere(
new Brackets(qb => {
qb.where("dataTargets.id IS NULL").orWhere("latestMessage.sentTime < NOW() - INTERVAL '24 HOURS'");
})
);

try {
const [result, total] = await queryBuilder.getManyAndCount();

return {
withError: result.length,
total: total,
};
} catch (error) {
throw new Error("Database query failed");
}
}
async countAllDevices(organizationId: number, whitelist?: number[]): Promise<number> {
const queryBuilder = this.applicationRepository
.createQueryBuilder("app")
.leftJoinAndSelect("app.iotDevices", "device")
.leftJoin("app.dataTargets", "dataTargets")
.where("app.belongsToId = :organizationId", { organizationId });

if (whitelist && whitelist.length > 0) {
queryBuilder.andWhere("app.id IN (:...whitelist)", { whitelist });
}

const count = await queryBuilder.select("COUNT(device.id)", "count").getRawOne();

try {
return count.count ? parseInt(count.count, 10) : 0;
} catch (error) {
throw new Error("Database query failed");
}
}
async findAndCountInList(
query?: ListAllApplicationsDto,
whitelist?: number[]
Expand All @@ -61,9 +114,9 @@ export class ApplicationService {
const queryBuilder = this.applicationRepository
.createQueryBuilder("app")
.leftJoinAndSelect("app.iotDevices", "device")
.leftJoinAndSelect("app.dataTargets", "dataTarget")
.leftJoinAndSelect("app.belongsTo", "organization")
.leftJoinAndSelect("device.latestReceivedMessage", "latestMessage");
.leftJoinAndSelect("device.latestReceivedMessage", "latestMessage")
.leftJoinAndSelect("app.dataTargets", "dataTargets");

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

if (query.status) {
console.log("status : " + query.status);
queryBuilder.andWhere("app.status = :status", { status: query.status });
}

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

if (query.statusCheck) {
if (query.statusCheck === "alert") {
queryBuilder.andWhere(
`
app.id IN (
SELECT
app.id,
CASE
WHEN COUNT(DISTINCT dataTarget.id) = 0 THEN 'alert'
WHEN latestMessage.sentTime < NOW() - INTERVAL '24 HOURS' THEN 'alert'
ELSE 'stable'
END AS statusCheck
FROM application app
LEFT JOIN data_target dataTarget ON dataTarget.applicationId = app.id
LEFT JOIN device ON device.applicationId = app.id
LEFT JOIN latestMessage ON latestMessage.deviceId = device.id
WHERE app.belongsToId = :organizationId
GROUP BY app.id
HAVING
(
COUNT(DISTINCT dataTarget.id) = 0
OR latestMessage.sentTime < NOW() - INTERVAL '24 HOURS'
)
AND (
:statusCheck = 'alert'
OR COUNT(DISTINCT dataTarget.id) > 0
)
)
`,
{ statusCheck: query.statusCheck, organizationId: query.organizationId }
new Brackets(qb => {
qb.where("dataTargets.id IS NULL").orWhere("latestMessage.sentTime < NOW() - INTERVAL '24 HOURS'");
})
);
}

if (query.statusCheck === "stable") {
queryBuilder.andWhere(
new Brackets(qb => {
qb.where("dataTargets.id IS NOT NULL").orWhere("latestMessage.sentTime > NOW() - INTERVAL '24 HOURS'");
})
);
}

Expand Down Expand Up @@ -145,7 +181,6 @@ export class ApplicationService {
count: total,
};
} catch (error) {
console.error("Error executing query:", error);
throw new Error("Database query failed");
}
}
Expand Down Expand Up @@ -252,8 +287,6 @@ export class ApplicationService {
}

async findFilterInformation(applicationIds: number[] | "admin", organizationId: number) {
console.log(applicationIds);

const query = this.applicationRepository
.createQueryBuilder("application")
.leftJoinAndSelect("application.belongsTo", "organization")
Expand Down

0 comments on commit 48f74eb

Please sign in to comment.