Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
88f1deb
Start working on date utility service
arvid-e Oct 16, 2025
cb637a3
Calculate and return current week number
arvid-e Oct 20, 2025
4bbe0e5
Change week number function to return week id instead
arvid-e Oct 20, 2025
61abd77
Add date utilities to util folder
arvid-e Oct 22, 2025
7039382
Simplify date util function using date-fns and make simple tests
arvid-e Oct 27, 2025
4fcf5ab
Simplify date util function using date-fns and make simple tests
arvid-e Oct 27, 2025
4ecf1e3
Fix biome error
arvid-e Oct 27, 2025
6736108
Improve naming in date util function
arvid-e Oct 28, 2025
2bcd825
Fix lint error
arvid-e Oct 28, 2025
f7f6421
Update test with new date util names
arvid-e Oct 28, 2025
93bcb29
Try fixing lint error
arvid-e Oct 28, 2025
3b2a712
Try fixing lint error
arvid-e Oct 29, 2025
e527092
Indenting is getting warning in both cases
arvid-e Oct 29, 2025
0eb0603
Change to 4 spaces
arvid-e Nov 11, 2025
9e52f83
Test 2 spaces
arvid-e Nov 11, 2025
7fbf2b7
Update eslint with utils to avoid lint contradiction
arvid-e Nov 11, 2025
0fbec50
Update activity hook to return populated activities
arvid-e Nov 18, 2025
1809729
Update RecentActivity component to include page path
arvid-e Nov 18, 2025
f283ddc
Add more activity interfaces
arvid-e Nov 18, 2025
5ed63a1
List page path inside the ActivityListItem component
arvid-e Nov 18, 2025
0770fa4
Increase space between path and action in log entries
arvid-e Nov 18, 2025
3153a80
Add missing semi colons
arvid-e Nov 18, 2025
505500a
Remove incorrect semi colons
arvid-e Nov 18, 2025
417eecd
Align timestamp with action name
arvid-e Nov 19, 2025
7925a63
Remove tailwind darkmode utility
arvid-e Nov 21, 2025
8bd8cba
Remove contribution graph files
arvid-e Nov 21, 2025
eb3c6dd
Remove contribution graph from eslint ignore pattern
arvid-e Nov 21, 2025
9083307
Only display the name of allowed pages
arvid-e Nov 27, 2025
4d2c0a5
Move type definitions together
arvid-e Nov 27, 2025
2e3f65b
Merge pull request #10557 from growilabs/imprv/174459-only-display-pu…
yuki-takei Dec 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file not shown.
95 changes: 82 additions & 13 deletions apps/app/src/client/components/RecentActivity/ActivityListItem.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { formatDistanceToNow } from 'date-fns';
import { useTranslation } from 'next-i18next';
import { type Locale } from 'date-fns/locale';
import { getLocale } from '~/server/util/locale-utils';
import type { ActivityHasUserId, SupportedActivityActionType } from '~/interfaces/activity';
import { useTranslation } from 'next-i18next';

import type { SupportedActivityActionType, ActivityHasTargetPage } from '~/interfaces/activity';
import { ActivityLogActions } from '~/interfaces/activity';
import { getLocale } from '~/server/util/locale-utils';


export const ActivityActionTranslationMap: Record<
Expand Down Expand Up @@ -36,6 +37,18 @@ export const IconActivityTranslationMap: Record<
[ActivityLogActions.ACTION_COMMENT_CREATE]: 'comment',
};

type ActivityListItemProps = {
activity: ActivityHasTargetPage,
}

type AllowPageDisplayPayload = {
grant: number | undefined,
status: string,
wip: boolean,
deletedAt?: Date,
path: string,
}

const translateAction = (action: SupportedActivityActionType): string => {
return ActivityActionTranslationMap[action] || 'unknown_action';
};
Expand All @@ -53,29 +66,85 @@ const calculateTimePassed = (date: Date, locale: Locale): string => {
return timePassed;
};

const pageAllowedForDisplay = (allowDisplayPayload: AllowPageDisplayPayload): boolean => {
const {
grant, status, wip, deletedAt,
} = allowDisplayPayload;
if (grant !== 1) return false;

if (status !== 'published') return false;

if (wip) return false;

if (deletedAt) return false;

return true;
};

