snapshot of https://github.com/makeplane/plane.git @ d868349a55de79979eb8b98701bf1702c67ad634 (2026-09-15)
Some checks failed
CodeQL / Analyze (javascript) (push) Failing after 16s
CodeQL / Analyze (python) (push) Failing after 3s
i18n sync check / check:sync (push) Failing after 3s
React Doctor / react-doctor (push) Failing after 16s
Branch Build CE / Build-Push Admin Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Admin Manifest (push) Has been cancelled
Branch Build CE / Build-Push Web Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Web Manifest (push) Has been cancelled
Branch Build CE / Build-Push Space Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Proxy Manifest (push) Has been cancelled
Branch Build CE / Merge Space Manifest (push) Has been cancelled
Branch Build CE / Prepare AIO Assets (push) Has been cancelled
Branch Build CE / Build Setup (push) Has been cancelled
Branch Build CE / Build-Push Live Collaboration Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Live Collaboration Manifest (push) Has been cancelled
Branch Build CE / Build-Push API Server Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge API Server Manifest (push) Has been cancelled
Branch Build CE / Build-Push Proxy Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Build-Push AIO Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge AIO Manifest (push) Has been cancelled
Branch Build CE / Upload Build Assets (push) Has been cancelled
Branch Build CE / Build Release (push) Has been cancelled

This commit is contained in:
upstream-mirror
2026-09-15 15:14:58 +05:00
commit 23059a1de3
5252 changed files with 573834 additions and 0 deletions

View File

@@ -0,0 +1,185 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import { observer } from "mobx-react";
import Link from "next/link";
import { useParams } from "next/navigation";
import { ChatOutline, HistoryOutline } from "@makeplane/propel/icons";
// plane imports
import type { IUserActivityResponse } from "@plane/types";
import { calculateTimeAgo, getFileURL } from "@plane/utils";
// components
import { ActivityIcon, ActivityMessage, IssueLink } from "@/components/core/activity";
import { RichTextEditor } from "@/components/editor/rich-text";
import { ActivitySettingsLoader } from "@/components/ui/loader/settings/activity";
// hooks
import { useWorkspace } from "@/hooks/store/use-workspace";
import { useUser } from "@/hooks/store/user";
type Props = {
activity: IUserActivityResponse | undefined;
};
export const ActivityList = observer(function ActivityList(props: Props) {
const { activity } = props;
// params
const { workspaceSlug } = useParams();
// store hooks
const { data: currentUser } = useUser();
const { getWorkspaceBySlug } = useWorkspace();
// derived values
const workspaceId = getWorkspaceBySlug(workspaceSlug?.toString() ?? "")?.id ?? "";
// TODO: refactor this component
return (
<>
{activity ? (
<ul role="list">
{activity.results.map((activityItem) => {
if (activityItem.field === "comment")
return (
<div key={activityItem.id} className="mt-2">
<div className="relative flex items-start space-x-3">
<div className="relative px-1">
{activityItem.field ? (
activityItem.new_value === "restore" && (
<HistoryOutline className="h-3.5 w-3.5 text-secondary" />
)
) : activityItem.actor_detail.avatar_url && activityItem.actor_detail.avatar_url !== "" ? (
<img
src={getFileURL(activityItem.actor_detail.avatar_url)}
alt={activityItem.actor_detail.display_name}
height={30}
width={30}
className="bg-gray-500 grid h-7 w-7 place-items-center rounded-full border-2 border-white text-on-color"
/>
) : (
<div className="bg-gray-500 grid h-7 w-7 place-items-center rounded-full border-2 border-white text-on-color capitalize">
{activityItem.actor_detail.display_name?.[0]}
</div>
)}
<span className="flex h-6 w-6 items-center justify-center rounded-full bg-layer-1 text-secondary ring-6 ring-white">
<ChatOutline className="h-6 w-6 !text-20 text-secondary" aria-hidden="true" />
</span>
</div>
<div className="min-w-0 flex-1">
<div>
<div className="text-11">
{activityItem.actor_detail.is_bot
? activityItem.actor_detail.first_name + " Bot"
: activityItem.actor_detail.display_name}
</div>
<p className="mt-0.5 text-11 text-secondary">
Commented {calculateTimeAgo(activityItem.created_at)}
</p>
</div>
<div className="issue-comments-section p-0">
<RichTextEditor
editable={false}
id={activityItem.id}
initialValue={
activityItem?.new_value !== ""
? (activityItem.new_value?.toString() as string)
: (activityItem.old_value?.toString() as string)
}
containerClassName="text-11 bg-surface-1"
workspaceId={workspaceId}
workspaceSlug={workspaceSlug?.toString() ?? ""}
projectId={activityItem.project}
/>
</div>
</div>
</div>
</div>
);
const message =
activityItem.verb === "created" &&
!["cycles", "modules", "attachment", "link", "estimate"].includes(
activityItem.field?.toString() as string
) &&
!activityItem.field ? (
<span>
created <IssueLink activity={activityItem} />
</span>
) : (
<ActivityMessage activity={activityItem} showIssue />
);
if ("field" in activityItem && activityItem.field !== "updated_by")
return (
<li key={activityItem.id}>
<div className="relative pb-1">
<div className="relative flex items-start space-x-2">
<>
<div>
<div className="relative mt-4 px-1.5">
<div className="mt-1.5">
<div className="flex h-6 w-6 items-center justify-center">
{activityItem.field ? (
activityItem.new_value === "restore" ? (
<HistoryOutline className="h-5 w-5 text-secondary" />
) : (
<ActivityIcon activity={activityItem} />
)
) : activityItem.actor_detail.avatar_url &&
activityItem.actor_detail.avatar_url !== "" ? (
<img
src={getFileURL(activityItem.actor_detail.avatar_url)}
alt={activityItem.actor_detail.display_name}
height={24}
width={24}
className="h-full w-full rounded-full object-cover"
/>
) : (
<div className="bg-gray-700 grid h-6 w-6 place-items-center rounded-full border-2 border-white text-11 text-on-color capitalize">
{activityItem.actor_detail.display_name?.[0]}
</div>
)}
</div>
</div>
</div>
</div>
<div className="min-w-0 flex-1 border-b border-subtle py-4">
<div className="text-13 break-words text-secondary">
{activityItem.field === "archived_at" && activityItem.new_value !== "restore" ? (
<span className="text-gray font-medium">Plane</span>
) : activityItem.actor_detail.is_bot ? (
<span className="text-gray font-medium">{activityItem.actor_detail.first_name} Bot</span>
) : (
<Link
href={`/${activityItem.workspace_detail?.slug}/profile/${activityItem.actor_detail.id}`}
className="inline"
>
<span className="text-gray font-medium">
{currentUser?.id === activityItem.actor_detail.id
? "You"
: activityItem.actor_detail.display_name}
</span>
</Link>
)}{" "}
<div className="inline gap-1">
{message}{" "}
<span className="flex-shrink-0 whitespace-nowrap">
{calculateTimeAgo(activityItem.created_at)}
</span>
</div>
</div>
</div>
</>
</div>
</div>
</li>
);
})}
</ul>
) : (
<ActivitySettingsLoader />
)}
</>
);
});

