Skip to content

Commit

Permalink
feat(mis): 管理系统平台数据统计作业提交用户前十数横坐标改为userName (#1206)
Browse files Browse the repository at this point in the history
### 做了什么
1.管理系统->平台数据统计->用户提交TOP10,横坐标改为userName
#### 改进前

![368260221c753d5de6ce002ca3bafd1](https://github.com/PKUHPC/SCOW/assets/72734623/b0e80829-fb2f-4526-bba0-ac3ce778920e)
#### 改进后

![ad2ee8bb6ccb1897dcd277e3bc29692](https://github.com/PKUHPC/SCOW/assets/72734623/c8081f48-3ff6-4c1f-a436-b3295c69fe91)
  • Loading branch information
usaveh committed Apr 18, 2024
1 parent 0bd490d commit 583978b
Show file tree
Hide file tree
Showing 8 changed files with 198 additions and 4 deletions.
7 changes: 7 additions & 0 deletions .changeset/great-carrots-refuse.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@scow/mis-server": patch
"@scow/mis-web": patch
"@scow/grpc-api": patch
---

管理系统下的平台数据统计提交作业前十的用户数横坐标改为以 userName 的方式显示.
34 changes: 34 additions & 0 deletions apps/mis-server/src/services/job.ts
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,40 @@ export const jobServiceServer = plugin((server) => {
];
},

// 返回用户名,需要联表查询
getUsersWithMostJobSubmissions: async ({ request, em }) => {
// topNUsers不传默认为10,最大限制为10
const { startTime, endTime, topNUsers = 10 } = ensureNotUndefined(request, ["startTime", "endTime"]);

// 控制topNUsers的数量
if (typeof topNUsers == "number" && (topNUsers > 10 || topNUsers < 0)) {
throw <ServiceError> { code: status.INVALID_ARGUMENT, message:"topNUsers must be between 0 and 10" };
}
// 直接使用Knex查询构建器
const knex = em.getKnex();

const results: {userName: string, userId: string, count: number}[] = await knex("job_info as j")
.select([
"u.name as userName",
"j.user as userId",
knex.raw("COUNT(*) as count"),
])
.join("user as u", "u.user_id", "=", "j.user")
.where("j.time_submit", ">=", startTime)
.andWhere("j.time_submit", "<=", endTime)
.groupBy("j.user")
.orderBy("count", "desc")
.limit(Math.min(topNUsers, 10));

// 直接返回构建的结果
return [
{
results,
},
];
},


getNewJobCount: async ({ request, em }) => {
const { startTime, endTime, timeZone = "UTC" } = ensureNotUndefined(request, ["startTime", "endTime"]);

Expand Down
30 changes: 30 additions & 0 deletions apps/mis-server/tests/job/JobService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,8 @@ it("get Top Submit Job Users correctly", async () => {

});



it("get new job count correctly in UTC+8 timezone", async () => {

const today = dayjs();
Expand Down Expand Up @@ -303,3 +305,31 @@ it("get new job count correctly in UTC+8 timezone", async () => {

]);
});


it("get Users With Most Job Submissions correctly", async () => {
const em = server.ext.orm.em.fork();

const today = dayjs();

const userAJobs = range(0, 20).map((_) =>
mockOriginalJobData(data.uaAA, new Decimal(20), new Decimal(10), today.toDate()));
const userBJobs = range(0, 30).map((_) =>
mockOriginalJobData(data.uaBB, new Decimal(20), new Decimal(10), today.toDate()));
const userCJobs = range(0, 40).map((_) =>
mockOriginalJobData(data.uaCC, new Decimal(20), new Decimal(10), today.toDate()));
await em.persistAndFlush([...userAJobs, ...userBJobs, ...userCJobs]);

const client = createClient();
const reply = await asyncClientCall(client, "getUsersWithMostJobSubmissions", {
startTime: today.startOf("day").toISOString(),
endTime: today.endOf("day").toISOString(),
});

expect(reply.results).toMatchObject([
{ userName: data.userC.name, userId: data.userC.userId, count: 40 },
{ userName: data.userB.name, userId: data.userB.userId, count: 30 },
{ userName: data.userA.name, userId: data.userA.userId, count: 20 },
]);

});
2 changes: 2 additions & 0 deletions apps/mis-web/src/apis/api.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,8 @@ export const mockApi: MockApi<typeof api> = {

getTopSubmitJobUser: async () => ({ results: [{ userId: "test", count:10 }]}),

getUsersWithMostJobSubmissions: async () => ({ results: [{ userName: "name1", userId: "test1", count:10 }]}),

getNewJobCount: async () => ({ results: [{ date: { year: 2023, month: 12, day: 21 }, count: 10 }]}),

getTenantUsers: async () => ({ results: mockUsers }),
Expand Down
2 changes: 2 additions & 0 deletions apps/mis-web/src/apis/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import type { GetTenantUsersSchema } from "src/pages/api/admin/getTenantUsers";
import type { GetTopChargeAccountSchema } from "src/pages/api/admin/getTopChargeAccount";
import type { GetTopPayAccountSchema } from "src/pages/api/admin/getTopPayAccount";
import type { GetTopSubmitJobUserSchema } from "src/pages/api/admin/getTopSubmitJobUser";
import type { GetUsersWithMostJobSubmissionsSchema } from "src/pages/api/admin/getUsersWithMostJobSubmissions";
import type { ImportUsersSchema } from "src/pages/api/admin/importUsers";
import type { GetAlarmDbIdSchema } from "src/pages/api/admin/monitor/getAlarmDbId";
import type { GetAlarmLogsSchema } from "src/pages/api/admin/monitor/getAlarmLogs";
Expand Down Expand Up @@ -172,6 +173,7 @@ export const api = {
queryJobTimeLimit: apiClient.fromTypeboxRoute<typeof QueryJobTimeLimitSchema>("GET", "/api/job/queryJobTimeLimit"),
getRunningJobs: apiClient.fromTypeboxRoute<typeof GetRunningJobsSchema>("GET", "/api/job/runningJobs"),
getTopSubmitJobUser: apiClient.fromTypeboxRoute<typeof GetTopSubmitJobUserSchema>("GET", "/api/admin/getTopSubmitJobUser"),
getUsersWithMostJobSubmissions: apiClient.fromTypeboxRoute<typeof GetUsersWithMostJobSubmissionsSchema>("GET", "/api/admin/getUsersWithMostJobSubmissions"),
getNewJobCount: apiClient.fromTypeboxRoute<typeof GetNewJobCountSchema>("GET", "/api/admin/getNewJobCount"),
getOperationLogs: apiClient.fromTypeboxRoute<typeof GetOperationLogsSchema>("GET", "/api/log/getOperationLog"),
getCustomEventTypes: apiClient.fromTypeboxRoute<typeof GetCustomEventTypesSchema>("GET", "/api/log/getCustomEventTypes"),
Expand Down
19 changes: 15 additions & 4 deletions apps/mis-web/src/pages/admin/statistic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -175,14 +175,25 @@ requireAuth((u) => u.platformRoles.includes(PlatformRole.PLATFORM_ADMIN))

const { data: dailyPay, isLoading: dailyPayLoading } = useAsync({ promiseFn: getDailyPayFn });

const getTopSubmitJobUserFn = useCallback(async () => {
return await api.getTopSubmitJobUser({ query: {
// const getTopSubmitJobUserFn = useCallback(async () => {
// return await api.getTopSubmitJobUser({ query: {
// startTime: query.filterTime[0].startOf("day").toISOString(),
// endTime: query.filterTime[1].endOf("day").toISOString(),
// } });
// }, [query]);

// const { data: topSubmitJobUser, isLoading: topSubmitJobUserLoading } =
// useAsync({ promiseFn: getTopSubmitJobUserFn });

const getUsersWithMostJobSubmissionsFn = useCallback(async () => {
return await api.getUsersWithMostJobSubmissions({ query: {
startTime: query.filterTime[0].startOf("day").toISOString(),
endTime: query.filterTime[1].endOf("day").toISOString(),
} });
}, [query]);

const { data: topSubmitJobUser, isLoading: topSubmitJobUserLoading } = useAsync({ promiseFn: getTopSubmitJobUserFn });
const { data: topSubmitJobUser, isLoading: topSubmitJobUserLoading } =
useAsync({ promiseFn: getUsersWithMostJobSubmissionsFn });

const getNewJobCountFn = useCallback(async () => {
return await api.getNewJobCount({ query: {
Expand Down Expand Up @@ -264,7 +275,7 @@ requireAuth((u) => u.platformRoles.includes(PlatformRole.PLATFORM_ADMIN))
const topSubmitJobUserData = useMemo(() => {

return topSubmitJobUser?.results.map((r) => ({
x: r.userId,
x: r.userName,
y: r.count,
})) || [];
}, [query, topSubmitJobUser]);
Expand Down
88 changes: 88 additions & 0 deletions apps/mis-web/src/pages/api/admin/getUsersWithMostJobSubmissions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/**
* Copyright (c) 2022 Peking University and Peking University Institute for Computing and Digital Economy
* SCOW is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/

import { typeboxRoute, typeboxRouteSchema } from "@ddadaal/next-typed-api-routes-runtime";
import { asyncClientCall } from "@ddadaal/tsgrpc-client";
import { JobServiceClient } from "@scow/protos/build/server/job";
import { Static, Type } from "@sinclair/typebox";
import { authenticate } from "src/auth/server";
import { PlatformRole } from "src/models/User";
import { getClient } from "src/utils/client";

export const GetUsersWithMostJobSubmissionsResponse = Type.Object({
results: Type.Array(Type.Object({
userName: Type.String(),
userId:Type.String(),
count: Type.Number(),
})),
});

// 定义错误相应类型
export const ErrorResponse = Type.Object({
message: Type.String(),
});

export type GetUsersWithMostJobSubmissionsResponse = Static<typeof GetUsersWithMostJobSubmissionsResponse>;


export const GetUsersWithMostJobSubmissionsSchema = typeboxRouteSchema({
method: "GET",

query: Type.Object({

startTime: Type.String({ format: "date-time" }),

endTime: Type.String({ format: "date-time" }),

// 最大为10,不传默认为10
topNUsers: Type.Optional(Type.Number()),

}),

responses: {
200: GetUsersWithMostJobSubmissionsResponse,
400: ErrorResponse,
},
});

const auth = authenticate((info) => info.platformRoles.includes(PlatformRole.PLATFORM_ADMIN));

export default typeboxRoute(GetUsersWithMostJobSubmissionsSchema,
async (req, res) => {

const info = await auth(req, res);
if (!info) {
return;
}


const { startTime, endTime, topNUsers } = req.query;
// 检查 topNUsers 是否符合要求
if (typeof topNUsers == "number" && (topNUsers > 10 || topNUsers < 0)) {
res.status(400).send({ message: "Parameter topNUsers must be between 0 and 10." });
return;
};

const client = getClient(JobServiceClient);

const { results } = await asyncClientCall(client, "getUsersWithMostJobSubmissions", {
startTime,
endTime,
topNUsers,
});

return {
200: {
results,
},
};
});
20 changes: 20 additions & 0 deletions protos/server/job.proto
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,24 @@ message GetTopSubmitJobUsersResponse {
repeated SubmitJobUser results = 1;
}

message GetUsersWithMostJobSubmissionsRequest {
google.protobuf.Timestamp start_time = 1;
google.protobuf.Timestamp end_time = 2;

//需要获取top 多少
//最大为10
optional uint32 top_n_users = 3;
}

message GetUsersWithMostJobSubmissionsResponse {
message UserInfo {
string user_name = 1;
string user_id = 2;
uint32 count = 3;
}
repeated UserInfo results = 1;
}

message GetNewJobCountRequest {
google.protobuf.Timestamp start_time = 1;
google.protobuf.Timestamp end_time = 2;
Expand Down Expand Up @@ -215,6 +233,8 @@ service JobService {

rpc GetTopSubmitJobUsers(GetTopSubmitJobUsersRequest) returns (GetTopSubmitJobUsersResponse);

rpc GetUsersWithMostJobSubmissions(GetUsersWithMostJobSubmissionsRequest) returns (GetUsersWithMostJobSubmissionsResponse);

rpc GetNewJobCount(GetNewJobCountRequest) returns (GetNewJobCountResponse);

rpc GetJobTotalCount(GetJobTotalCountRequest) returns (GetJobTotalCountResponse);
Expand Down

0 comments on commit 583978b

Please sign in to comment.