forked from github/plane
refactor: issue activity component (#1749)
This commit is contained in:
parent
d315a24c1c
commit
a8816ef473
@ -1,5 +1,7 @@
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
// icons
|
||||
import { Icon } from "components/ui";
|
||||
import { Icon, Tooltip } from "components/ui";
|
||||
import { Squares2X2Icon } from "@heroicons/react/24/outline";
|
||||
import { BlockedIcon, BlockerIcon } from "components/icons";
|
||||
// helpers
|
||||
@ -8,26 +10,65 @@ import { capitalizeFirstLetter } from "helpers/string.helper";
|
||||
// types
|
||||
import { IIssueActivity } from "types";
|
||||
|
||||
export const activityDetails: {
|
||||
const IssueLink = ({ activity }: { activity: IIssueActivity }) => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
tooltipContent={
|
||||
activity.issue_detail ? activity.issue_detail.name : "This issue has been deleted"
|
||||
}
|
||||
>
|
||||
<a
|
||||
href={`/${workspaceSlug}/projects/${activity.project}/issues/${activity.issue}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="font-medium text-custom-text-100 inline-flex items-center gap-1 hover:underline"
|
||||
>
|
||||
{activity.issue_detail
|
||||
? `${activity.project_detail.identifier}-${activity.issue_detail.sequence_id}`
|
||||
: "Issue"}
|
||||
<Icon iconName="launch" className="!text-xs" />
|
||||
</a>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
const activityDetails: {
|
||||
[key: string]: {
|
||||
message: (activity: IIssueActivity) => React.ReactNode;
|
||||
message: (activity: IIssueActivity, showIssue: boolean) => React.ReactNode;
|
||||
icon: React.ReactNode;
|
||||
};
|
||||
} = {
|
||||
assignees: {
|
||||
message: (activity) => {
|
||||
message: (activity, showIssue) => {
|
||||
if (activity.old_value === "")
|
||||
return (
|
||||
<>
|
||||
added a new assignee{" "}
|
||||
<span className="font-medium text-custom-text-100">{activity.new_value}</span>.
|
||||
<span className="font-medium text-custom-text-100">{activity.new_value}</span>
|
||||
{showIssue && (
|
||||
<>
|
||||
{" "}
|
||||
to <IssueLink activity={activity} />
|
||||
</>
|
||||
)}
|
||||
.
|
||||
</>
|
||||
);
|
||||
else
|
||||
return (
|
||||
<>
|
||||
removed the assignee{" "}
|
||||
<span className="font-medium text-custom-text-100">{activity.old_value}</span>.
|
||||
<span className="font-medium text-custom-text-100">{activity.old_value}</span>
|
||||
{showIssue && (
|
||||
<>
|
||||
{" "}
|
||||
from <IssueLink activity={activity} />
|
||||
</>
|
||||
)}
|
||||
.
|
||||
</>
|
||||
);
|
||||
},
|
||||
@ -41,7 +82,7 @@ export const activityDetails: {
|
||||
icon: <Icon iconName="archive" className="!text-sm" aria-hidden="true" />,
|
||||
},
|
||||
attachment: {
|
||||
message: (activity) => {
|
||||
message: (activity, showIssue) => {
|
||||
if (activity.verb === "created")
|
||||
return (
|
||||
<>
|
||||
@ -55,9 +96,27 @@ export const activityDetails: {
|
||||
attachment
|
||||
<Icon iconName="launch" className="!text-xs" />
|
||||
</a>
|
||||
{showIssue && (
|
||||
<>
|
||||
{" "}
|
||||
to <IssueLink activity={activity} />
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
else
|
||||
return (
|
||||
<>
|
||||
removed an attachment
|
||||
{showIssue && (
|
||||
<>
|
||||
{" "}
|
||||
from <IssueLink activity={activity} />
|
||||
</>
|
||||
)}
|
||||
.
|
||||
</>
|
||||
);
|
||||
else return "removed an attachment.";
|
||||
},
|
||||
icon: <Icon iconName="attach_file" className="!text-sm" aria-hidden="true" />,
|
||||
},
|
||||
@ -126,17 +185,47 @@ export const activityDetails: {
|
||||
icon: <Icon iconName="contrast" className="!text-sm" aria-hidden="true" />,
|
||||
},
|
||||
description: {
|
||||
message: (activity) => "updated the description.",
|
||||
message: (activity, showIssue) => (
|
||||
<>
|
||||
updated the description
|
||||
{showIssue && (
|
||||
<>
|
||||
{" "}
|
||||
of <IssueLink activity={activity} />
|
||||
</>
|
||||
)}
|
||||
.
|
||||
</>
|
||||
),
|
||||
icon: <Icon iconName="chat" className="!text-sm" aria-hidden="true" />,
|
||||
},
|
||||
estimate_point: {
|
||||
message: (activity) => {
|
||||
if (!activity.new_value) return "removed the estimate point.";
|
||||
message: (activity, showIssue) => {
|
||||
if (!activity.new_value)
|
||||
return (
|
||||
<>
|
||||
removed the estimate point
|
||||
{showIssue && (
|
||||
<>
|
||||
{" "}
|
||||
from <IssueLink activity={activity} />
|
||||
</>
|
||||
)}
|
||||
.
|
||||
</>
|
||||
);
|
||||
else
|
||||
return (
|
||||
<>
|
||||
set the estimate point to{" "}
|
||||
<span className="font-medium text-custom-text-100">{activity.new_value}</span>.
|
||||
<span className="font-medium text-custom-text-100">{activity.new_value}</span>
|
||||
{showIssue && (
|
||||
<>
|
||||
{" "}
|
||||
for <IssueLink activity={activity} />
|
||||
</>
|
||||
)}
|
||||
.
|
||||
</>
|
||||
);
|
||||
},
|
||||
@ -150,7 +239,7 @@ export const activityDetails: {
|
||||
icon: <Icon iconName="stack" className="!text-sm" aria-hidden="true" />,
|
||||
},
|
||||
labels: {
|
||||
message: (activity) => {
|
||||
message: (activity, showIssue) => {
|
||||
if (activity.old_value === "")
|
||||
return (
|
||||
<>
|
||||
@ -165,6 +254,12 @@ export const activityDetails: {
|
||||
/>
|
||||
<span className="font-medium text-custom-text-100">{activity.new_value}</span>
|
||||
</span>
|
||||
{showIssue && (
|
||||
<>
|
||||
{" "}
|
||||
to <IssueLink activity={activity} />
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
else
|
||||
@ -181,13 +276,19 @@ export const activityDetails: {
|
||||
/>
|
||||
<span className="font-medium text-custom-text-100">{activity.old_value}</span>
|
||||
</span>
|
||||
{showIssue && (
|
||||
<>
|
||||
{" "}
|
||||
from <IssueLink activity={activity} />
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
},
|
||||
icon: <Icon iconName="sell" className="!text-sm" aria-hidden="true" />,
|
||||
},
|
||||
link: {
|
||||
message: (activity) => {
|
||||
message: (activity, showIssue) => {
|
||||
if (activity.verb === "created")
|
||||
return (
|
||||
<>
|
||||
@ -200,8 +301,14 @@ export const activityDetails: {
|
||||
>
|
||||
link
|
||||
<Icon iconName="launch" className="!text-xs" />
|
||||
</a>{" "}
|
||||
to the issue.
|
||||
</a>
|
||||
{showIssue && (
|
||||
<>
|
||||
{" "}
|
||||
to <IssueLink activity={activity} />
|
||||
</>
|
||||
)}
|
||||
.
|
||||
</>
|
||||
);
|
||||
else
|
||||
@ -216,8 +323,14 @@ export const activityDetails: {
|
||||
>
|
||||
link
|
||||
<Icon iconName="launch" className="!text-xs" />
|
||||
</a>{" "}
|
||||
from the issue.
|
||||
</a>
|
||||
{showIssue && (
|
||||
<>
|
||||
{" "}
|
||||
from <IssueLink activity={activity} />
|
||||
</>
|
||||
)}
|
||||
.
|
||||
</>
|
||||
);
|
||||
},
|
||||
@ -250,52 +363,102 @@ export const activityDetails: {
|
||||
icon: <Icon iconName="dataset" className="!text-sm" aria-hidden="true" />,
|
||||
},
|
||||
name: {
|
||||
message: (activity) => `set the name to ${activity.new_value}.`,
|
||||
message: (activity, showIssue) => (
|
||||
<>
|
||||
set the name to {activity.new_value}
|
||||
{showIssue && (
|
||||
<>
|
||||
{" "}
|
||||
of <IssueLink activity={activity} />
|
||||
</>
|
||||
)}
|
||||
.
|
||||
</>
|
||||
),
|
||||
icon: <Icon iconName="chat" className="!text-sm" aria-hidden="true" />,
|
||||
},
|
||||
parent: {
|
||||
message: (activity) => {
|
||||
message: (activity, showIssue) => {
|
||||
if (!activity.new_value)
|
||||
return (
|
||||
<>
|
||||
removed the parent{" "}
|
||||
<span className="font-medium text-custom-text-100">{activity.old_value}</span>.
|
||||
<span className="font-medium text-custom-text-100">{activity.old_value}</span>
|
||||
{showIssue && (
|
||||
<>
|
||||
{" "}
|
||||
from <IssueLink activity={activity} />
|
||||
</>
|
||||
)}
|
||||
.
|
||||
</>
|
||||
);
|
||||
else
|
||||
return (
|
||||
<>
|
||||
set the parent to{" "}
|
||||
<span className="font-medium text-custom-text-100">{activity.new_value}</span>.
|
||||
<span className="font-medium text-custom-text-100">{activity.new_value}</span>
|
||||
{showIssue && (
|
||||
<>
|
||||
{" "}
|
||||
for <IssueLink activity={activity} />
|
||||
</>
|
||||
)}
|
||||
.
|
||||
</>
|
||||
);
|
||||
},
|
||||
icon: <Icon iconName="supervised_user_circle" className="!text-sm" aria-hidden="true" />,
|
||||
},
|
||||
priority: {
|
||||
message: (activity) => (
|
||||
message: (activity, showIssue) => (
|
||||
<>
|
||||
set the priority to{" "}
|
||||
<span className="font-medium text-custom-text-100">
|
||||
{activity.new_value ? capitalizeFirstLetter(activity.new_value) : "None"}
|
||||
</span>
|
||||
{showIssue && (
|
||||
<>
|
||||
{" "}
|
||||
for <IssueLink activity={activity} />
|
||||
</>
|
||||
)}
|
||||
.
|
||||
</>
|
||||
),
|
||||
icon: <Icon iconName="signal_cellular_alt" className="!text-sm" aria-hidden="true" />,
|
||||
},
|
||||
state: {
|
||||
message: (activity) => (
|
||||
message: (activity, showIssue) => (
|
||||
<>
|
||||
set the state to{" "}
|
||||
<span className="font-medium text-custom-text-100">{activity.new_value}</span>.
|
||||
<span className="font-medium text-custom-text-100">{activity.new_value}</span>
|
||||
{showIssue && (
|
||||
<>
|
||||
{" "}
|
||||
for <IssueLink activity={activity} />
|
||||
</>
|
||||
)}
|
||||
.
|
||||
</>
|
||||
),
|
||||
icon: <Squares2X2Icon className="h-3 w-3" aria-hidden="true" />,
|
||||
},
|
||||
target_date: {
|
||||
message: (activity) => {
|
||||
if (!activity.new_value) return "removed the due date.";
|
||||
message: (activity, showIssue) => {
|
||||
if (!activity.new_value)
|
||||
return (
|
||||
<>
|
||||
removed the due date
|
||||
{showIssue && (
|
||||
<>
|
||||
{" "}
|
||||
from <IssueLink activity={activity} />
|
||||
</>
|
||||
)}
|
||||
.
|
||||
</>
|
||||
);
|
||||
else
|
||||
return (
|
||||
<>
|
||||
@ -303,6 +466,12 @@ export const activityDetails: {
|
||||
<span className="font-medium text-custom-text-100">
|
||||
{renderShortDateWithYearFormat(activity.new_value)}
|
||||
</span>
|
||||
{showIssue && (
|
||||
<>
|
||||
{" "}
|
||||
for <IssueLink activity={activity} />
|
||||
</>
|
||||
)}
|
||||
.
|
||||
</>
|
||||
);
|
||||
@ -310,3 +479,19 @@ export const activityDetails: {
|
||||
icon: <Icon iconName="calendar_today" className="!text-sm" aria-hidden="true" />,
|
||||
},
|
||||
};
|
||||
|
||||
export const ActivityIcon = ({ activity }: { activity: IIssueActivity }) => (
|
||||
<>{activityDetails[activity.field as keyof typeof activityDetails].icon}</>
|
||||
);
|
||||
|
||||
export const ActivityMessage = ({
|
||||
activity,
|
||||
showIssue = false,
|
||||
}: {
|
||||
activity: IIssueActivity;
|
||||
showIssue?: boolean;
|
||||
}) => (
|
||||
<>
|
||||
{activityDetails[activity.field as keyof typeof activityDetails].message(activity, showIssue)}
|
||||
</>
|
||||
);
|
@ -3,6 +3,7 @@ export * from "./modals";
|
||||
export * from "./sidebar";
|
||||
export * from "./theme";
|
||||
export * from "./views";
|
||||
export * from "./activity";
|
||||
export * from "./feeds";
|
||||
export * from "./reaction-selector";
|
||||
export * from "./image-picker-popover";
|
||||
|
@ -8,12 +8,12 @@ import useSWR from "swr";
|
||||
// services
|
||||
import issuesService from "services/issues.service";
|
||||
// components
|
||||
import { ActivityIcon, ActivityMessage } from "components/core";
|
||||
import { CommentCard } from "components/issues/comment";
|
||||
// ui
|
||||
import { Icon, Loader } from "components/ui";
|
||||
// helpers
|
||||
import { timeAgo } from "helpers/date-time.helper";
|
||||
import { activityDetails } from "helpers/activity.helper";
|
||||
// types
|
||||
import { ICurrentUserResponse, IIssueComment } from "types";
|
||||
// fetch-keys
|
||||
@ -91,11 +91,11 @@ export const IssueActivitySection: React.FC<Props> = ({ issueId, user }) => {
|
||||
<ul role="list" className="-mb-4">
|
||||
{issueActivities.map((activityItem, index) => {
|
||||
// determines what type of action is performed
|
||||
const message = activityItem.field
|
||||
? activityDetails[activityItem.field as keyof typeof activityDetails]?.message(
|
||||
activityItem
|
||||
)
|
||||
: "created the issue.";
|
||||
const message = activityItem.field ? (
|
||||
<ActivityMessage activity={activityItem} />
|
||||
) : (
|
||||
"created the issue."
|
||||
);
|
||||
|
||||
if ("field" in activityItem && activityItem.field !== "updated_by") {
|
||||
return (
|
||||
@ -116,8 +116,7 @@ export const IssueActivitySection: React.FC<Props> = ({ issueId, user }) => {
|
||||
activityItem.new_value === "restore" ? (
|
||||
<Icon iconName="history" className="text-sm text-custom-text-200" />
|
||||
) : (
|
||||
activityDetails[activityItem.field as keyof typeof activityDetails]
|
||||
?.icon
|
||||
<ActivityIcon activity={activityItem} />
|
||||
)
|
||||
) : activityItem.actor_detail.avatar &&
|
||||
activityItem.actor_detail.avatar !== "" ? (
|
||||
|
@ -140,11 +140,7 @@ export const CommentCard: React.FC<Props> = ({ comment, onSubmit, handleCommentD
|
||||
ref={showEditorRef}
|
||||
/>
|
||||
|
||||
<CommentReaction
|
||||
workspaceSlug={comment?.workspace_detail?.slug}
|
||||
projectId={comment.project}
|
||||
commentId={comment.id}
|
||||
/>
|
||||
<CommentReaction projectId={comment.project} commentId={comment.id} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,5 +1,7 @@
|
||||
import React from "react";
|
||||
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
// hooks
|
||||
import useUser from "hooks/use-user";
|
||||
import useCommentReaction from "hooks/use-comment-reaction";
|
||||
@ -9,13 +11,15 @@ import { ReactionSelector } from "components/core";
|
||||
import { renderEmoji } from "helpers/emoji.helper";
|
||||
|
||||
type Props = {
|
||||
workspaceSlug?: string | string[];
|
||||
projectId?: string | string[];
|
||||
commentId: string;
|
||||
};
|
||||
|
||||
export const CommentReaction: React.FC<Props> = (props) => {
|
||||
const { workspaceSlug, projectId, commentId } = props;
|
||||
const { projectId, commentId } = props;
|
||||
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
|
||||
const { user } = useUser();
|
||||
|
||||
|
@ -1,14 +1,14 @@
|
||||
import { useRouter } from "next/router";
|
||||
import Link from "next/link";
|
||||
|
||||
import useSWR from "swr";
|
||||
|
||||
// services
|
||||
import userService from "services/user.service";
|
||||
// components
|
||||
import { ActivityMessage } from "components/core";
|
||||
// ui
|
||||
import { Icon, Loader } from "components/ui";
|
||||
// helpers
|
||||
import { activityDetails } from "helpers/activity.helper";
|
||||
import { timeAgo } from "helpers/date-time.helper";
|
||||
// fetch-keys
|
||||
import { USER_PROFILE_ACTIVITY } from "constants/fetch-keys";
|
||||
@ -55,12 +55,12 @@ export const ProfileActivity = () => {
|
||||
{activity.actor_detail.first_name} {activity.actor_detail.last_name}{" "}
|
||||
</span>
|
||||
{activity.field ? (
|
||||
activityDetails[activity.field]?.message(activity as any)
|
||||
<ActivityMessage activity={activity} showIssue />
|
||||
) : (
|
||||
<span>
|
||||
created this{" "}
|
||||
<a
|
||||
href={`/${activity.workspace_detail.slug}/projects/${activity.project}/issues/${activity.issue}`}
|
||||
href={`/${workspaceSlug}/projects/${activity.project}/issues/${activity.issue}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="font-medium text-custom-text-100 inline-flex items-center gap-1 hover:underline"
|
||||
|
25
apps/app/types/issues.d.ts
vendored
25
apps/app/types/issues.d.ts
vendored
@ -38,19 +38,6 @@ export interface IIssueModule {
|
||||
workspace: string;
|
||||
}
|
||||
|
||||
export interface IIssueCycle {
|
||||
created_at: Date;
|
||||
created_by: string;
|
||||
cycle: string;
|
||||
cycle_detail: ICycle;
|
||||
id: string;
|
||||
issue: string;
|
||||
project: string;
|
||||
updated_at: Date;
|
||||
updated_by: string;
|
||||
workspace: string;
|
||||
}
|
||||
|
||||
export interface IIssueParent {
|
||||
description: any;
|
||||
id: string;
|
||||
@ -200,18 +187,26 @@ export interface IIssueActivity {
|
||||
created_by: string;
|
||||
field: string | null;
|
||||
id: string;
|
||||
issue: string;
|
||||
issue: string | null;
|
||||
issue_comment: string | null;
|
||||
issue_detail: {
|
||||
description: any;
|
||||
description_html: string;
|
||||
id: string;
|
||||
name: string;
|
||||
priority: string | null;
|
||||
sequence_id: string;
|
||||
} | null;
|
||||
new_identifier: string | null;
|
||||
new_value: string | null;
|
||||
old_identifier: string | null;
|
||||
old_value: string | null;
|
||||
project: string;
|
||||
project_detail: IProjectLite;
|
||||
updated_at: Date;
|
||||
updated_by: string;
|
||||
verb: string;
|
||||
workspace: string;
|
||||
workspace_detail: IWorkspaceLite;
|
||||
}
|
||||
|
||||
export interface IIssueComment extends IIssueActivity {
|
||||
|
26
apps/app/types/users.d.ts
vendored
26
apps/app/types/users.d.ts
vendored
@ -1,5 +1,6 @@
|
||||
import {
|
||||
IIssue,
|
||||
IIssueActivity,
|
||||
IIssueLite,
|
||||
IWorkspace,
|
||||
IWorkspaceLite,
|
||||
@ -99,29 +100,6 @@ export interface IUserWorkspaceDashboard {
|
||||
upcoming_issues: IIssueLite[];
|
||||
}
|
||||
|
||||
export interface IUserDetailedActivity {
|
||||
actor: string;
|
||||
actor_detail: IUserLite;
|
||||
attachments: any[];
|
||||
comment: string;
|
||||
created_at: string;
|
||||
created_by: string | null;
|
||||
field: string;
|
||||
id: string;
|
||||
issue: string;
|
||||
issue_comment: string | null;
|
||||
new_identifier: string | null;
|
||||
new_value: string | null;
|
||||
old_identifier: string | null;
|
||||
old_value: string | null;
|
||||
project: string;
|
||||
updated_at: string;
|
||||
updated_by: string | null;
|
||||
verb: string;
|
||||
workspace: string;
|
||||
workspace_detail: IWorkspaceLite;
|
||||
}
|
||||
|
||||
export interface IUserActivityResponse {
|
||||
count: number;
|
||||
extra_stats: null;
|
||||
@ -129,7 +107,7 @@ export interface IUserActivityResponse {
|
||||
next_page_results: boolean;
|
||||
prev_cursor: string;
|
||||
prev_page_results: boolean;
|
||||
results: IUserDetailedActivity[];
|
||||
results: IIssueActivity[];
|
||||
total_pages: number;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user