Skip to content

Commit

Permalink
feat: projects using native sql query sorting
Browse files Browse the repository at this point in the history
  • Loading branch information
Allen Zhang (张涛) committed Feb 5, 2024
1 parent f3301d6 commit 7efc083
Show file tree
Hide file tree
Showing 7 changed files with 130 additions and 116 deletions.
17 changes: 16 additions & 1 deletion packages/canyon-backend/schema.gql
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,22 @@ type Query {
me: User!

"""获取Project"""
getProjects(current: Int!, pageSize: Int!, keyword: String!, bu: [String!]!): ProjectPagesModel!
getProjects(
keyword: String!
bu: [String!]!

"""当前页码"""
current: Int!

"""每页数量"""
pageSize: Int!

"""排序字段名称"""
field: String!

"""升序或降序"""
order: String!
): ProjectPagesModel!

"""获取Projects部门选项"""
getProjectsBuOptions: [BuOption!]!
Expand Down
4 changes: 3 additions & 1 deletion packages/canyon-backend/src/prisma/prisma.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ export class PrismaService
implements OnModuleInit, OnModuleDestroy
{
constructor() {
super();
super({
// log: ['query', 'info', 'warn', 'error'],
});
}
async onModuleInit() {
await this.$connect();
Expand Down
16 changes: 12 additions & 4 deletions packages/canyon-backend/src/project/project.resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { ProjectPagesModel } from './models/project-pages.model';
import { ProjectService } from './services/project.service';
import { GetProjectChartDataService } from './services/get-project-chart-data.service';
import { ProjectChartDataModel } from './models/project-chart-data.model';
import { ProjectRecordsModel } from './models/project-records.model';
import { GetProjectRecordsService } from './services/get-project-records.service';
import { ProjectRecordsPagesModel } from './models/project-records-pages.model';
import { GetProjectCompartmentDataService } from './services/get-project-compartment-data.service';
Expand All @@ -17,6 +16,7 @@ import { GetProjectRecordDetailByShaService } from './services/get-project-recor
import { ProjectRecordDetailModel } from './models/project-record-detail.model';
import { Project2 } from './project2.model';
import { GetProjectsService } from './services/get-projects.service';
import { PaginationArgs, SorterArgs } from '../types/input-types.args';
@Resolver(() => 'Project')
export class ProjectResolver {
constructor(
Expand All @@ -31,12 +31,20 @@ export class ProjectResolver {
description: '获取Project',
})
getProjects(
@Args('current', { type: () => Int }) current: number,
@Args('pageSize', { type: () => Int }) pageSize: number,
@Args('keyword', { type: () => String }) keyword: string,
@Args('bu', { type: () => [String] }) bu: string[],
@Args() paginationArgs: PaginationArgs,
@Args() sorterArgs: SorterArgs,
): Promise<ProjectPagesModel> {
return this.getProjectsService.invoke(current, pageSize, keyword, bu);
// @ts-ignore
return this.getProjectsService.invoke(
paginationArgs.current,
paginationArgs.pageSize,
keyword,
bu,
sorterArgs.field,
sorterArgs.order,
);
}

@Query(() => [BuOption], {
Expand Down
139 changes: 50 additions & 89 deletions packages/canyon-backend/src/project/services/get-projects.service.ts
Original file line number Diff line number Diff line change
@@ -1,100 +1,61 @@
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../../prisma/prisma.service';
import { Project } from '../project.model';
import { percent } from '../../utils/utils';
import { getProjectByID } from 'src/adapter/gitlab.adapter';
// import { getProjectByID } from '../adapter/gitlab.adapter';
function parseGitLabUrl(gitLabUrl) {
// 匹配 GitLab URL 的正则表达式
const gitLabRegex = /^(?:https?:\/\/)?([^\/]+)\/(.+)$/;

// 尝试匹配正则表达式
const match = gitLabUrl.match(gitLabRegex);

if (match) {
// 提取匹配的组和仓库名
const groupAndRepo = match[2].split('/');
const groupName = groupAndRepo.slice(0, -1).join('/');
const repositoryName = groupAndRepo.slice(-1)[0];
return { groupName, repositoryName };
} else {
// 如果没有匹配到,返回 null
return { groupName: null, repositoryName: null };
}
import { Prisma } from '@prisma/client';
function underscoreToCamelCase(string) {
return string.replace(/_([a-z])/g, function (match, letter) {
return letter.toUpperCase();
});
}
function camelCaseToUnderscore(string) {
return string.replace(/([A-Z])/g, '_$1').toLowerCase();
}

@Injectable()
export class GetProjectsService {
constructor(private readonly prisma: PrismaService) {}
async invoke(current, pageSize, keyword, bu) {
const whereCondition: any = {
OR: [
// { description: { contains: keyword } },
// { name: { contains: keyword } },
{ pathWithNamespace: { contains: keyword, mode: 'insensitive' } },
{ id: { contains: keyword, mode: 'insensitive' } },
],
};

if (bu.length > 0) {
whereCondition['bu'] = { in: bu };
}
const total = await this.prisma.project.count({
where: whereCondition,
});

const projects = await this.prisma.project.findMany({
where: whereCondition,
skip: (current - 1) * pageSize,
take: pageSize - 0,
});

const rows = [];

for (let i = 0; i < projects.length; i++) {
const coverages = await this.prisma.coverage.findMany({
where: {
projectID: projects[i].id,
covType: 'all',
},
});
async invoke(current, pageSize, keyword, _bu, field, order) {
const bu =
_bu.length > 0
? _bu
: await this.prisma.project
.groupBy({
by: ['bu'],
_count: {
bu: true,
},
})
.then((r) => r.map((item) => item.bu));

const limit = pageSize; // pageSize控制每页的记录数
const offset = (current - 1) * pageSize; // 计算要跳过的记录数

const total: any = await this.prisma.$queryRaw`
SELECT COUNT(DISTINCT p.id) AS total
FROM project_20240130 AS p
LEFT JOIN coverage_20240130 AS c ON p.id = c.project_id
LEFT JOIN summary_20240130 AS s ON c.sha = s.sha and c.report_id=s.report_id
WHERE s.metric_type='statements' AND s.cov_type='all' AND bu IN (${Prisma.join(bu)}) AND (p.path_with_namespace ILIKE ${'%' + keyword + '%'} OR p.id ILIKE ${'%' + keyword + '%'})
GROUP BY p.id
`.then((r: any) => r.length);

const rows: any[] = await this.prisma.$queryRaw`
SELECT p.id AS id, p.path_with_namespace,p.description,p.name, CAST(COUNT(DISTINCT c.sha) AS INT) AS report_times,p.bu, ROUND(MAX(100*covered::decimal / NULLIF(total, 0)),2) AS max_coverage, MAX(s.created_at) AS last_report_time
FROM project_20240130 AS p
LEFT JOIN coverage_20240130 AS c ON p.id = c.project_id
LEFT JOIN summary_20240130 AS s ON c.sha = s.sha and c.report_id=s.report_id
WHERE s.metric_type='statements' AND s.cov_type='all' AND bu IN (${Prisma.join(bu)}) AND (p.path_with_namespace ILIKE ${'%' + keyword + '%'} OR p.id ILIKE ${'%' + keyword + '%'})
GROUP BY p.id
ORDER BY ${Prisma.sql([camelCaseToUnderscore(field) || 'last_report_time'])} ${Prisma.sql([{ ascend: 'ASC', descend: 'DESC' }[order] || 'DESC'])}
LIMIT ${limit} OFFSET ${offset};
`;

const summarys = await this.prisma.summary.findMany({
where: {
// projectID: projects[i].id,
covType: 'all',
metricType: 'statements',
sha: {
in: [...new Set(coverages.map((item) => item.sha))],
},
},
orderBy: {
createdAt: 'desc',
},
});
if (summarys.length > 0) {
rows.push({
...projects[i],
lastReportTime: summarys[0]?.createdAt,
reportTimes: summarys.length,
maxCoverage: Math.max(
...summarys.map((item) =>
item.total === 0 ? 0 : percent(item.covered, item.total),
),
),
});
} else {
rows.push({
...projects[i],
lastReportTime: new Date(),
reportTimes: 0,
maxCoverage: 0,
});
}
}
return {
data: rows,
total,
data: rows.map((item: any) => {
return Object.keys(item).reduce((acc, key) => {
acc[underscoreToCamelCase(key)] = item[key];
return acc;
}, {});
}),
total: total,
};
}
}
29 changes: 29 additions & 0 deletions packages/canyon-backend/src/types/input-types.args.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { ArgsType, Field, ID, InputType, Int } from '@nestjs/graphql';

@ArgsType()
@InputType()
export class PaginationArgs {
@Field(() => Int, {
description: '当前页码',
})
current: number;

@Field(() => Int, {
description: '每页数量',
})
pageSize: number;
}

@ArgsType()
@InputType()
export class SorterArgs {
@Field({
description: '排序字段名称',
})
field: string;

@Field({
description: '升序或降序',
})
order: string;
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
query GetProjects($current: Int!, $pageSize: Int!, $keyword: String!,$bu: [String!]!) {
getProjects(current:$current, pageSize:$pageSize, keyword:$keyword,bu:$bu) {
query GetProjects($current: Int!, $pageSize: Int!, $keyword: String!,$bu: [String!]!,$field: String!,$order: String!) {
getProjects(current:$current, pageSize:$pageSize, keyword:$keyword,bu:$bu,field:$field,order:$order) {
total
data {
id
Expand Down
37 changes: 18 additions & 19 deletions packages/canyon-platform/src/pages/index/projects/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Button, Card, Divider, Popconfirm, Select, Table, Typography } from 'an
import Search from 'antd/es/input/Search';
import { ColumnsType } from 'antd/es/table';
import dayjs from 'dayjs';
import { useEffect, useMemo, useState } from 'react';
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';

Expand Down Expand Up @@ -34,22 +34,26 @@ const ProjectPage = () => {
{
title: 'Bu',
dataIndex: 'bu',
sorter: true,
},
{
title: t('Report Times'),
dataIndex: 'reportTimes',
sorter: true,
},
{
title: 'Max coverage',
dataIndex: 'maxCoverage',
key: 'maxCoverage',
sorter: true,
render: (text) => {
return <span>{text}%</span>;
},
},
{
title: t('Last Report Time'),
dataIndex: 'lastReportTime',
sorter: true,
render(_) {
return <span>{dayjs(_).format('YYYY-MM-DD HH:mm')}</span>;
},
Expand Down Expand Up @@ -102,12 +106,16 @@ const ProjectPage = () => {
return [];
}
})();
// const [loading, setLoading] = useState(false);
const [keyword, setKeyword] = useState('');
const [bu, setBu] = useState<string[]>(initBu);
const [current, setCurrent] = useState(1);
const [pageSize, setPageSize] = useState(10);
// const [loading, setLoading] = useState(false);
const [sorter, setSorter] = useState<any>({});

const { data: projectsBuOptionsData } = useQuery(GetProjectsBuOptionsDocument, {
fetchPolicy: 'no-cache',
});

const {
data: projectsData,
loading,
Expand All @@ -118,24 +126,12 @@ const ProjectPage = () => {
pageSize: pageSize,
keyword: keyword,
bu: bu,
field: sorter.field || '',
order: sorter.order || '',
},
fetchPolicy: 'no-cache',
});

const {
data: projectsBuOptionsData,
// loading,
// refetch,
} = useQuery(GetProjectsBuOptionsDocument, {
fetchPolicy: 'no-cache',
});

useEffect(() => {
// refetch();
}, []);

// const s = useMemo(() => {}, [projectsData]);

return (
<div>
<div className={'flex justify-between items-center px-6 pb-2 pt-0'}>
Expand All @@ -144,7 +140,6 @@ const ProjectPage = () => {
<FolderOutlined className={'text-[#687076] text-[32px]'} />
<span>{t('menus.projects')}</span>
</Title>
{/*<Text type={'secondary'}>{t('projects.desc')}</Text>*/}
</div>
<div>
<Link to={`/projects/new`}>
Expand Down Expand Up @@ -191,7 +186,11 @@ const ProjectPage = () => {
bordered={false}
dataSource={projectsData?.getProjects?.data || []}
columns={columns}
onChange={(val) => {
onChange={(val, _, _sorter:any) => {
setSorter({
field: _sorter.field,
order: _sorter.order,
});
setCurrent(val.current || 1);
setPageSize(val.pageSize || 10);
}}
Expand Down

0 comments on commit 7efc083

Please sign in to comment.