From c76ab87a0a5aee3a5ef5cd29f86a88b75829b4ff Mon Sep 17 00:00:00 2001 From: johnnyjoy Date: Wed, 15 Jan 2025 19:25:36 +0800 Subject: [PATCH] refactor: update list user stats --- proto/api/v1/user_service.proto | 10 +- server/router/api/v1/user_service_stats.go | 100 +++++++++++------- .../ExploreSidebar/ExploreSidebar.tsx | 5 +- .../components/HomeSidebar/HomeSidebar.tsx | 3 +- web/src/store/v1/userStats.ts | 6 +- 5 files changed, 70 insertions(+), 54 deletions(-) diff --git a/proto/api/v1/user_service.proto b/proto/api/v1/user_service.proto index 882766a6..38babc1e 100644 --- a/proto/api/v1/user_service.proto +++ b/proto/api/v1/user_service.proto @@ -198,11 +198,7 @@ message UserStats { } } -message ListAllUserStatsRequest { - // Filter is used to filter memos to calculate stats. - // Same as `ListMemosRequest.filter`. - string filter = 1; -} +message ListAllUserStatsRequest {} message ListAllUserStatsResponse { repeated UserStats user_stats = 1; @@ -212,10 +208,6 @@ message GetUserStatsRequest { // The name of the user. // Format: users/{user}. string name = 1; - - // Filter is used to filter memos to calculate stats. - // Same as `ListMemosRequest.filter`. - string filter = 2; } message UserSetting { diff --git a/server/router/api/v1/user_service_stats.go b/server/router/api/v1/user_service_stats.go index b17c00d4..9e4bbf36 100644 --- a/server/router/api/v1/user_service_stats.go +++ b/server/router/api/v1/user_service_stats.go @@ -3,7 +3,6 @@ package v1 import ( "context" "fmt" - "slices" "time" "github.com/pkg/errors" @@ -16,19 +15,66 @@ import ( ) func (s *APIV1Service) ListAllUserStats(ctx context.Context, request *v1pb.ListAllUserStatsRequest) (*v1pb.ListAllUserStatsResponse, error) { - users, err := s.Store.ListUsers(ctx, &store.FindUser{}) + currentUser, err := s.GetCurrentUser(ctx) if err != nil { - return nil, status.Errorf(codes.Internal, "failed to list users: %v", err) + return nil, status.Errorf(codes.Internal, "failed to get user: %v", err) } - userStatsList := []*v1pb.UserStats{} - for _, user := range users { - userStats, err := s.GetUserStats(ctx, &v1pb.GetUserStatsRequest{ - Name: fmt.Sprintf("%s%d", UserNamePrefix, user.ID), - Filter: request.Filter, - }) - if err != nil { - return nil, status.Errorf(codes.Internal, "failed to get user stats: %v", err) + visibilities := []store.Visibility{store.Public} + if currentUser != nil { + visibilities = append(visibilities, store.Protected) + } + + workspaceMemoRelatedSetting, err := s.Store.GetWorkspaceMemoRelatedSetting(ctx) + if err != nil { + return nil, errors.Wrap(err, "failed to get workspace memo related setting") + } + + memoFind := &store.FindMemo{ + // Exclude comments by default. + ExcludeComments: true, + ExcludeContent: true, + VisibilityList: visibilities, + } + memos, err := s.Store.ListMemos(ctx, memoFind) + if err != nil { + return nil, status.Errorf(codes.Internal, "failed to list memos: %v", err) + } + userStatsMap := map[string]*v1pb.UserStats{} + for _, memo := range memos { + creator := fmt.Sprintf("%s%d", UserNamePrefix, memo.CreatorID) + if _, ok := userStatsMap[creator]; !ok { + userStatsMap[creator] = &v1pb.UserStats{ + Name: creator, + MemoDisplayTimestamps: []*timestamppb.Timestamp{}, + MemoTypeStats: &v1pb.UserStats_MemoTypeStats{}, + TagCount: map[string]int32{}, + } + } + displayTs := memo.CreatedTs + if workspaceMemoRelatedSetting.DisplayWithUpdateTime { + displayTs = memo.UpdatedTs } + userStats := userStatsMap[creator] + userStats.MemoDisplayTimestamps = append(userStats.MemoDisplayTimestamps, timestamppb.New(time.Unix(displayTs, 0))) + // Handle duplicated tags. + for _, tag := range memo.Payload.Tags { + userStats.TagCount[tag]++ + } + if memo.Payload.Property.GetHasLink() { + userStats.MemoTypeStats.LinkCount++ + } + if memo.Payload.Property.GetHasCode() { + userStats.MemoTypeStats.CodeCount++ + } + if memo.Payload.Property.GetHasTaskList() { + userStats.MemoTypeStats.TodoCount++ + } + if memo.Payload.Property.GetHasIncompleteTasks() { + userStats.MemoTypeStats.UndoCount++ + } + } + userStatsList := []*v1pb.UserStats{} + for _, userStats := range userStatsMap { userStatsList = append(userStatsList, userStats) } return &v1pb.ListAllUserStatsResponse{ @@ -60,39 +106,21 @@ func (s *APIV1Service) GetUserStats(ctx context.Context, request *v1pb.GetUserSt // Exclude comments by default. ExcludeComments: true, ExcludeContent: true, - } - if err := s.buildMemoFindWithFilter(ctx, memoFind, request.Filter); err != nil { - return nil, status.Errorf(codes.InvalidArgument, "failed to build find memos with filter: %v", err) + CreatorID: &userID, } currentUser, err := s.GetCurrentUser(ctx) if err != nil { return nil, status.Errorf(codes.Internal, "failed to get user: %v", err) } - if len(memoFind.VisibilityList) == 0 { - visibilities := []store.Visibility{store.Public} - if currentUser != nil { - visibilities = append(visibilities, store.Protected) - if currentUser.ID == user.ID { - visibilities = append(visibilities, store.Private) - } - } - memoFind.VisibilityList = visibilities - } else { - if slices.Contains(memoFind.VisibilityList, store.Private) { - if currentUser == nil || currentUser.ID != user.ID { - return nil, status.Errorf(codes.PermissionDenied, "permission denied") - } - } - if slices.Contains(memoFind.VisibilityList, store.Protected) { - if currentUser == nil { - return nil, status.Errorf(codes.PermissionDenied, "permission denied") - } + visibilities := []store.Visibility{store.Public} + if currentUser != nil { + visibilities = append(visibilities, store.Protected) + if currentUser.ID == user.ID { + visibilities = append(visibilities, store.Private) } } - - // Override the creator ID. - memoFind.CreatorID = &user.ID + memoFind.VisibilityList = visibilities memos, err := s.Store.ListMemos(ctx, memoFind) if err != nil { return nil, status.Errorf(codes.Internal, "failed to list memos: %v", err) diff --git a/web/src/components/ExploreSidebar/ExploreSidebar.tsx b/web/src/components/ExploreSidebar/ExploreSidebar.tsx index b6beceae..0265d73d 100644 --- a/web/src/components/ExploreSidebar/ExploreSidebar.tsx +++ b/web/src/components/ExploreSidebar/ExploreSidebar.tsx @@ -1,7 +1,6 @@ import clsx from "clsx"; import useDebounce from "react-use/lib/useDebounce"; import SearchBar from "@/components/SearchBar"; -import useCurrentUser from "@/hooks/useCurrentUser"; import { useUserStatsStore } from "@/store/v1"; import TagsSection from "../HomeSidebar/TagsSection"; import StatisticsView from "../StatisticsView"; @@ -11,13 +10,11 @@ interface Props { } const ExploreSidebar = (props: Props) => { - const currentUser = useCurrentUser(); const userStatsStore = useUserStatsStore(); useDebounce( async () => { - const filters = [`state == "NORMAL"`, `visibilities == [${currentUser ? "'PUBLIC', 'PROTECTED'" : "'PUBLIC'"}]`]; - userStatsStore.listUserStats(undefined, filters.join(" && ")); + userStatsStore.listUserStats(); }, 300, [], diff --git a/web/src/components/HomeSidebar/HomeSidebar.tsx b/web/src/components/HomeSidebar/HomeSidebar.tsx index cb5c18d0..6f99c188 100644 --- a/web/src/components/HomeSidebar/HomeSidebar.tsx +++ b/web/src/components/HomeSidebar/HomeSidebar.tsx @@ -17,8 +17,7 @@ const HomeSidebar = (props: Props) => { useDebounce( async () => { - const filters = [`state == "NORMAL"`, `creator == "${currentUser.name}"`]; - await userStatsStore.listUserStats(currentUser.name, filters.join(" && ")); + await userStatsStore.listUserStats(currentUser.name); }, 300, [memoList.size(), currentUser], diff --git a/web/src/store/v1/userStats.ts b/web/src/store/v1/userStats.ts index 8fc93bf1..8f48c1d8 100644 --- a/web/src/store/v1/userStats.ts +++ b/web/src/store/v1/userStats.ts @@ -20,15 +20,15 @@ export const useUserStatsStore = create( combine(getDefaultState(), (set, get) => ({ setState: (state: State) => set(state), getState: () => get(), - listUserStats: async (user?: string, filter?: string) => { + listUserStats: async (user?: string) => { const userStatsByName: Record = {}; if (!user) { - const { userStats } = await userServiceClient.listAllUserStats({ filter }); + const { userStats } = await userServiceClient.listAllUserStats({}); for (const stats of userStats) { userStatsByName[stats.name] = stats; } } else { - const userStats = await userServiceClient.getUserStats({ name: user, filter }); + const userStats = await userServiceClient.getUserStats({ name: user }); userStatsByName[user] = userStats; } set({ stateId: uniqueId(), userStatsByName });