mirror of
https://github.com/usememos/memos.git
synced 2025-06-05 22:09:59 +02:00
refactor: update list user stats
This commit is contained in:
@ -198,11 +198,7 @@ message UserStats {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
message ListAllUserStatsRequest {
|
message ListAllUserStatsRequest {}
|
||||||
// Filter is used to filter memos to calculate stats.
|
|
||||||
// Same as `ListMemosRequest.filter`.
|
|
||||||
string filter = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ListAllUserStatsResponse {
|
message ListAllUserStatsResponse {
|
||||||
repeated UserStats user_stats = 1;
|
repeated UserStats user_stats = 1;
|
||||||
@ -212,10 +208,6 @@ message GetUserStatsRequest {
|
|||||||
// The name of the user.
|
// The name of the user.
|
||||||
// Format: users/{user}.
|
// Format: users/{user}.
|
||||||
string name = 1;
|
string name = 1;
|
||||||
|
|
||||||
// Filter is used to filter memos to calculate stats.
|
|
||||||
// Same as `ListMemosRequest.filter`.
|
|
||||||
string filter = 2;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message UserSetting {
|
message UserSetting {
|
||||||
|
@ -3,7 +3,6 @@ package v1
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"slices"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@ -16,19 +15,66 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (s *APIV1Service) ListAllUserStats(ctx context.Context, request *v1pb.ListAllUserStatsRequest) (*v1pb.ListAllUserStatsResponse, error) {
|
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 {
|
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)
|
||||||
|
}
|
||||||
|
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{}
|
userStatsList := []*v1pb.UserStats{}
|
||||||
for _, user := range users {
|
for _, userStats := range userStatsMap {
|
||||||
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)
|
|
||||||
}
|
|
||||||
userStatsList = append(userStatsList, userStats)
|
userStatsList = append(userStatsList, userStats)
|
||||||
}
|
}
|
||||||
return &v1pb.ListAllUserStatsResponse{
|
return &v1pb.ListAllUserStatsResponse{
|
||||||
@ -60,39 +106,21 @@ func (s *APIV1Service) GetUserStats(ctx context.Context, request *v1pb.GetUserSt
|
|||||||
// Exclude comments by default.
|
// Exclude comments by default.
|
||||||
ExcludeComments: true,
|
ExcludeComments: true,
|
||||||
ExcludeContent: true,
|
ExcludeContent: true,
|
||||||
}
|
CreatorID: &userID,
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
currentUser, err := s.GetCurrentUser(ctx)
|
currentUser, err := s.GetCurrentUser(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Errorf(codes.Internal, "failed to get user: %v", err)
|
return nil, status.Errorf(codes.Internal, "failed to get user: %v", err)
|
||||||
}
|
}
|
||||||
if len(memoFind.VisibilityList) == 0 {
|
visibilities := []store.Visibility{store.Public}
|
||||||
visibilities := []store.Visibility{store.Public}
|
if currentUser != nil {
|
||||||
if currentUser != nil {
|
visibilities = append(visibilities, store.Protected)
|
||||||
visibilities = append(visibilities, store.Protected)
|
if currentUser.ID == user.ID {
|
||||||
if currentUser.ID == user.ID {
|
visibilities = append(visibilities, store.Private)
|
||||||
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")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
memoFind.VisibilityList = visibilities
|
||||||
// Override the creator ID.
|
|
||||||
memoFind.CreatorID = &user.ID
|
|
||||||
memos, err := s.Store.ListMemos(ctx, memoFind)
|
memos, err := s.Store.ListMemos(ctx, memoFind)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Errorf(codes.Internal, "failed to list memos: %v", err)
|
return nil, status.Errorf(codes.Internal, "failed to list memos: %v", err)
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import useDebounce from "react-use/lib/useDebounce";
|
import useDebounce from "react-use/lib/useDebounce";
|
||||||
import SearchBar from "@/components/SearchBar";
|
import SearchBar from "@/components/SearchBar";
|
||||||
import useCurrentUser from "@/hooks/useCurrentUser";
|
|
||||||
import { useUserStatsStore } from "@/store/v1";
|
import { useUserStatsStore } from "@/store/v1";
|
||||||
import TagsSection from "../HomeSidebar/TagsSection";
|
import TagsSection from "../HomeSidebar/TagsSection";
|
||||||
import StatisticsView from "../StatisticsView";
|
import StatisticsView from "../StatisticsView";
|
||||||
@ -11,13 +10,11 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ExploreSidebar = (props: Props) => {
|
const ExploreSidebar = (props: Props) => {
|
||||||
const currentUser = useCurrentUser();
|
|
||||||
const userStatsStore = useUserStatsStore();
|
const userStatsStore = useUserStatsStore();
|
||||||
|
|
||||||
useDebounce(
|
useDebounce(
|
||||||
async () => {
|
async () => {
|
||||||
const filters = [`state == "NORMAL"`, `visibilities == [${currentUser ? "'PUBLIC', 'PROTECTED'" : "'PUBLIC'"}]`];
|
userStatsStore.listUserStats();
|
||||||
userStatsStore.listUserStats(undefined, filters.join(" && "));
|
|
||||||
},
|
},
|
||||||
300,
|
300,
|
||||||
[],
|
[],
|
||||||
|
@ -17,8 +17,7 @@ const HomeSidebar = (props: Props) => {
|
|||||||
|
|
||||||
useDebounce(
|
useDebounce(
|
||||||
async () => {
|
async () => {
|
||||||
const filters = [`state == "NORMAL"`, `creator == "${currentUser.name}"`];
|
await userStatsStore.listUserStats(currentUser.name);
|
||||||
await userStatsStore.listUserStats(currentUser.name, filters.join(" && "));
|
|
||||||
},
|
},
|
||||||
300,
|
300,
|
||||||
[memoList.size(), currentUser],
|
[memoList.size(), currentUser],
|
||||||
|
@ -20,15 +20,15 @@ export const useUserStatsStore = create(
|
|||||||
combine(getDefaultState(), (set, get) => ({
|
combine(getDefaultState(), (set, get) => ({
|
||||||
setState: (state: State) => set(state),
|
setState: (state: State) => set(state),
|
||||||
getState: () => get(),
|
getState: () => get(),
|
||||||
listUserStats: async (user?: string, filter?: string) => {
|
listUserStats: async (user?: string) => {
|
||||||
const userStatsByName: Record<string, UserStats> = {};
|
const userStatsByName: Record<string, UserStats> = {};
|
||||||
if (!user) {
|
if (!user) {
|
||||||
const { userStats } = await userServiceClient.listAllUserStats({ filter });
|
const { userStats } = await userServiceClient.listAllUserStats({});
|
||||||
for (const stats of userStats) {
|
for (const stats of userStats) {
|
||||||
userStatsByName[stats.name] = stats;
|
userStatsByName[stats.name] = stats;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const userStats = await userServiceClient.getUserStats({ name: user, filter });
|
const userStats = await userServiceClient.getUserStats({ name: user });
|
||||||
userStatsByName[user] = userStats;
|
userStatsByName[user] = userStats;
|
||||||
}
|
}
|
||||||
set({ stateId: uniqueId(), userStatsByName });
|
set({ stateId: uniqueId(), userStatsByName });
|
||||||
|
Reference in New Issue
Block a user