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
5 changes: 5 additions & 0 deletions packages/types/src/issues.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,11 @@ export type GroupByColumnTypes =
| "created_by"
| "team_project";

type TGetColumns = {
isWorkspaceLevel?: boolean;
projectId?: string;
};

export interface IGroupByColumn {
id: string;
name: string;
Expand Down
32 changes: 31 additions & 1 deletion web/ce/components/issues/issue-layouts/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
Users,
} from "lucide-react";
// types
import { IGroupByColumn, IIssueDisplayProperties, TSpreadsheetColumn } from "@plane/types";
import { IGroupByColumn, IIssueDisplayProperties, TGetColumns, TSpreadsheetColumn } from "@plane/types";
import { DiceIcon, DoubleCircleIcon, ISvgIcons } from "@plane/ui";
// components
import {
Expand All @@ -32,6 +32,36 @@ import {
SpreadsheetSubIssueColumn,
SpreadsheetUpdatedOnColumn,
} from "@/components/issues/issue-layouts/spreadsheet";
// store
import { store } from "@/lib/store-context";

export type TGetScopeMemberIdsResult = {
memberIds: string[];
includeNone: boolean;
};

export const getScopeMemberIds = ({ isWorkspaceLevel, projectId }: TGetColumns): TGetScopeMemberIdsResult => {
// store values
const { workspaceMemberIds } = store.memberRoot.workspace;
const { projectMemberIds } = store.memberRoot.project;
// derived values
const memberIds = workspaceMemberIds;

if (isWorkspaceLevel) {
return { memberIds: memberIds ?? [], includeNone: true };
}

if (projectId || (projectMemberIds && projectMemberIds.length > 0)) {
const { getProjectMemberIds } = store.memberRoot.project;
const _projectMemberIds = projectId ? getProjectMemberIds(projectId, false) : projectMemberIds;
return {
memberIds: _projectMemberIds ?? [],
includeNone: true,
};
}

return { memberIds: [], includeNone: true };
};

export const getTeamProjectColumns = (): IGroupByColumn[] | undefined => undefined;

Expand Down
73 changes: 34 additions & 39 deletions web/core/components/issues/issue-layouts/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import { CSSProperties, FC } from "react";
import { extractInstruction } from "@atlaskit/pragmatic-drag-and-drop-hitbox/tree-item";
import { isEmpty } from "lodash";
import clone from "lodash/clone";
import concat from "lodash/concat";
import isEqual from "lodash/isEqual";
Expand All @@ -27,6 +26,7 @@ import {
TGroupedIssues,
IWorkspaceView,
IIssueDisplayFilterOptions,
TGetColumns,
} from "@plane/types";
// plane ui
import { Avatar, CycleGroupIcon, DiceIcon, ISvgIcons, PriorityIcon, StateGroupIcon } from "@plane/ui";
Expand All @@ -37,7 +37,11 @@ import { Logo } from "@/components/common";
// store
import { store } from "@/lib/store-context";
// plane web store
import { getTeamProjectColumns, SpreadSheetPropertyIconMap } from "@/plane-web/components/issues/issue-layouts/utils";
import {
getScopeMemberIds,
getTeamProjectColumns,
SpreadSheetPropertyIconMap,
} from "@/plane-web/components/issues/issue-layouts/utils";
// store
import { ISSUE_FILTER_DEFAULT_DATA } from "@/store/issue/helpers/base-issues.store";
import { DEFAULT_DISPLAY_PROPERTIES } from "@/store/issue/issue-details/sub_issues_filter.store";
Expand All @@ -60,13 +64,16 @@ export type IssueUpdates = {
};
};

type TGetColumns = {
isWorkspaceLevel?: boolean;
projectId?: string;
};

export const isWorkspaceLevel = (type: EIssuesStoreType) =>
[EIssuesStoreType.PROFILE, EIssuesStoreType.GLOBAL].includes(type) ? true : false;
[
EIssuesStoreType.PROFILE,
EIssuesStoreType.GLOBAL,
EIssuesStoreType.TEAM,
EIssuesStoreType.TEAM_VIEW,
EIssuesStoreType.PROJECT_VIEW,
].includes(type)
? true
: false;

type TGetGroupByColumns = {
groupBy: GroupByColumnTypes | null;
Expand Down Expand Up @@ -264,40 +271,28 @@ const getLabelsColumns = ({ isWorkspaceLevel }: TGetColumns): IGroupByColumn[] =
};

