This is an automated email from the ASF dual-hosted git repository. linkinstar pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/incubator-answer.git
commit 98eceff5494a2114cbe0c2846785320acc14addc Author: sy-records <[email protected]> AuthorDate: Thu Nov 14 17:37:43 2024 +0800 feat: Add key metrics to the dashboard --- i18n/en_US.yaml | 2 ++ i18n/zh_CN.yaml | 2 ++ internal/repo/question/question_repo.go | 23 +++++++++++++++++ internal/schema/dashboard_schema.go | 4 +++ internal/service/dashboard/dashboard_service.go | 29 +++++++++++++++++++++- internal/service/question_common/question.go | 2 ++ ui/src/common/interface.ts | 4 +++ .../Dashboard/components/Statistics/index.tsx | 22 ++++++++++++++++ 8 files changed, 87 insertions(+), 1 deletion(-) diff --git a/i18n/en_US.yaml b/i18n/en_US.yaml index 6f3f5215..775d7456 100644 --- a/i18n/en_US.yaml +++ b/i18n/en_US.yaml @@ -1762,6 +1762,8 @@ ui: welcome: Welcome to Admin! site_statistics: Site statistics questions: "Questions:" + resolved: "Resolved:" + unanswered: "Unanswered:" answers: "Answers:" comments: "Comments:" votes: "Votes:" diff --git a/i18n/zh_CN.yaml b/i18n/zh_CN.yaml index 85581968..d0feebb9 100644 --- a/i18n/zh_CN.yaml +++ b/i18n/zh_CN.yaml @@ -1722,6 +1722,8 @@ ui: welcome: 欢迎来到管理后台! site_statistics: 站点统计 questions: "问题:" + resolved: "已解决:" + unanswered: "未回复:" answers: "回答:" comments: "评论:" votes: "投票:" diff --git a/internal/repo/question/question_repo.go b/internal/repo/question/question_repo.go index 16b6085f..1132501e 100644 --- a/internal/repo/question/question_repo.go +++ b/internal/repo/question/question_repo.go @@ -284,6 +284,29 @@ func (qr *questionRepo) GetQuestionCount(ctx context.Context) (count int64, err return count, nil } +func (qr *questionRepo) GetUnansweredQuestionCount(ctx context.Context) (count int64, err error) { + session := qr.data.DB.Context(ctx) + session.Where(builder.Lt{"status": entity.QuestionStatusDeleted}). + And(builder.Eq{"answer_count": 0}) + count, err = session.Count(&entity.Question{Show: entity.QuestionShow}) + if err != nil { + return 0, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return count, nil +} + +func (qr *questionRepo) GetResolvedQuestionCount(ctx context.Context) (count int64, err error) { + session := qr.data.DB.Context(ctx) + session.Where(builder.Lt{"status": entity.QuestionStatusDeleted}). + And(builder.Neq{"answer_count": 0}). + And(builder.Neq{"accepted_answer_id": 0}) + count, err = session.Count(&entity.Question{Show: entity.QuestionShow}) + if err != nil { + return 0, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } + return count, nil +} + func (qr *questionRepo) GetUserQuestionCount(ctx context.Context, userID string, show int) (count int64, err error) { session := qr.data.DB.Context(ctx) session.Where(builder.Lt{"status": entity.QuestionStatusDeleted}) diff --git a/internal/schema/dashboard_schema.go b/internal/schema/dashboard_schema.go index 2d780b8c..c2c7677d 100644 --- a/internal/schema/dashboard_schema.go +++ b/internal/schema/dashboard_schema.go @@ -30,6 +30,10 @@ const ( type DashboardInfo struct { QuestionCount int64 `json:"question_count"` + ResolvedCount int64 `json:"resolved_count"` + ResolvedRate string `json:"resolved_rate"` + UnansweredCount int64 `json:"unanswered_count"` + UnansweredRate string `json:"unanswered_rate"` AnswerCount int64 `json:"answer_count"` CommentCount int64 `json:"comment_count"` VoteCount int64 `json:"vote_count"` diff --git a/internal/service/dashboard/dashboard_service.go b/internal/service/dashboard/dashboard_service.go index 67abc2a7..ebc9a79d 100644 --- a/internal/service/dashboard/dashboard_service.go +++ b/internal/service/dashboard/dashboard_service.go @@ -103,7 +103,6 @@ func (ds *dashboardService) Statistical(ctx context.Context) (*schema.DashboardI dashboardInfo := ds.getFromCache(ctx) if dashboardInfo == nil { dashboardInfo = &schema.DashboardInfo{} - dashboardInfo.QuestionCount = ds.questionCount(ctx) dashboardInfo.AnswerCount = ds.answerCount(ctx) dashboardInfo.CommentCount = ds.commentCount(ctx) dashboardInfo.UserCount = ds.userCount(ctx) @@ -121,6 +120,18 @@ func (ds *dashboardService) Statistical(ctx context.Context) (*schema.DashboardI dashboardInfo.DatabaseSize = ds.GetDatabaseSize() } + dashboardInfo.QuestionCount = ds.questionCount(ctx) + dashboardInfo.UnansweredCount = ds.unansweredQuestionCount(ctx) + dashboardInfo.ResolvedCount = ds.resolvedQuestionCount(ctx) + + if dashboardInfo.QuestionCount == 0 { + dashboardInfo.ResolvedRate = "0.00" + dashboardInfo.UnansweredRate = "0.00" + } else { + dashboardInfo.ResolvedRate = fmt.Sprintf("%.2f", float64(dashboardInfo.ResolvedCount)/float64(dashboardInfo.QuestionCount)*100) + dashboardInfo.UnansweredRate = fmt.Sprintf("%.2f", float64(dashboardInfo.UnansweredCount)/float64(dashboardInfo.QuestionCount)*100) + } + dashboardInfo.ReportCount = ds.reportCount(ctx) dashboardInfo.SMTP = ds.smtpStatus(ctx) dashboardInfo.HTTPS = ds.httpsStatus(ctx) @@ -170,6 +181,22 @@ func (ds *dashboardService) questionCount(ctx context.Context) int64 { return questionCount } +func (ds *dashboardService) unansweredQuestionCount(ctx context.Context) int64 { + unansweredQuestionCount, err := ds.questionRepo.GetUnansweredQuestionCount(ctx) + if err != nil { + log.Errorf("get unanswered question count failed: %s", err) + } + return unansweredQuestionCount +} + +func (ds *dashboardService) resolvedQuestionCount(ctx context.Context) int64 { + resolvedQuestionCount, err := ds.questionRepo.GetResolvedQuestionCount(ctx) + if err != nil { + log.Errorf("get resolved question count failed: %s", err) + } + return resolvedQuestionCount +} + func (ds *dashboardService) answerCount(ctx context.Context) int64 { answerCount, err := ds.answerRepo.GetAnswerCount(ctx) if err != nil { diff --git a/internal/service/question_common/question.go b/internal/service/question_common/question.go index 13df3389..08c74f29 100644 --- a/internal/service/question_common/question.go +++ b/internal/service/question_common/question.go @@ -74,6 +74,8 @@ type QuestionRepo interface { FindByID(ctx context.Context, id []string) (questionList []*entity.Question, err error) AdminQuestionPage(ctx context.Context, search *schema.AdminQuestionPageReq) ([]*entity.Question, int64, error) GetQuestionCount(ctx context.Context) (count int64, err error) + GetUnansweredQuestionCount(ctx context.Context) (count int64, err error) + GetResolvedQuestionCount(ctx context.Context) (count int64, err error) GetUserQuestionCount(ctx context.Context, userID string, show int) (count int64, err error) SitemapQuestions(ctx context.Context, page, pageSize int) (questionIDList []*schema.SiteMapQuestionInfo, err error) RemoveAllUserQuestion(ctx context.Context, userID string) (err error) diff --git a/ui/src/common/interface.ts b/ui/src/common/interface.ts index 8a933471..903c5c28 100644 --- a/ui/src/common/interface.ts +++ b/ui/src/common/interface.ts @@ -524,6 +524,10 @@ export interface SearchRes extends ListResult<SearchResItem> { export interface AdminDashboard { info: { question_count: number; + resolved_count: number; + resolved_rate: string; + unanswered_count: number; + unanswered_rate: string; answer_count: number; comment_count: number; vote_count: number; diff --git a/ui/src/pages/Admin/Dashboard/components/Statistics/index.tsx b/ui/src/pages/Admin/Dashboard/components/Statistics/index.tsx index c95eaba7..a3077ba4 100644 --- a/ui/src/pages/Admin/Dashboard/components/Statistics/index.tsx +++ b/ui/src/pages/Admin/Dashboard/components/Statistics/index.tsx @@ -39,6 +39,28 @@ const Statistics: FC<IProps> = ({ data }) => { <span className="text-secondary me-1">{t('questions')}</span> <strong>{data.question_count}</strong> </Col> + <Col xs={6} className="mb-1"> + <span className="text-secondary me-1">{t('resolved')}</span> + <strong>{data.resolved_count}</strong> + {data.resolved_count > 0 ? ( + <span className="text-secondary m-1"> + ({data.resolved_rate}%) + </span> + ) : ( + '' + )} + </Col> + <Col xs={6} className="mb-1"> + <span className="text-secondary me-1">{t('unanswered')}</span> + <strong>{data.unanswered_count}</strong> + {data.unanswered_count > 0 ? ( + <span className="text-secondary m-1"> + ({data.unanswered_rate}%) + </span> + ) : ( + '' + )} + </Col> <Col xs={6} className="mb-1"> <span className="text-secondary me-1">{t('answers')}</span> <strong>{data.answer_count}</strong>
