Skip to content

Commit

Permalink
refactor: update list user stats
Browse files Browse the repository at this point in the history
  • Loading branch information
johnnyjoygh committed Jan 15, 2025
1 parent 8b65d24 commit c76ab87
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 54 deletions.
10 changes: 1 addition & 9 deletions proto/api/v1/user_service.proto
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 {
Expand Down
100 changes: 64 additions & 36 deletions server/router/api/v1/user_service_stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package v1
import (
"context"
"fmt"
"slices"
"time"

"github.com/pkg/errors"
Expand All @@ -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{
Expand Down Expand Up @@ -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)
Expand Down
5 changes: 1 addition & 4 deletions web/src/components/ExploreSidebar/ExploreSidebar.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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,
[],
Expand Down
3 changes: 1 addition & 2 deletions web/src/components/HomeSidebar/HomeSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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],
Expand Down
6 changes: 3 additions & 3 deletions web/src/store/v1/userStats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, UserStats> = {};
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 });
Expand Down

0 comments on commit c76ab87

Please sign in to comment.