View File

@@ -0,0 +1,65 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import { useState } from "react";
import { useParams } from "next/navigation";
// services
// ui
import { useTranslation } from "@plane/i18n";
import { Button } from "@plane/propel/button";
// helpers
import { renderFormattedPayloadDate } from "@plane/utils";
import { UserService } from "@/services/user.service";
const userService = new UserService();
export function DownloadActivityButton() {
// states
const [isDownloading, setIsDownloading] = useState(false);
// router
const { workspaceSlug, userId } = useParams();
//hooks
const { t } = useTranslation();
const handleDownload = async () => {
const today = renderFormattedPayloadDate(new Date());
if (!workspaceSlug || !userId || !today) return;
setIsDownloading(true);
const csv = await userService
.downloadProfileActivity(workspaceSlug.toString(), userId.toString(), {
date: today,
})
.finally(() => setIsDownloading(false));
// create a Blob object
const blob = new Blob([csv], { type: "text/csv" });
// create URL for the Blob object
const url = window.URL.createObjectURL(blob);
// create a link element
const a = document.createElement("a");
a.href = url;
a.download = `profile-activity-${Date.now()}.csv`;
document.body.appendChild(a);
// simulate click on the link element to trigger download
a.click();
// cleanup
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
};
return (
<Button onClick={handleDownload} loading={isDownloading}>
{isDownloading ? t("profile.stats.recent_activity.button_loading") : t("profile.stats.recent_activity.button")}
</Button>
);
}

View File

@@ -0,0 +1,55 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import { useEffect } from "react";
import { useParams } from "next/navigation";
import useSWR from "swr";
// services
import { USER_PROFILE_ACTIVITY } from "@plane/constants";
import { UserService } from "@/services/user.service";
// components
import { ActivityList } from "./activity-list";
// fetch-keys
// services
const userService = new UserService();
type Props = {
cursor: string;
perPage: number;
updateResultsCount: (count: number) => void;
updateTotalPages: (count: number) => void;
};
export function WorkspaceActivityListPage(props: Props) {
const { cursor, perPage, updateResultsCount, updateTotalPages } = props;
// router
const { workspaceSlug, userId } = useParams();
const { data: userProfileActivity } = useSWR(
workspaceSlug && userId
? USER_PROFILE_ACTIVITY(workspaceSlug.toString(), userId.toString(), {
cursor,
})
: null,
workspaceSlug && userId
? () =>
userService.getUserProfileActivity(workspaceSlug.toString(), userId.toString(), {
cursor,
per_page: perPage,
})
: null
);
useEffect(() => {
if (!userProfileActivity) return;
updateTotalPages(userProfileActivity.total_pages);
updateResultsCount(userProfileActivity.results.length);
}, [updateResultsCount, updateTotalPages, userProfileActivity]);
return <ActivityList activity={userProfileActivity} />;
}