Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 5 additions & 2 deletions packages/constants/src/analytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@
import { TXAxisValues, TYAxisValues } from "@plane/types";

export const ANALYTICS_TABS = [
{ key: "scope_and_demand", title: "Scope and Demand" },
{ key: "custom", title: "Custom Analytics" },
{
key: "scope_and_demand",
i18n_title: "workspace_analytics.tabs.scope_and_demand",
},
{ key: "custom", i18n_title: "workspace_analytics.tabs.custom" },
];

export const ANALYTICS_X_AXIS_VALUES: { value: TXAxisValues; label: string }[] =
Expand Down
37 changes: 33 additions & 4 deletions packages/i18n/src/locales/en/translations.json
Original file line number Diff line number Diff line change
Expand Up @@ -328,16 +328,15 @@
"deleting": "Deleting",
"make_a_copy": "Make a copy",
"move_to_project": "Move to project",
"deleting": "Deleting",
"make_a_copy": "Make a copy",
"move_to_project": "Move to project",
"good": "Good",
"morning": "morning",
"afternoon": "afternoon",
"evening": "evening",
"show_all": "Show all",
"show_less": "Show less",
"no_data_yet": "No Data yet",
"unassigned": "Unassigned",
"work_items": "Work items",
"add_link": "Add link",
"points": "Points",
"no_assignee": "No assignee",
Expand Down Expand Up @@ -677,6 +676,35 @@
},

