Przeglądaj źródła

Merge pull request #6342 from weseek/feat/100596-show-user-picture

feat: Show user picture in Audit Log
Yuki Takei 3 lat temu
rodzic
commit
45714a2fb5

+ 1 - 0
packages/app/public/static/locales/en_US/admin/admin.json

@@ -529,6 +529,7 @@
     }
     }
   },
   },
   "audit_log_management": {
   "audit_log_management": {
+    "user": "User",
     "username": "Username",
     "username": "Username",
     "date": "Date",
     "date": "Date",
     "action": "Action",
     "action": "Action",

+ 1 - 0
packages/app/public/static/locales/ja_JP/admin/admin.json

@@ -528,6 +528,7 @@
     }
     }
   },
   },
   "audit_log_management": {
   "audit_log_management": {
+    "user": "ユーザー",
     "username": "ユーザー名",
     "username": "ユーザー名",
     "date": "日付",
     "date": "日付",
     "action": "アクション",
     "action": "アクション",

+ 1 - 0
packages/app/public/static/locales/zh_CN/admin/admin.json

@@ -538,6 +538,7 @@
     }
     }
   },
   },
   "audit_log_management": {
   "audit_log_management": {
+    "user": "用户",
     "username": "帐号",
     "username": "帐号",
     "date": "日期",
     "date": "日期",
     "action": "行动",
     "action": "行动",

+ 11 - 2
packages/app/src/components/Admin/AuditLog/ActivityTable.tsx

@@ -1,5 +1,7 @@
 import React, { FC } from 'react';
 import React, { FC } from 'react';
 
 
+import { pagePathUtils } from '@growi/core';
+import { UserPicture } from '@growi/ui';
 import { format } from 'date-fns';
 import { format } from 'date-fns';
 import { useTranslation } from 'react-i18next';
 import { useTranslation } from 'react-i18next';
 
 
@@ -21,7 +23,7 @@ export const ActivityTable : FC<Props> = (props: Props) => {
       <table className="table table-default table-bordered table-user-list">
       <table className="table table-default table-bordered table-user-list">
         <thead>
         <thead>
           <tr>
           <tr>
-            <th scope="col">{t('admin:audit_log_management.username')}</th>
+            <th scope="col">{t('admin:audit_log_management.user')}</th>
             <th scope="col">{t('admin:audit_log_management.date')}</th>
             <th scope="col">{t('admin:audit_log_management.date')}</th>
             <th scope="col">{t('admin:audit_log_management.action')}</th>
             <th scope="col">{t('admin:audit_log_management.action')}</th>
             <th scope="col">{t('admin:audit_log_management.ip')}</th>
             <th scope="col">{t('admin:audit_log_management.ip')}</th>
@@ -32,7 +34,14 @@ export const ActivityTable : FC<Props> = (props: Props) => {
           {props.activityList.map((activity) => {
           {props.activityList.map((activity) => {
             return (
             return (
               <tr data-testid="activity-table" key={activity._id}>
               <tr data-testid="activity-table" key={activity._id}>
-                <td>{activity.snapshot?.username}</td>
+                <td>
+                  { activity.user != null && (
+                    <>
+                      <UserPicture user={activity.user} className="picture rounded-circle" />
+                      <a className="ml-2" href={pagePathUtils.userPageRoot(activity.user)}>{activity.snapshot?.username}</a>
+                    </>
+                  )}
+                </td>
                 <td>{formatDate(activity.createdAt)}</td>
                 <td>{formatDate(activity.createdAt)}</td>
                 <td>{t(`admin:audit_log_action.${activity.action}`)}</td>
                 <td>{t(`admin:audit_log_action.${activity.action}`)}</td>
                 <td>{activity.ip}</td>
                 <td>{activity.ip}</td>

+ 0 - 12
packages/app/src/server/models/activity.ts

@@ -126,18 +126,6 @@ activitySchema.statics.updateByParameters = async function(activityId: string, p
   return activity;
   return activity;
 };
 };
 
 
-activitySchema.statics.getPaginatedActivity = async function(limit: number, offset: number, query) {
-  const paginateResult = await this.paginate(
-    query,
-    {
-      limit,
-      offset,
-      sort: { createdAt: -1 },
-    },
-  );
-  return paginateResult;
-};
-
 activitySchema.statics.findSnapshotUsernamesByUsernameRegexWithTotalCount = async function(
 activitySchema.statics.findSnapshotUsernamesByUsernameRegexWithTotalCount = async function(
     q: string, option: { sortOpt: number | string, offset: number, limit: number},
     q: string, option: { sortOpt: number | string, offset: number, limit: number},
 ): Promise<{usernames: string[], totalCount: number}> {
 ): Promise<{usernames: string[], totalCount: number}> {

+ 26 - 3
packages/app/src/server/routes/apiv3/activity.ts

@@ -2,12 +2,13 @@ import { parseISO, addMinutes, isValid } from 'date-fns';
 import express, { Request, Router } from 'express';
 import express, { Request, Router } from 'express';
 import { query } from 'express-validator';
 import { query } from 'express-validator';
 
 
-import { ISearchFilter } from '~/interfaces/activity';
+import { IActivity, ISearchFilter } from '~/interfaces/activity';
 import Activity from '~/server/models/activity';
 import Activity from '~/server/models/activity';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
 import Crowi from '../../crowi';
 import Crowi from '../../crowi';
 import { apiV3FormValidator } from '../../middlewares/apiv3-form-validator';
 import { apiV3FormValidator } from '../../middlewares/apiv3-form-validator';
+import { serializeUserSecurely } from '../../models/serializers/user-serializer';
 
 
 import { ApiV3Response } from './interfaces/apiv3-response';
 import { ApiV3Response } from './interfaces/apiv3-response';
 
 
@@ -92,8 +93,30 @@ module.exports = (crowi: Crowi): Router => {
     }
     }
 
 
     try {
     try {
-      const paginationResult = await Activity.getPaginatedActivity(limit, offset, query);
-      return res.apiv3({ paginationResult });
+      const paginateResult = await Activity.paginate(
+        query,
+        {
+          limit,
+          offset,
+          sort: { createdAt: -1 },
+          populate: 'user',
+        },
+      );
+
+      const User = crowi.model('User');
+      const serializedDocs = paginateResult.docs.map((doc: IActivity) => {
+        if (doc.user != null && doc.user instanceof User) {
+          doc.user = serializeUserSecurely(doc.user);
+        }
+        return doc;
+      });
+
+      const serializedPaginationResult = {
+        ...paginateResult,
+        docs: serializedDocs,
+      };
+
+      return res.apiv3({ serializedPaginationResult });
     }
     }
     catch (err) {
     catch (err) {
       logger.error('Failed to get paginated activity', err);
       logger.error('Failed to get paginated activity', err);

+ 1 - 1
packages/app/src/stores/activity.ts

@@ -13,6 +13,6 @@ export const useSWRxActivity = (limit?: number, offset?: number, searchFilter?:
   return useSWRImmutable(
   return useSWRImmutable(
     auditLogEnabled ? ['/activity', limit, offset, stringifiedSearchFilter] : null,
     auditLogEnabled ? ['/activity', limit, offset, stringifiedSearchFilter] : null,
     (endpoint, limit, offset, stringifiedSearchFilter) => apiv3Get(endpoint, { limit, offset, searchFilter: stringifiedSearchFilter })
     (endpoint, limit, offset, stringifiedSearchFilter) => apiv3Get(endpoint, { limit, offset, searchFilter: stringifiedSearchFilter })
-      .then(result => result.data.paginationResult),
+      .then(result => result.data.serializedPaginationResult),
   );
   );
 };
 };