const setPath = (path: string, allowed: boolean): string => {
if (allowed) return path;

export const ActivityListItem = ({ activity }: { activity: ActivityHasUserId }): JSX.Element => {
return '';
};


export const ActivityListItem = ({ props }: { props: ActivityListItemProps }): JSX.Element => {
const { t, i18n } = useTranslation();
const currentLangCode = i18n.language;
const dateFnsLocale = getLocale(currentLangCode);

const { activity } = props;

const {
path, grant, status, wip, deletedAt,
} = activity.target;


const allowDisplayPayload: AllowPageDisplayPayload = {
grant,
status,
wip,
deletedAt,
path,
};

const isPageAllowed = pageAllowedForDisplay(allowDisplayPayload);

const action = activity.action as SupportedActivityActionType;
const keyToTranslate = translateAction(action);
const fullKeyPath = `user_home_page.${keyToTranslate}`;

return (
<div className="activity-row">
<p className="mb-1">
<span className="material-symbols-outlined me-2">{setIcon(action)}</span>

<span className="dark:text-white">
{' '}{t(fullKeyPath)}
<div className="d-flex align-items-center">
<span className="material-symbols-outlined me-2 flex-shrink-0">
{setIcon(action)}
</span>

<span className="text-secondary small ms-3">
{calculateTimePassed(activity.createdAt, dateFnsLocale)}
</span>
</p>
<div className="flex-grow-1 ms-2">
<div className="activity-path-line mb-0">
<a
href={setPath(path, isPageAllowed)}
className="activity-target-link fw-bold text-wrap d-block"
>
<span>
{setPath(path, isPageAllowed)}
</span>
</a>
</div>

<div className="activity-details-line d-flex">
<span>
{t(fullKeyPath)}
</span>

<span className="text-secondary small ms-3 align-self-center">
{calculateTimePassed(activity.createdAt, dateFnsLocale)}
</span>

</div>
</div>
</div>
</div>
);
};
14 changes: 8 additions & 6 deletions apps/app/src/client/components/RecentActivity/RecentActivity.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import React, {
} from 'react';

import { toastError } from '~/client/util/toastr';
import type { IActivityHasId, ActivityHasUserId } from '~/interfaces/activity';
import type { IActivityHasId, ActivityHasTargetPage } from '~/interfaces/activity';
import { useSWRxRecentActivity } from '~/stores/recent-activity';
import loggerFactory from '~/utils/logger';

Expand All @@ -18,15 +18,17 @@ type RecentActivityProps = {
userId: string,
}

const hasUser = (activity: IActivityHasId): activity is ActivityHasUserId => {
const hasTargetPage = (activity: IActivityHasId): activity is ActivityHasTargetPage => {
return activity.user != null
&& typeof activity.user === 'object';
&& typeof activity.user === 'object'
&& activity.target != null
&& typeof activity.target === 'object';
};

export const RecentActivity = (props: RecentActivityProps): JSX.Element => {
const { userId } = props;

const [activities, setActivities] = useState<ActivityHasUserId[]>([]);
const [activities, setActivities] = useState<ActivityHasTargetPage[]>([]);
const [activePage, setActivePage] = useState(1);
const [limit] = useState(10);
const [offset, setOffset] = useState(0);
Expand All @@ -49,7 +51,7 @@ export const RecentActivity = (props: RecentActivityProps): JSX.Element => {

if (paginatedData) {
const activitiesWithPages = paginatedData.docs
.filter(hasUser);
.filter(hasTargetPage);

setActivities(activitiesWithPages);
}
Expand All @@ -63,7 +65,7 @@ export const RecentActivity = (props: RecentActivityProps): JSX.Element => {
<ul className="page-list-ul page-list-ul-flat mb-3">
{activities.map(activity => (
<li key={`recent-activity-view:${activity._id}`} className="mt-4">
<ActivityListItem activity={activity} />
<ActivityListItem props={{ activity }} />
</li>
))}
</ul>
Expand Down
25 changes: 20 additions & 5 deletions apps/app/src/interfaces/activity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -683,18 +683,33 @@ export type IActivity = {
snapshot?: ISnapshot;
};

export type IActivityHasId = IActivity & HasObjectId;

export type ActivityHasUserId = IActivityHasId & {
user: IUserHasId;
};

export type IActivityHasId = IActivity & HasObjectId;
export type ActivityHasTargetPage = IActivityHasId & {
user: IUserHasId;
target: IPopulatedPageTarget;
};

import type { PageGrant } from '@growi/core';
export interface IPopulatedPageTarget {
_id: string;
path: string;
status: string;
grant?: PageGrant;
wip: boolean;
deletedAt: Date;
}

export interface PopulatedUserActivitiesResult {
serializedPaginationResult: PaginateResult<ActivityHasTargetPage>;
}

export type ISearchFilter = {
usernames?: string[];
dates?: { startDate: string | null; endDate: string | null };
actions?: SupportedActionType[];
};

export interface UserActivitiesResult {
serializedPaginationResult: PaginateResult<IActivityHasId>;
}
8 changes: 4 additions & 4 deletions apps/app/src/stores/recent-activity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,23 @@ import useSWRImmutable from 'swr/immutable';

import { apiv3Get } from '~/client/util/apiv3-client';
import type {
IActivityHasId,
UserActivitiesResult,
ActivityHasTargetPage,
PopulatedUserActivitiesResult,
} from '~/interfaces/activity';
import type { PaginateResult } from '~/interfaces/mongoose-utils';

export const useSWRxRecentActivity = (
limit?: number,
offset?: number,
targetUserId?: string,
): SWRResponse<PaginateResult<IActivityHasId>, Error> => {
): SWRResponse<PaginateResult<ActivityHasTargetPage>, Error> => {
const shouldFetch = targetUserId && targetUserId.length > 0;
const key = shouldFetch
? ['/user-activities', limit, offset, targetUserId]
: null;

const fetcher = ([endpoint, limitParam, offsetParam, targetUserIdParam]) => {
const promise = apiv3Get<UserActivitiesResult>(endpoint, {
const promise = apiv3Get<PopulatedUserActivitiesResult>(endpoint, {
limit: limitParam,
offset: offsetParam,
targetUserId: targetUserIdParam,
Expand Down
Loading