const getAssigneeColumns = ({ isWorkspaceLevel, projectId }: TGetColumns): IGroupByColumn[] | undefined => {
// store values
const { getUserDetails } = store.memberRoot;
// derived values
const { memberIds, includeNone } = getScopeMemberIds({ isWorkspaceLevel, projectId });
const assigneeColumns: IGroupByColumn[] = [];
const {
project: { projectMemberIds, getProjectMemberIds },
getUserDetails,
} = store.memberRoot;
// if workspace level
if (isWorkspaceLevel) {
const { workspaceMemberIds } = store.memberRoot.workspace;
if (!workspaceMemberIds) return;
workspaceMemberIds.forEach((memberId) => {
const member = getUserDetails(memberId);
assigneeColumns.push({
id: memberId,
name: member?.display_name || "",
icon: <Avatar name={member?.display_name} src={getFileURL(member?.avatar_url ?? "")} size="md" />,
payload: { assignee_ids: [memberId] },
});
});
} else {
// if project level
const _projectMemberIds = projectId ? getProjectMemberIds(projectId, false) : projectMemberIds;
if (!_projectMemberIds) return;
// Map project member ids to group by assignee columns
_projectMemberIds.forEach((memberId) => {
const member = getUserDetails(memberId);
assigneeColumns.push({
id: memberId,
name: member?.display_name || "",
icon: <Avatar name={member?.display_name} src={getFileURL(member?.avatar_url ?? "")} size="md" />,
payload: { assignee_ids: [memberId] },
});

if (!memberIds) return [];

memberIds.forEach((memberId) => {
const member = getUserDetails(memberId);
if (!member) return;
assigneeColumns.push({
id: memberId,
name: member?.display_name || "",
icon: <Avatar name={member?.display_name} src={getFileURL(member?.avatar_url ?? "")} size="md" />,
payload: { assignee_ids: [memberId] },
});
});
if (includeNone) {
assigneeColumns.push({ id: "None", name: "None", icon: <Avatar size="md" />, payload: {} });
}
assigneeColumns.push({ id: "None", name: "None", icon: <Avatar size="md" />, payload: {} });

return assigneeColumns;
Comment on lines 273 to 296
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Guard for empty member list is ineffective

memberIds is always an array (helper guarantees at least []), so
if (!memberIds) return []; never triggers. Early-return should test length:

-  if (!memberIds) return [];
+  if (memberIds.length === 0) {
+    return includeNone ? [{ id: "None", name: "None", icon: <Avatar size="md" />, payload: {} }] : [];
+  }

Ensures we skip the loop when there are no members and prevents unnecessary computation.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const getAssigneeColumns = ({ isWorkspaceLevel, projectId }: TGetColumns): IGroupByColumn[] | undefined => {
// store values
const { getUserDetails } = store.memberRoot;
// derived values
const { memberIds, includeNone } = getScopeMemberIds({ isWorkspaceLevel, projectId });
const assigneeColumns: IGroupByColumn[] = [];
const {
project: { projectMemberIds, getProjectMemberIds },
getUserDetails,
} = store.memberRoot;
// if workspace level
if (isWorkspaceLevel) {
const { workspaceMemberIds } = store.memberRoot.workspace;
if (!workspaceMemberIds) return;
workspaceMemberIds.forEach((memberId) => {
const member = getUserDetails(memberId);
assigneeColumns.push({
id: memberId,
name: member?.display_name || "",
icon: <Avatar name={member?.display_name} src={getFileURL(member?.avatar_url ?? "")} size="md" />,
payload: { assignee_ids: [memberId] },
});
});
} else {
// if project level
const _projectMemberIds = projectId ? getProjectMemberIds(projectId, false) : projectMemberIds;
if (!_projectMemberIds) return;
// Map project member ids to group by assignee columns
_projectMemberIds.forEach((memberId) => {
const member = getUserDetails(memberId);
assigneeColumns.push({
id: memberId,
name: member?.display_name || "",
icon: <Avatar name={member?.display_name} src={getFileURL(member?.avatar_url ?? "")} size="md" />,
payload: { assignee_ids: [memberId] },
});
if (!memberIds) return [];
memberIds.forEach((memberId) => {
const member = getUserDetails(memberId);
if (!member) return;
assigneeColumns.push({
id: memberId,
name: member?.display_name || "",
icon: <Avatar name={member?.display_name} src={getFileURL(member?.avatar_url ?? "")} size="md" />,
payload: { assignee_ids: [memberId] },
});
});
if (includeNone) {
assigneeColumns.push({ id: "None", name: "None", icon: <Avatar size="md" />, payload: {} });
}
assigneeColumns.push({ id: "None", name: "None", icon: <Avatar size="md" />, payload: {} });
return assigneeColumns;
const getAssigneeColumns = ({ isWorkspaceLevel, projectId }: TGetColumns): IGroupByColumn[] | undefined => {
// store values
const { getUserDetails } = store.memberRoot;
// derived values
const { memberIds, includeNone } = getScopeMemberIds({ isWorkspaceLevel, projectId });
const assigneeColumns: IGroupByColumn[] = [];
if (memberIds.length === 0) {
return includeNone
? [{ id: "None", name: "None", icon: <Avatar size="md" />, payload: {} }]
: [];
}
memberIds.forEach((memberId) => {
const member = getUserDetails(memberId);
if (!member) return;
assigneeColumns.push({
id: memberId,
name: member.display_name || "",
icon: (
<Avatar
name={member.display_name}
src={getFileURL(member.avatar_url ?? "")}
size="md"
/>
),
payload: { assignee_ids: [memberId] },
});
});
if (includeNone) {
assigneeColumns.push({
id: "None",
name: "None",
icon: <Avatar size="md" />,
payload: {},
});
}
return assigneeColumns;
};
🤖 Prompt for AI Agents
In web/core/components/issues/issue-layouts/utils.tsx around lines 273 to 296,
the guard condition checking if memberIds is falsy is ineffective because
memberIds is always an array. Replace the condition `if (!memberIds) return [];`
with a check for an empty array using `if (memberIds.length === 0) return [];`
to properly skip processing when there are no members.

};

Expand Down