"workspace_analytics": {
"label": "Analytics",
"page_label": "{workspace} - Analytics",
"open_tasks": "Total open tasks",
"error": "There was some error in fetching the data.",
"work_items_closed_in": "Work items closed in",
"selected_projects": "Selected projects",
"total_members": "Total members",
"total_cycles": "Total cycles",
"total_modules": "Total modules",
"pending_work_items":{
"title": "Pending work items",
"empty_state": "Analysis of pending work items by co-workers appears here."
},
"work_items_closed_in_a_year": {
"title": "Work items closed in a year",
"empty_state": "Close work items to view analysis of the same in the form of a graph."
},
"most_work_items_created":{
"title": "Most work items created",
"empty_state": "Co-workers and the number of work items created by them appears here."
},
"most_work_items_closed":{
"title": "Most work items closed",
"empty_state": "Co-workers and the number of work items closed by them appears here."
},
"tabs": {
"scope_and_demand": "Scope and Demand",
"custom": "Custom Analytics"
},
"empty_state": {
"general": {
"title": "Track progress, workloads, and allocations. Spot trends, remove blockers, and move work faster",
Expand Down Expand Up @@ -1382,7 +1410,8 @@
"exporter": {
"csv": {
"title": "CSV",
"description": "Export issues to a CSV file."
"description": "Export issues to a CSV file.",
"short_description": "Export as csv"
},
"xlsx": {
"title": "Excel",
Expand Down
7 changes: 6 additions & 1 deletion web/app/[workspaceSlug]/(projects)/analytics/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,12 @@ export const WorkspaceAnalyticsHeader = observer(() => {
<Breadcrumbs>
<Breadcrumbs.BreadcrumbItem
type="text"
link={<BreadcrumbLink label={t("analytics")} icon={<BarChart2 className="h-4 w-4 text-custom-text-300" />} />}
link={
<BreadcrumbLink
label={t("workspace_analytics.label")}
icon={<BarChart2 className="h-4 w-4 text-custom-text-300" />}
/>
}
/>
</Breadcrumbs>
{analytics_tab === "custom" ? (
Expand Down
6 changes: 4 additions & 2 deletions web/app/[workspaceSlug]/(projects)/analytics/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ const AnalyticsPage = observer(() => {
// helper hooks
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/onboarding/analytics" });
// derived values
const pageTitle = currentWorkspace?.name ? `${currentWorkspace?.name} - Analytics` : undefined;
const pageTitle = currentWorkspace?.name
? t(`workspace_analytics.page_label`, { workspace: currentWorkspace?.name })
: undefined;

// permissions
const canPerformEmptyStateActions = allowPermissions(
Expand All @@ -59,7 +61,7 @@ const AnalyticsPage = observer(() => {
selected ? "text-custom-primary-100 " : "hover:text-custom-text-200"
}`}
>
{tab.title}
{t(tab.i18n_title)}
<div
className={`border absolute bottom-0 right-0 left-0 rounded-t-md ${selected ? "border-custom-primary-100" : "border-transparent group-hover:border-custom-border-200"}`}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { observer } from "mobx-react";
// icons
import { Contrast, LayoutGrid, Users } from "lucide-react";
// components
import { useTranslation } from "@plane/i18n";
import { Logo } from "@/components/common";
// helpers
import { truncateText } from "@/helpers/string.helper";
Expand All @@ -16,10 +17,11 @@ export const CustomAnalyticsSidebarProjectsList: React.FC<Props> = observer((pro
const { projectIds } = props;

const { getProjectById } = useProject();
const { t } = useTranslation();

return (
<div className="relative flex flex-col gap-4 h-full">
<h4 className="font-medium">Selected Projects</h4>
<h4 className="font-medium">{t("workspace_analytics.selected_projects")}</h4>
<div className="relative space-y-6 overflow-hidden overflow-y-auto vertical-scrollbar scrollbar-md">
{projectIds.map((projectId) => {
const project = getProjectById(projectId);
Expand All @@ -41,21 +43,21 @@ export const CustomAnalyticsSidebarProjectsList: React.FC<Props> = observer((pro
<div className="flex items-center justify-between gap-2 text-xs">
<div className="flex items-center gap-2">
<Users className="text-custom-text-200" size={14} strokeWidth={2} />
<h6>Total members</h6>
<h6>{t("workspace_analytics.total_members")}</h6>
</div>
<span className="text-custom-text-200">{project.total_members}</span>
</div>
<div className="flex items-center justify-between gap-2 text-xs">
<div className="flex items-center gap-2">
<Contrast className="text-custom-text-200" size={14} strokeWidth={2} />
<h6>Total cycles</h6>
<h6>{t("workspace_analytics.total_cycles")}</h6>
</div>
<span className="text-custom-text-200">{project.total_cycles}</span>
</div>
<div className="flex items-center justify-between gap-2 text-xs">
<div className="flex items-center gap-2">
<LayoutGrid className="text-custom-text-200" size={14} strokeWidth={2} />
<h6>Total modules</h6>
<h6>{t("workspace_analytics.total_modules")}</h6>
</div>
<span className="text-custom-text-200">{project.total_modules}</span>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { mutate } from "swr";
// icons
import { CalendarDays, Download, RefreshCw } from "lucide-react";
// types
import { useTranslation } from "@plane/i18n";
import { IAnalyticsParams, IAnalyticsResponse, IExportAnalyticsFormData, IWorkspace } from "@plane/types";
// ui
import { Button, LayersIcon, TOAST_TYPE, setToast } from "@plane/ui";
Expand Down Expand Up @@ -38,11 +39,12 @@ export const CustomAnalyticsSidebar: React.FC<Props> = observer((props) => {
const { data: currentUser } = useUser();
const { workspaceProjectIds, getProjectById } = useProject();
const { getWorkspaceById } = useWorkspace();
const { t } = useTranslation();

const { fetchCycleDetails, getCycleById } = useCycle();
const { fetchModuleDetails, getModuleById } = useModule();

const projectDetails = projectId ? getProjectById(projectId.toString()) ?? undefined : undefined;
const projectDetails = projectId ? (getProjectById(projectId.toString()) ?? undefined) : undefined;

const trackExportAnalytics = () => {
if (!currentUser) return;
Expand Down Expand Up @@ -152,7 +154,7 @@ export const CustomAnalyticsSidebar: React.FC<Props> = observer((props) => {
<div className="flex items-center gap-1 rounded-md bg-custom-background-80 px-3 py-1 text-xs text-custom-text-200">
<LayersIcon height={14} width={14} />
{analytics ? analytics.total : "..."}
<div className={cn(isProjectLevel ? "hidden md:block" : "")}>Issues</div>
<div className={cn(isProjectLevel ? "hidden md:block" : "")}>{t("work_items")}</div>
</div>
{isProjectLevel && (
<div className="flex items-center gap-1 rounded-md bg-custom-background-80 px-3 py-1 text-xs text-custom-text-200">
Expand Down Expand Up @@ -187,10 +189,10 @@ export const CustomAnalyticsSidebar: React.FC<Props> = observer((props) => {
mutate(ANALYTICS(workspaceSlug.toString(), params));
}}
>
<div className={cn(isProjectLevel ? "hidden md:block" : "")}>Refresh</div>
<div className={cn(isProjectLevel ? "hidden md:block" : "", "capitalize")}>{t("refresh")}</div>
</Button>
<Button variant="primary" prependIcon={<Download className="h-3.5 w-3.5" />} onClick={exportAnalytics}>
<div className={cn(isProjectLevel ? "hidden md:block" : "")}>Export as CSV</div>
<div className={cn(isProjectLevel ? "hidden md:block" : "")}>{t("exporter.csv.short_description")}</div>
</Button>
</div>
</div>
Expand Down
5 changes: 3 additions & 2 deletions web/core/components/analytics/project-modal/main-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { observer } from "mobx-react";
import { Tab } from "@headlessui/react";
// plane package imports
import { ANALYTICS_TABS } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
import { ICycle, IModule, IProject } from "@plane/types";
// components
import { CustomAnalytics, ScopeAndDemand } from "@/components/analytics";
Expand All @@ -16,7 +17,7 @@ type Props = {

export const ProjectAnalyticsModalMainContent: React.FC<Props> = observer((props) => {
const { fullScreen, cycleDetails, moduleDetails } = props;

const { t } = useTranslation();
return (
<Tab.Group as={React.Fragment}>
<Tab.List as="div" className="flex space-x-2 border-b h-[50px] border-custom-border-200 px-0 md:px-3">
Expand All @@ -28,7 +29,7 @@ export const ProjectAnalyticsModalMainContent: React.FC<Props> = observer((props
selected ? "text-custom-primary-100 " : "hover:text-custom-text-200"
}`}
>
{tab.title}
{t(tab.i18n_title)}
<div
className={`border absolute bottom-0 right-0 left-0 rounded-t-md ${selected ? "border-custom-primary-100" : "border-transparent group-hover:border-custom-border-200"}`}
/>
Expand Down
75 changes: 40 additions & 35 deletions web/core/components/analytics/scope-and-demand/demand.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// plane imports
import { STATE_GROUPS } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
// types
import { IDefaultAnalyticsResponse, TStateGroups } from "@plane/types";
// constants
Expand All @@ -9,45 +10,49 @@ type Props = {
defaultAnalytics: IDefaultAnalyticsResponse;
};

export const AnalyticsDemand: React.FC<Props> = ({ defaultAnalytics }) => (
<Card>
<div>
<h4 className="text-base font-medium text-custom-text-100">Total open tasks</h4>
<h3 className="mt-1 text-xl font-semibold">{defaultAnalytics.open_issues}</h3>
</div>
<div className="space-y-6 pb-2">
{defaultAnalytics?.open_issues_classified.map((group) => {
const percentage = ((group.state_count / defaultAnalytics.total_issues) * 100).toFixed(0);
export const AnalyticsDemand: React.FC<Props> = ({ defaultAnalytics }) => {
const { t } = useTranslation();

return (
<div key={group.state_group} className="space-y-2">
<div className="flex items-center justify-between gap-2 text-xs">
<div className="flex items-center gap-1">
<span
className="h-2 w-2 rounded-full"
return (
<Card>
<div>
<h4 className="text-base font-medium text-custom-text-100">{t("workspace_analytics.open_tasks")}</h4>
<h3 className="mt-1 text-xl font-semibold">{defaultAnalytics.open_issues}</h3>
</div>
<div className="space-y-6 pb-2">
{defaultAnalytics?.open_issues_classified.map((group) => {
const percentage = ((group.state_count / defaultAnalytics.total_issues) * 100).toFixed(0);

return (
<div key={group.state_group} className="space-y-2">
<div className="flex items-center justify-between gap-2 text-xs">
<div className="flex items-center gap-1">
<span
className="h-2 w-2 rounded-full"
style={{
backgroundColor: STATE_GROUPS[group.state_group as TStateGroups].color,
}}
/>
<h6 className="capitalize">{group.state_group}</h6>
<span className="ml-1 rounded-3xl bg-custom-background-80 px-2 py-0.5 text-[0.65rem] text-custom-text-200">
{group.state_count}
</span>
</div>
<p className="text-custom-text-200">{percentage}%</p>
</div>
<div className="bar relative h-1 w-full rounded bg-custom-background-80">
<div
className="absolute left-0 top-0 h-1 rounded duration-300"
style={{
width: `${percentage}%`,
backgroundColor: STATE_GROUPS[group.state_group as TStateGroups].color,
}}
/>
<h6 className="capitalize">{group.state_group}</h6>
<span className="ml-1 rounded-3xl bg-custom-background-80 px-2 py-0.5 text-[0.65rem] text-custom-text-200">
{group.state_count}
</span>
</div>
<p className="text-custom-text-200">{percentage}%</p>
</div>
<div className="bar relative h-1 w-full rounded bg-custom-background-80">
<div
className="absolute left-0 top-0 h-1 rounded duration-300"
style={{
width: `${percentage}%`,
backgroundColor: STATE_GROUPS[group.state_group as TStateGroups].color,
}}
/>
</div>
</div>
);
})}
</div>
</Card>
);
);
})}
</div>
</Card>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { useParams } from "next/navigation";
import useSWR from "swr";
// ui
import { useTranslation } from "@plane/i18n";
import { Button, ContentWrapper, Loader } from "@plane/ui";
// components
import { AnalyticsDemand, AnalyticsLeaderBoard, AnalyticsScope, AnalyticsYearWiseIssues } from "@/components/analytics";
Expand All @@ -21,6 +22,7 @@ export const ScopeAndDemand: React.FC<Props> = (props) => {
const { fullScreen = true } = props;

const { workspaceSlug, projectId, cycleId, moduleId } = useParams();
const { t } = useTranslation();

const isProjectLevel = projectId ? true : false;

Expand Down Expand Up @@ -66,8 +68,8 @@ export const ScopeAndDemand: React.FC<Props> = (props) => {
count: user?.count,
id: user?.created_by__id,
}))}
title="Most issues created"
emptyStateMessage="Co-workers and the number of issues created by them appears here."
title={t("workspace_analytics.most_work_items_created.title")}
emptyStateMessage={t("workspace_analytics.most_work_items_created.empty_state")}
workspaceSlug={workspaceSlug?.toString() ?? ""}
/>
<AnalyticsLeaderBoard
Expand All @@ -79,8 +81,8 @@ export const ScopeAndDemand: React.FC<Props> = (props) => {
count: user?.count,
id: user?.assignees__id,
}))}
title="Most issues closed"
emptyStateMessage="Co-workers and the number of issues closed by them appears here."
title={t("workspace_analytics.most_work_items_closed.title")}
emptyStateMessage={t("workspace_analytics.most_work_items_closed.empty_state")}
workspaceSlug={workspaceSlug?.toString() ?? ""}
/>
<div className={fullScreen ? "md:col-span-2" : ""}>
Expand All @@ -99,10 +101,10 @@ export const ScopeAndDemand: React.FC<Props> = (props) => {
) : (
<div className="grid h-full place-items-center p-5">
<div className="space-y-4 text-custom-text-200">
<p className="text-sm">There was some error in fetching the data.</p>
<p className="text-sm">{t("workspace_analytics.error")}</p>
<div className="flex items-center justify-center gap-2">
<Button variant="primary" onClick={() => mutateDefaultAnalytics()}>
Refresh
{t("refresh")}
</Button>
</div>
</div>
Expand Down
Loading
Loading