-
Notifications
You must be signed in to change notification settings - Fork 3.6k
chore: added common component for project activity #6212
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,51 @@ | ||||||||||||||||||||||||||
| "use client"; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| import { FC, ReactNode } from "react"; | ||||||||||||||||||||||||||
| import { Network } from "lucide-react"; | ||||||||||||||||||||||||||
| // hooks | ||||||||||||||||||||||||||
| import { Tooltip } from "@plane/ui"; | ||||||||||||||||||||||||||
| import { renderFormattedTime, renderFormattedDate, calculateTimeAgo } from "@/helpers/date-time.helper"; | ||||||||||||||||||||||||||
| import { usePlatformOS } from "@/hooks/use-platform-os"; | ||||||||||||||||||||||||||
| import { TProjectActivity } from "@/plane-web/types"; | ||||||||||||||||||||||||||
| import { User } from "./user"; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| type TActivityBlockComponent = { | ||||||||||||||||||||||||||
| icon?: ReactNode; | ||||||||||||||||||||||||||
| activity: TProjectActivity; | ||||||||||||||||||||||||||
| ends: "top" | "bottom" | undefined; | ||||||||||||||||||||||||||
| children: ReactNode; | ||||||||||||||||||||||||||
| customUserName?: string; | ||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| export const ActivityBlockComponent: FC<TActivityBlockComponent> = (props) => { | ||||||||||||||||||||||||||
| const { icon, activity, ends, children, customUserName } = props; | ||||||||||||||||||||||||||
| // hooks | ||||||||||||||||||||||||||
| const { isMobile } = usePlatformOS(); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| if (!activity) return <></>; | ||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||
| <div | ||||||||||||||||||||||||||
| className={`relative flex items-center gap-3 text-xs ${ | ||||||||||||||||||||||||||
| ends === "top" ? `pb-2` : ends === "bottom" ? `pt-2` : `py-2` | ||||||||||||||||||||||||||
| }`} | ||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||
| <div className="absolute left-[13px] top-0 bottom-0 w-0.5 bg-custom-background-80" aria-hidden /> | ||||||||||||||||||||||||||
| <div className="flex-shrink-0 ring-6 w-7 h-7 rounded-full overflow-hidden flex justify-center items-center z-[4] bg-custom-background-80 text-custom-text-200"> | ||||||||||||||||||||||||||
| {icon ? icon : <Network className="w-3.5 h-3.5" />} | ||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||
|
Comment on lines
+32
to
+35
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Improve accessibility for activity timeline The timeline implementation could benefit from better accessibility attributes. Add proper ARIA attributes and semantic HTML: - <div className="absolute left-[13px] top-0 bottom-0 w-0.5 bg-custom-background-80" aria-hidden />
+ <div
+ className="absolute left-[13px] top-0 bottom-0 w-0.5 bg-custom-background-80"
+ role="separator"
+ aria-label="Activity timeline"
+ />📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||
| <div className="w-full truncate text-custom-text-200"> | ||||||||||||||||||||||||||
| <User activity={activity} customUserName={customUserName} /> {children} | ||||||||||||||||||||||||||
| <div className="mt-1"> | ||||||||||||||||||||||||||
| <Tooltip | ||||||||||||||||||||||||||
| isMobile={isMobile} | ||||||||||||||||||||||||||
| tooltipContent={`${renderFormattedDate(activity.created_at)}, ${renderFormattedTime(activity.created_at)}`} | ||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||
| <span className="whitespace-nowrap text-custom-text-350 font-medium"> | ||||||||||||||||||||||||||
| {calculateTimeAgo(activity.created_at)} | ||||||||||||||||||||||||||
| </span> | ||||||||||||||||||||||||||
| </Tooltip> | ||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,30 @@ | ||||||||||||||
| "use client"; | ||||||||||||||
|
|
||||||||||||||
| import { FC } from "react"; | ||||||||||||||
| import { observer } from "mobx-react"; | ||||||||||||||
|
|
||||||||||||||
| import { TProjectActivity } from "@/plane-web/types"; | ||||||||||||||
| import { ActivityBlockComponent } from "./activity-block"; | ||||||||||||||
| import { iconsMap, messages } from "./helper"; | ||||||||||||||
|
|
||||||||||||||
| type TActivityItem = { | ||||||||||||||
| activity: TProjectActivity; | ||||||||||||||
| showProject?: boolean; | ||||||||||||||
| ends?: "top" | "bottom" | undefined; | ||||||||||||||
| }; | ||||||||||||||
|
|
||||||||||||||
| export const ActivityItem: FC<TActivityItem> = observer((props) => { | ||||||||||||||
| const { activity, showProject = true, ends } = props; | ||||||||||||||
|
|
||||||||||||||
| if (!activity) return null; | ||||||||||||||
|
|
||||||||||||||
| const activityType = activity.field; | ||||||||||||||
| const { message, customUserName } = messages(activity); | ||||||||||||||
| const icon = iconsMap[activityType] || iconsMap.default; | ||||||||||||||
|
|
||||||||||||||
| return ( | ||||||||||||||
| <ActivityBlockComponent icon={icon} activity={activity} ends={ends} customUserName={customUserName}> | ||||||||||||||
| <>{message}</> | ||||||||||||||
| </ActivityBlockComponent> | ||||||||||||||
|
Comment on lines
+26
to
+28
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Remove unnecessary fragment and add error handling
Suggested improvements: <ActivityBlockComponent icon={icon} activity={activity} ends={ends} customUserName={customUserName}>
- <>{message}</>
+ {message}
</ActivityBlockComponent>Also, consider wrapping the message generation in a try-catch: - const { message, customUserName } = messages(activity);
+ let message, customUserName;
+ try {
+ ({ message, customUserName } = messages(activity));
+ } catch (error) {
+ console.error('Failed to generate activity message:', error);
+ message = 'Activity details unavailable';
+ customUserName = undefined;
+ }📝 Committable suggestion
Suggested change
🧰 Tools🪛 Biome (1.9.4)[error] 27-27: Avoid using unnecessary Fragment. A fragment is redundant if it contains only one child, or if it is the child of a html element, and is not a keyed fragment. (lint/complexity/noUselessFragments) |
||||||||||||||
| ); | ||||||||||||||
| }); | ||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,279 @@ | ||||||||||||||||||||||||||||||||||||||||||||||
| import { ReactNode } from "react"; | ||||||||||||||||||||||||||||||||||||||||||||||
| import { | ||||||||||||||||||||||||||||||||||||||||||||||
| Signal, | ||||||||||||||||||||||||||||||||||||||||||||||
| RotateCcw, | ||||||||||||||||||||||||||||||||||||||||||||||
| Network, | ||||||||||||||||||||||||||||||||||||||||||||||
| Link as LinkIcon, | ||||||||||||||||||||||||||||||||||||||||||||||
| Calendar, | ||||||||||||||||||||||||||||||||||||||||||||||
| Tag, | ||||||||||||||||||||||||||||||||||||||||||||||
| Inbox, | ||||||||||||||||||||||||||||||||||||||||||||||
| AlignLeft, | ||||||||||||||||||||||||||||||||||||||||||||||
| Users, | ||||||||||||||||||||||||||||||||||||||||||||||
| Paperclip, | ||||||||||||||||||||||||||||||||||||||||||||||
| Type, | ||||||||||||||||||||||||||||||||||||||||||||||
| Triangle, | ||||||||||||||||||||||||||||||||||||||||||||||
| FileText, | ||||||||||||||||||||||||||||||||||||||||||||||
| Globe, | ||||||||||||||||||||||||||||||||||||||||||||||
| Hash, | ||||||||||||||||||||||||||||||||||||||||||||||
| Clock, | ||||||||||||||||||||||||||||||||||||||||||||||
| Bell, | ||||||||||||||||||||||||||||||||||||||||||||||
| LayoutGrid, | ||||||||||||||||||||||||||||||||||||||||||||||
| GitBranch, | ||||||||||||||||||||||||||||||||||||||||||||||
| Timer, | ||||||||||||||||||||||||||||||||||||||||||||||
| ListTodo, | ||||||||||||||||||||||||||||||||||||||||||||||
| Layers, | ||||||||||||||||||||||||||||||||||||||||||||||
| } from "lucide-react"; | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // components | ||||||||||||||||||||||||||||||||||||||||||||||
| import { ArchiveIcon, DoubleCircleIcon, ContrastIcon, DiceIcon, Intake } from "@plane/ui"; | ||||||||||||||||||||||||||||||||||||||||||||||
| import { TProjectActivity } from "@/plane-web/types"; | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| type ActivityIconMap = { | ||||||||||||||||||||||||||||||||||||||||||||||
| [key: string]: ReactNode; | ||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||
| export const iconsMap: ActivityIconMap = { | ||||||||||||||||||||||||||||||||||||||||||||||
| priority: <Signal size={14} className="text-custom-text-200" />, | ||||||||||||||||||||||||||||||||||||||||||||||
| archived_at: <ArchiveIcon className="h-3.5 w-3.5 text-custom-text-200" />, | ||||||||||||||||||||||||||||||||||||||||||||||
| restored: <RotateCcw className="h-3.5 w-3.5 text-custom-text-200" />, | ||||||||||||||||||||||||||||||||||||||||||||||
| link: <LinkIcon className="h-3.5 w-3.5 text-custom-text-200" />, | ||||||||||||||||||||||||||||||||||||||||||||||
| start_date: <Calendar className="h-3.5 w-3.5 text-custom-text-200" />, | ||||||||||||||||||||||||||||||||||||||||||||||
| target_date: <Calendar className="h-3.5 w-3.5 text-custom-text-200" />, | ||||||||||||||||||||||||||||||||||||||||||||||
| label: <Tag className="h-3.5 w-3.5 text-custom-text-200" />, | ||||||||||||||||||||||||||||||||||||||||||||||
| inbox: <Inbox className="h-3.5 w-3.5 text-custom-text-200" />, | ||||||||||||||||||||||||||||||||||||||||||||||
| description: <AlignLeft className="h-3.5 w-3.5 text-custom-text-200" />, | ||||||||||||||||||||||||||||||||||||||||||||||
| assignee: <Users className="h-3.5 w-3.5 text-custom-text-200" />, | ||||||||||||||||||||||||||||||||||||||||||||||
| attachment: <Paperclip className="h-3.5 w-3.5 text-custom-text-200" />, | ||||||||||||||||||||||||||||||||||||||||||||||
| name: <Type className="h-3.5 w-3.5 text-custom-text-200" />, | ||||||||||||||||||||||||||||||||||||||||||||||
| state: <DoubleCircleIcon className="h-4 w-4 flex-shrink-0 text-custom-text-200" />, | ||||||||||||||||||||||||||||||||||||||||||||||
| estimate: <Triangle size={14} className="text-custom-text-200" />, | ||||||||||||||||||||||||||||||||||||||||||||||
| cycle: <ContrastIcon className="h-4 w-4 flex-shrink-0 text-custom-text-200" />, | ||||||||||||||||||||||||||||||||||||||||||||||
| module: <DiceIcon className="h-4 w-4 flex-shrink-0 text-custom-text-200" />, | ||||||||||||||||||||||||||||||||||||||||||||||
| page: <FileText className="h-3.5 w-3.5 text-custom-text-200" />, | ||||||||||||||||||||||||||||||||||||||||||||||
| network: <Globe className="h-3.5 w-3.5 text-custom-text-200" />, | ||||||||||||||||||||||||||||||||||||||||||||||
| identifier: <Hash className="h-3.5 w-3.5 text-custom-text-200" />, | ||||||||||||||||||||||||||||||||||||||||||||||
| timezone: <Clock className="h-3.5 w-3.5 text-custom-text-200" />, | ||||||||||||||||||||||||||||||||||||||||||||||
| is_project_updates_enabled: <Bell className="h-3.5 w-3.5 text-custom-text-200" />, | ||||||||||||||||||||||||||||||||||||||||||||||
| is_epic_enabled: <LayoutGrid className="h-3.5 w-3.5 text-custom-text-200" />, | ||||||||||||||||||||||||||||||||||||||||||||||
| is_workflow_enabled: <GitBranch className="h-3.5 w-3.5 text-custom-text-200" />, | ||||||||||||||||||||||||||||||||||||||||||||||
| is_time_tracking_enabled: <Timer className="h-3.5 w-3.5 text-custom-text-200" />, | ||||||||||||||||||||||||||||||||||||||||||||||
| is_issue_type_enabled: <ListTodo className="h-3.5 w-3.5 text-custom-text-200" />, | ||||||||||||||||||||||||||||||||||||||||||||||
| default: <Network className="h-3.5 w-3.5 text-custom-text-200" />, | ||||||||||||||||||||||||||||||||||||||||||||||
| module_view: <DiceIcon className="h-3.5 w-3.5 text-custom-text-200" />, | ||||||||||||||||||||||||||||||||||||||||||||||
| cycle_view: <ContrastIcon className="h-3.5 w-3.5 text-custom-text-200" />, | ||||||||||||||||||||||||||||||||||||||||||||||
| issue_views_view: <Layers className="h-3.5 w-3.5 text-custom-text-200" />, | ||||||||||||||||||||||||||||||||||||||||||||||
| page_view: <FileText className="h-3.5 w-3.5 text-custom-text-200" />, | ||||||||||||||||||||||||||||||||||||||||||||||
| intake_view: <Intake className="h-3.5 w-3.5 text-custom-text-200" />, | ||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| export const messages = (activity: TProjectActivity): { message: string | ReactNode; customUserName?: string } => { | ||||||||||||||||||||||||||||||||||||||||||||||
| const activityType = activity.field; | ||||||||||||||||||||||||||||||||||||||||||||||
| const newValue = activity.new_value; | ||||||||||||||||||||||||||||||||||||||||||||||
| const oldValue = activity.old_value; | ||||||||||||||||||||||||||||||||||||||||||||||
| const verb = activity.verb; | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| const getBooleanActionText = (value: string) => { | ||||||||||||||||||||||||||||||||||||||||||||||
| if (value === "true") return "enabled"; | ||||||||||||||||||||||||||||||||||||||||||||||
| if (value === "false") return "disabled"; | ||||||||||||||||||||||||||||||||||||||||||||||
| return verb; | ||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| switch (activityType) { | ||||||||||||||||||||||||||||||||||||||||||||||
| case "priority": | ||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||
| message: ( | ||||||||||||||||||||||||||||||||||||||||||||||
| <> | ||||||||||||||||||||||||||||||||||||||||||||||
| set the priority to <span className="font-medium text-custom-text-100">{newValue || "none"}</span> | ||||||||||||||||||||||||||||||||||||||||||||||
| </> | ||||||||||||||||||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||
| case "archived_at": | ||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||
| message: newValue === "restore" ? "restored the project" : "archived the project", | ||||||||||||||||||||||||||||||||||||||||||||||
| customUserName: newValue === "archive" ? "Plane" : undefined, | ||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||
| case "name": | ||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||
| message: ( | ||||||||||||||||||||||||||||||||||||||||||||||
| <> | ||||||||||||||||||||||||||||||||||||||||||||||
| renamed the project to <span className="font-medium text-custom-text-100">{newValue}</span> | ||||||||||||||||||||||||||||||||||||||||||||||
| </> | ||||||||||||||||||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||
| case "description": | ||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||
| message: newValue ? "updated the project description" : "removed the project description", | ||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||
| case "start_date": | ||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||
| message: ( | ||||||||||||||||||||||||||||||||||||||||||||||
| <> | ||||||||||||||||||||||||||||||||||||||||||||||
| {newValue ? ( | ||||||||||||||||||||||||||||||||||||||||||||||
| <> | ||||||||||||||||||||||||||||||||||||||||||||||
| set the start date to <span className="font-medium text-custom-text-100">{newValue}</span> | ||||||||||||||||||||||||||||||||||||||||||||||
| </> | ||||||||||||||||||||||||||||||||||||||||||||||
| ) : ( | ||||||||||||||||||||||||||||||||||||||||||||||
| "removed the start date" | ||||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||||
| </> | ||||||||||||||||||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||
| case "target_date": | ||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||
| message: ( | ||||||||||||||||||||||||||||||||||||||||||||||
| <> | ||||||||||||||||||||||||||||||||||||||||||||||
| {newValue ? ( | ||||||||||||||||||||||||||||||||||||||||||||||
| <> | ||||||||||||||||||||||||||||||||||||||||||||||
| set the target date to <span className="font-medium text-custom-text-100">{newValue}</span> | ||||||||||||||||||||||||||||||||||||||||||||||
| </> | ||||||||||||||||||||||||||||||||||||||||||||||
| ) : ( | ||||||||||||||||||||||||||||||||||||||||||||||
| "removed the target date" | ||||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||||
| </> | ||||||||||||||||||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||
| case "state": | ||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||
| message: ( | ||||||||||||||||||||||||||||||||||||||||||||||
| <> | ||||||||||||||||||||||||||||||||||||||||||||||
| set the state to <span className="font-medium text-custom-text-100">{newValue || "none"}</span> | ||||||||||||||||||||||||||||||||||||||||||||||
| </> | ||||||||||||||||||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||
| case "estimate": | ||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||
| message: ( | ||||||||||||||||||||||||||||||||||||||||||||||
| <> | ||||||||||||||||||||||||||||||||||||||||||||||
| {newValue ? ( | ||||||||||||||||||||||||||||||||||||||||||||||
| <> | ||||||||||||||||||||||||||||||||||||||||||||||
| set the estimate point to <span className="font-medium text-custom-text-100">{newValue}</span> | ||||||||||||||||||||||||||||||||||||||||||||||
| </> | ||||||||||||||||||||||||||||||||||||||||||||||
| ) : ( | ||||||||||||||||||||||||||||||||||||||||||||||
| <> | ||||||||||||||||||||||||||||||||||||||||||||||
| removed the estimate point | ||||||||||||||||||||||||||||||||||||||||||||||
| {oldValue && ( | ||||||||||||||||||||||||||||||||||||||||||||||
| <> | ||||||||||||||||||||||||||||||||||||||||||||||
| {" "} | ||||||||||||||||||||||||||||||||||||||||||||||
| <span className="font-medium text-custom-text-100">{oldValue}</span> | ||||||||||||||||||||||||||||||||||||||||||||||
| </> | ||||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||||
| </> | ||||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||||
| </> | ||||||||||||||||||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||
| case "cycles": | ||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||
| message: ( | ||||||||||||||||||||||||||||||||||||||||||||||
| <> | ||||||||||||||||||||||||||||||||||||||||||||||
| <span> | ||||||||||||||||||||||||||||||||||||||||||||||
| {verb} this project {verb === "removed" ? "from" : "to"} the cycle{" "} | ||||||||||||||||||||||||||||||||||||||||||||||
| </span> | ||||||||||||||||||||||||||||||||||||||||||||||
| {verb !== "removed" ? ( | ||||||||||||||||||||||||||||||||||||||||||||||
| <a | ||||||||||||||||||||||||||||||||||||||||||||||
| href={`/${activity.workspace_detail?.slug}/projects/${activity.project}/cycles/${activity.new_identifier}`} | ||||||||||||||||||||||||||||||||||||||||||||||
| target="_blank" | ||||||||||||||||||||||||||||||||||||||||||||||
| rel="noopener noreferrer" | ||||||||||||||||||||||||||||||||||||||||||||||
| className="inline-flex font-medium text-custom-text-100" | ||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||
| {activity.new_value} | ||||||||||||||||||||||||||||||||||||||||||||||
| </a> | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+172
to
+179
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add error handling for navigation links The cycle link implementation could benefit from proper error handling for missing slugs or identifiers. Add proper checks: {verb !== "removed" && activity.workspace_detail?.slug && activity.new_identifier ? (
<a
href={`/${activity.workspace_detail.slug}/projects/${activity.project}/cycles/${activity.new_identifier}`}
target="_blank"
rel="noopener noreferrer"
className="inline-flex font-medium text-custom-text-100"
>
{activity.new_value}
</a>
) : (
<span className="font-medium text-custom-text-100">
{activity.new_value || "Unknown cycle"}
</span>
)}📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||
| ) : ( | ||||||||||||||||||||||||||||||||||||||||||||||
| <span className="font-medium text-custom-text-100">{activity.old_value || "Unknown cycle"}</span> | ||||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||||
| </> | ||||||||||||||||||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||
| case "modules": | ||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||
| message: ( | ||||||||||||||||||||||||||||||||||||||||||||||
| <> | ||||||||||||||||||||||||||||||||||||||||||||||
| <span> | ||||||||||||||||||||||||||||||||||||||||||||||
| {verb} this project {verb === "removed" ? "from" : "to"} the module{" "} | ||||||||||||||||||||||||||||||||||||||||||||||
| </span> | ||||||||||||||||||||||||||||||||||||||||||||||
| <span className="font-medium text-custom-text-100"> | ||||||||||||||||||||||||||||||||||||||||||||||
| {verb === "removed" ? oldValue : newValue || "Unknown module"} | ||||||||||||||||||||||||||||||||||||||||||||||
| </span> | ||||||||||||||||||||||||||||||||||||||||||||||
| </> | ||||||||||||||||||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||
| case "labels": | ||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||
| message: ( | ||||||||||||||||||||||||||||||||||||||||||||||
| <> | ||||||||||||||||||||||||||||||||||||||||||||||
| {verb} the label{" "} | ||||||||||||||||||||||||||||||||||||||||||||||
| <span className="font-medium text-custom-text-100">{newValue || oldValue || "Untitled label"}</span> | ||||||||||||||||||||||||||||||||||||||||||||||
| </> | ||||||||||||||||||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||
| case "inbox": | ||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||
| message: <>{newValue ? "enabled" : "disabled"} inbox</>, | ||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||
| case "page": | ||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||
| message: ( | ||||||||||||||||||||||||||||||||||||||||||||||
| <> | ||||||||||||||||||||||||||||||||||||||||||||||
| {newValue ? "created" : "removed"} the project page{" "} | ||||||||||||||||||||||||||||||||||||||||||||||
| <span className="font-medium text-custom-text-100">{newValue || oldValue || "Untitled page"}</span> | ||||||||||||||||||||||||||||||||||||||||||||||
| </> | ||||||||||||||||||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||
| case "network": | ||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||
| message: <>{newValue ? "enabled" : "disabled"} network access</>, | ||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||
| case "identifier": | ||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||
| message: ( | ||||||||||||||||||||||||||||||||||||||||||||||
| <> | ||||||||||||||||||||||||||||||||||||||||||||||
| updated project identifier to <span className="font-medium text-custom-text-100">{newValue || "none"}</span> | ||||||||||||||||||||||||||||||||||||||||||||||
| </> | ||||||||||||||||||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||
| case "timezone": | ||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||
| message: ( | ||||||||||||||||||||||||||||||||||||||||||||||
| <> | ||||||||||||||||||||||||||||||||||||||||||||||
| changed project timezone to{" "} | ||||||||||||||||||||||||||||||||||||||||||||||
| <span className="font-medium text-custom-text-100">{newValue || "default"}</span> | ||||||||||||||||||||||||||||||||||||||||||||||
| </> | ||||||||||||||||||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||
| case "module_view": | ||||||||||||||||||||||||||||||||||||||||||||||
| case "cycle_view": | ||||||||||||||||||||||||||||||||||||||||||||||
| case "issue_views_view": | ||||||||||||||||||||||||||||||||||||||||||||||
| case "page_view": | ||||||||||||||||||||||||||||||||||||||||||||||
| case "intake_view": | ||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||
| message: ( | ||||||||||||||||||||||||||||||||||||||||||||||
| <> | ||||||||||||||||||||||||||||||||||||||||||||||
| {getBooleanActionText(newValue)} {activityType.replace(/_view$/, "").replace(/_/g, " ")} view | ||||||||||||||||||||||||||||||||||||||||||||||
| </> | ||||||||||||||||||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||
| case "is_project_updates_enabled": | ||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||
| message: <>{getBooleanActionText(newValue)} project updates</>, | ||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||
| case "is_epic_enabled": | ||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||
| message: <>{getBooleanActionText(newValue)} epics</>, | ||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||
| case "is_workflow_enabled": | ||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||
| message: <>{getBooleanActionText(newValue)} custom workflow</>, | ||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||
| case "is_time_tracking_enabled": | ||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||
| message: <>{getBooleanActionText(newValue)} time tracking</>, | ||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||
| case "is_issue_type_enabled": | ||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||
| message: <>{getBooleanActionText(newValue)} issue types</>, | ||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||
| default: | ||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||
| message: `${verb} ${activityType.replace(/_/g, " ")} `, | ||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+68
to
+279
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Consider breaking down the message generation logic The message generation function is quite large and handles many cases. This could be split into smaller, more manageable functions. Consider breaking down the switch statement into separate handlers: const messageHandlers = {
priority: (activity: TProjectActivity) => ({
message: (
<>
set the priority to <span className="font-medium text-custom-text-100">{activity.new_value || "none"}</span>
</>
),
}),
// ... other handlers
};
export const messages = (activity: TProjectActivity) => {
const handler = messageHandlers[activity.field];
return handler ? handler(activity) : {
message: `${activity.verb} ${activity.field.replace(/_/g, " ")} `,
};
};This would make the code more maintainable and easier to test. |
||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| export * from "./activity-item"; |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,27 @@ | ||||||||||||||||||||||||||||||||||||
| import { FC } from "react"; | ||||||||||||||||||||||||||||||||||||
| import Link from "next/link"; | ||||||||||||||||||||||||||||||||||||
| import { TProjectActivity } from "@/plane-web/types"; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| type TUser = { | ||||||||||||||||||||||||||||||||||||
| activity: TProjectActivity; | ||||||||||||||||||||||||||||||||||||
| customUserName?: string; | ||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| export const User: FC<TUser> = (props) => { | ||||||||||||||||||||||||||||||||||||
| const { activity, customUserName } = props; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||
| <> | ||||||||||||||||||||||||||||||||||||
| {customUserName || activity.actor_detail?.display_name.includes("-intake") ? ( | ||||||||||||||||||||||||||||||||||||
| <span className="text-custom-text-100 font-medium">{customUserName || "Plane"}</span> | ||||||||||||||||||||||||||||||||||||
| ) : ( | ||||||||||||||||||||||||||||||||||||
|
Comment on lines
+15
to
+17
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add null checks for actor_detail properties The current implementation could throw runtime errors if Suggested fix: - {customUserName || activity.actor_detail?.display_name.includes("-intake") ? (
+ {customUserName || (activity.actor_detail?.display_name || "").includes("-intake") ? (📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||
| <Link | ||||||||||||||||||||||||||||||||||||
| href={`/${activity?.workspace_detail?.slug}/profile/${activity?.actor_detail?.id}`} | ||||||||||||||||||||||||||||||||||||
| className="hover:underline text-custom-text-100 font-medium" | ||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||
| {activity.actor_detail?.display_name} | ||||||||||||||||||||||||||||||||||||
| </Link> | ||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||
|
Comment on lines
+18
to
+24
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Add safety checks for profile link and enhance accessibility The profile link construction needs safety checks for undefined values, and the link should have proper accessibility attributes. Suggested improvements: <Link
- href={`/${activity?.workspace_detail?.slug}/profile/${activity?.actor_detail?.id}`}
+ href={activity?.workspace_detail?.slug && activity?.actor_detail?.id
+ ? `/${activity.workspace_detail.slug}/profile/${activity.actor_detail.id}`
+ : "#"}
- className="hover:underline text-custom-text-100 font-medium"
+ className="hover:underline text-custom-text-100 font-medium"
+ aria-label={`View ${activity.actor_detail?.display_name}'s profile`}
>
{activity.actor_detail?.display_name}
</Link>📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||
| </> | ||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider adding error boundary or proper error handling
The early return with empty fragment when activity is null could be improved with proper error handling.
Consider adding error boundary or proper error state:
📝 Committable suggestion