[WEB-5574]chore: notification card refactor (#8234)
* chore: notification card refactor * chore: moved base activity types to constants package
This commit is contained in:
parent
3c8624b1ba
commit
5499e49b72
4 changed files with 192 additions and 65 deletions
|
|
@ -0,0 +1,25 @@
|
|||
import { replaceUnderscoreIfSnakeCase } from "@plane/utils";
|
||||
import type { TNotificationContentMap } from "@/components/workspace-notifications/sidebar/notification-card/content";
|
||||
|
||||
// Additional notification content map for CE (empty - EE extends this)
|
||||
export const ADDITIONAL_NOTIFICATION_CONTENT_MAP: TNotificationContentMap = {};
|
||||
|
||||
// Fallback action renderer for fields not in the map
|
||||
export const renderAdditionalAction = (notificationField: string, verb: string | undefined) => {
|
||||
const baseAction = !["comment", "archived_at"].includes(notificationField) ? verb : "";
|
||||
return `${baseAction} ${replaceUnderscoreIfSnakeCase(notificationField)}`;
|
||||
};
|
||||
|
||||
// Fallback value renderer for fields not in the map
|
||||
export const renderAdditionalValue = (
|
||||
_notificationField: string | undefined,
|
||||
newValue: string | undefined,
|
||||
_oldValue: string | undefined
|
||||
) => newValue;
|
||||
|
||||
export const shouldShowConnector = (notificationField: string | undefined) =>
|
||||
!["comment", "archived_at", "None", "assignees", "labels", "start_date", "target_date", "parent"].includes(
|
||||
notificationField || ""
|
||||
);
|
||||
|
||||
export const shouldRender = (notificationField: string | undefined, verb: string | undefined) => verb !== "deleted";
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { observer } from "mobx-react";
|
||||
// plane imports
|
||||
import type { E_SORT_ORDER, TActivityFilters } from "@plane/constants";
|
||||
import { EActivityFilterType, filterActivityOnSelectedFilters } from "@plane/constants";
|
||||
import type { E_SORT_ORDER, TActivityFilters, EActivityFilterType } from "@plane/constants";
|
||||
import { BASE_ACTIVITY_FILTER_TYPES, filterActivityOnSelectedFilters } from "@plane/constants";
|
||||
import type { TCommentsOperations } from "@plane/types";
|
||||
// components
|
||||
import { CommentCard } from "@/components/comments/card/root";
|
||||
|
|
@ -52,13 +52,6 @@ export const IssueActivityCommentRoot = observer(function IssueActivityCommentRo
|
|||
|
||||
const filteredActivityAndComments = filterActivityOnSelectedFilters(activityAndComments, selectedFilters);
|
||||
|
||||
const BASE_ACTIVITY_FILTER_TYPES = [
|
||||
EActivityFilterType.ACTIVITY,
|
||||
EActivityFilterType.STATE,
|
||||
EActivityFilterType.ASSIGNEE,
|
||||
EActivityFilterType.DEFAULT,
|
||||
];
|
||||
|
||||
return (
|
||||
<div>
|
||||
{filteredActivityAndComments.map((activityComment, index) => {
|
||||
|
|
|
|||
|
|
@ -1,14 +1,144 @@
|
|||
import type { ReactNode } from "react";
|
||||
// plane imports
|
||||
import type { TNotification } from "@plane/types";
|
||||
import {
|
||||
convertMinutesToHoursMinutesString,
|
||||
renderFormattedDate,
|
||||
sanitizeCommentForNotification,
|
||||
replaceUnderscoreIfSnakeCase,
|
||||
stripAndTruncateHTML,
|
||||
} from "@plane/utils";
|
||||
// components
|
||||
import { LiteTextEditor } from "@/components/editor/lite-text";
|
||||
import {
|
||||
ADDITIONAL_NOTIFICATION_CONTENT_MAP,
|
||||
renderAdditionalAction,
|
||||
renderAdditionalValue,
|
||||
shouldShowConnector,
|
||||
} from "@/plane-web/components/workspace-notifications/notification-card/content";
|
||||
|
||||
// Types
|
||||
export type TNotificationFieldData = {
|
||||
field: string | undefined;
|
||||
newValue: string | undefined;
|
||||
oldValue: string | undefined;
|
||||
verb: string | undefined;
|
||||
};
|
||||
|
||||
export type TNotificationContentDetails = {
|
||||
action?: ReactNode;
|
||||
value?: ReactNode;
|
||||
showConnector?: boolean;
|
||||
};
|
||||
|
||||
export type TNotificationContentHandler = (data: TNotificationFieldData) => TNotificationContentDetails | null;
|
||||
|
||||
export type TNotificationContentMap = {
|
||||
[key: string]: TNotificationContentHandler;
|
||||
};
|
||||
|
||||
// Base notification content map for core fields
|
||||
export const BASE_NOTIFICATION_CONTENT_MAP: TNotificationContentMap = {
|
||||
duplicate: ({ verb }) => ({
|
||||
action:
|
||||
verb === "created"
|
||||
? "marked that this work item is a duplicate of"
|
||||
: "marked that this work item is not a duplicate",
|
||||
value: null,
|
||||
showConnector: false,
|
||||
}),
|
||||
assignees: ({ newValue, oldValue }) => ({
|
||||
action: newValue !== "" ? "added assignee" : "removed assignee",
|
||||
value: newValue !== "" ? newValue : oldValue,
|
||||
showConnector: false,
|
||||
}),
|
||||
start_date: ({ newValue }) => ({
|
||||
action: newValue !== "" ? "set start date" : "removed the start date",
|
||||
value: renderFormattedDate(newValue),
|
||||
showConnector: false,
|
||||
}),
|
||||
target_date: ({ newValue }) => ({
|
||||
action: newValue !== "" ? "set due date" : "removed the due date",
|
||||
value: renderFormattedDate(newValue),
|
||||
showConnector: false,
|
||||
}),
|
||||
labels: ({ newValue, oldValue }) => ({
|
||||
action: newValue !== "" ? "added label" : "removed label",
|
||||
value: newValue !== "" ? newValue : oldValue,
|
||||
showConnector: false,
|
||||
}),
|
||||
parent: ({ newValue, oldValue }) => ({
|
||||
action: newValue !== "" ? "added parent" : "removed parent",
|
||||
value: newValue !== "" ? newValue : oldValue,
|
||||
showConnector: false,
|
||||
}),
|
||||
relates_to: () => ({
|
||||
action: "marked that this work item is related to",
|
||||
value: null,
|
||||
showConnector: true,
|
||||
}),
|
||||
comment: ({ newValue }, renderCommentBox?: boolean) => ({
|
||||
action: "commented",
|
||||
value: renderCommentBox ? null : sanitizeCommentForNotification(newValue),
|
||||
showConnector: false,
|
||||
}),
|
||||
archived_at: ({ newValue }) => ({
|
||||
action: newValue === "restore" ? "restored the work item" : "archived the work item",
|
||||
value: null,
|
||||
showConnector: false,
|
||||
}),
|
||||
None: () => ({
|
||||
action: null,
|
||||
value: "the work item and assigned it to you.",
|
||||
showConnector: false,
|
||||
}),
|
||||
// Fields below only define value - action falls through to default handler
|
||||
attachment: () => ({
|
||||
action: null,
|
||||
value: "the work item",
|
||||
showConnector: true,
|
||||
}),
|
||||
description: ({ newValue }) => ({
|
||||
value: stripAndTruncateHTML(newValue || "", 55),
|
||||
showConnector: true,
|
||||
}),
|
||||
estimate_time: ({ newValue, oldValue }) => ({
|
||||
value:
|
||||
newValue !== ""
|
||||
? convertMinutesToHoursMinutesString(Number(newValue))
|
||||
: convertMinutesToHoursMinutesString(Number(oldValue)),
|
||||
showConnector: true,
|
||||
}),
|
||||
};
|
||||
|
||||
// Helper to get content details from maps
|
||||
const getNotificationContentDetails = (
|
||||
fieldData: TNotificationFieldData,
|
||||
renderCommentBox?: boolean
|
||||
): TNotificationContentDetails | null => {
|
||||
const { field } = fieldData;
|
||||
if (!field) return null;
|
||||
|
||||
// Check base map first
|
||||
const baseHandler = BASE_NOTIFICATION_CONTENT_MAP[field];
|
||||
if (baseHandler) {
|
||||
// Special case for comment field that needs renderCommentBox
|
||||
if (field === "comment") {
|
||||
return (baseHandler as (data: TNotificationFieldData, renderCommentBox?: boolean) => TNotificationContentDetails)(
|
||||
fieldData,
|
||||
renderCommentBox
|
||||
);
|
||||
}
|
||||
return baseHandler(fieldData);
|
||||
}
|
||||
|
||||
// Check additional map from plane-web (EE extensions)
|
||||
const additionalHandler = ADDITIONAL_NOTIFICATION_CONTENT_MAP[field];
|
||||
if (additionalHandler) {
|
||||
return additionalHandler(fieldData);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export function NotificationContent({
|
||||
notification,
|
||||
|
|
@ -29,71 +159,43 @@ export function NotificationContent({
|
|||
const oldValue = data?.issue_activity.old_value;
|
||||
const verb = data?.issue_activity.verb;
|
||||
|
||||
const fieldData: TNotificationFieldData = {
|
||||
field: notificationField,
|
||||
newValue,
|
||||
oldValue,
|
||||
verb,
|
||||
};
|
||||
|
||||
const renderTriggerName = () => (
|
||||
<span className="text-primary font-medium">
|
||||
{triggeredBy?.is_bot ? triggeredBy.first_name : triggeredBy?.display_name}{" "}
|
||||
</span>
|
||||
);
|
||||
|
||||
const renderAction = () => {
|
||||
// Get content details from map
|
||||
const contentDetails = getNotificationContentDetails(fieldData, renderCommentBox);
|
||||
|
||||
// Render action - use map value if defined, otherwise fall through to default handler
|
||||
// Note: undefined = fall through to default, null = explicitly no action text
|
||||
const renderAction = (): ReactNode => {
|
||||
if (!notificationField) return "";
|
||||
if (notificationField === "duplicate")
|
||||
return verb === "created"
|
||||
? "marked that this work item is a duplicate of"
|
||||
: "marked that this work item is not a duplicate";
|
||||
if (notificationField === "assignees") {
|
||||
return newValue !== "" ? "added assignee" : "removed assignee";
|
||||
}
|
||||
if (notificationField === "start_date") {
|
||||
return newValue !== "" ? "set start date" : "removed the start date";
|
||||
}
|
||||
if (notificationField === "target_date") {
|
||||
return newValue !== "" ? "set due date" : "removed the due date";
|
||||
}
|
||||
if (notificationField === "labels") {
|
||||
return newValue !== "" ? "added label" : "removed label";
|
||||
}
|
||||
if (notificationField === "parent") {
|
||||
return newValue !== "" ? "added parent" : "removed parent";
|
||||
}
|
||||
if (notificationField === "relates_to") return "marked that this work item is related to";
|
||||
if (notificationField === "comment") return "commented";
|
||||
if (notificationField === "archived_at") {
|
||||
return newValue === "restore" ? "restored the work item" : "archived the work item";
|
||||
}
|
||||
if (notificationField === "None") return null;
|
||||
|
||||
const baseAction = !["comment", "archived_at"].includes(notificationField) ? verb : "";
|
||||
return `${baseAction} ${replaceUnderscoreIfSnakeCase(notificationField)}`;
|
||||
// Check if action is explicitly defined in map (including null)
|
||||
if (contentDetails && "action" in contentDetails) return contentDetails.action;
|
||||
// Fallback to default action handler for fields not in map or without action defined
|
||||
return renderAdditionalAction(notificationField, verb);
|
||||
};
|
||||
|
||||
const renderValue = () => {
|
||||
if (notificationField === "None") return "the work item and assigned it to you.";
|
||||
if (notificationField === "comment") return renderCommentBox ? null : sanitizeCommentForNotification(newValue);
|
||||
if (notificationField === "target_date" || notificationField === "start_date") return renderFormattedDate(newValue);
|
||||
if (notificationField === "attachment") return "the work item";
|
||||
if (notificationField === "description") return stripAndTruncateHTML(newValue || "", 55);
|
||||
if (notificationField === "archived_at") return null;
|
||||
if (notificationField === "assignees") return newValue !== "" ? newValue : oldValue;
|
||||
if (notificationField === "labels") return newValue !== "" ? newValue : oldValue;
|
||||
if (notificationField === "parent") return newValue !== "" ? newValue : oldValue;
|
||||
if (notificationField === "estimate_time")
|
||||
return newValue !== ""
|
||||
? convertMinutesToHoursMinutesString(Number(newValue))
|
||||
: convertMinutesToHoursMinutesString(Number(oldValue));
|
||||
return newValue;
|
||||
// Render value - use map value if defined, otherwise fall through to default handler
|
||||
const renderValue = (): ReactNode => {
|
||||
// Check if value is explicitly defined in map
|
||||
if (contentDetails && "value" in contentDetails) return contentDetails.value;
|
||||
// Fallback to default value handler for fields not in map or without value defined
|
||||
return renderAdditionalValue(notificationField, newValue, oldValue);
|
||||
};
|
||||
|
||||
const shouldShowConnector = ![
|
||||
"comment",
|
||||
"archived_at",
|
||||
"None",
|
||||
"assignees",
|
||||
"labels",
|
||||
"start_date",
|
||||
"target_date",
|
||||
"parent",
|
||||
].includes(notificationField || "");
|
||||
// Determine if connector should be shown - prefer map value, fallback to function
|
||||
const showConnector =
|
||||
contentDetails?.showConnector !== undefined ? contentDetails.showConnector : shouldShowConnector(notificationField);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
@ -101,7 +203,7 @@ export function NotificationContent({
|
|||
<span className="text-tertiary">{renderAction()} </span>
|
||||
{verb !== "deleted" && (
|
||||
<>
|
||||
{shouldShowConnector && <span className="text-tertiary">to </span>}
|
||||
{showConnector && <span className="text-tertiary">to </span>}
|
||||
<span className="text-primary font-medium">{renderValue()}</span>
|
||||
{notificationField === "comment" && renderCommentBox && (
|
||||
<div className="scale-75 origin-left">
|
||||
|
|
|
|||
|
|
@ -353,3 +353,10 @@ export const filterActivityOnSelectedFilters = (
|
|||
});
|
||||
|
||||
export const ENABLE_ISSUE_DEPENDENCIES = false;
|
||||
|
||||
export const BASE_ACTIVITY_FILTER_TYPES = [
|
||||
EActivityFilterType.ACTIVITY,
|
||||
EActivityFilterType.STATE,
|
||||
EActivityFilterType.ASSIGNEE,
|
||||
EActivityFilterType.DEFAULT,
|
||||
];
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue