[WEB-5647] chore: list layout work item identifier enhancements (#8326)

This commit is contained in:
Anmol Singh Bhatia
2025-12-12 19:21:29 +05:30
committed by GitHub
parent 1b427392c4
commit 2ac5efe2f0
5 changed files with 43 additions and 3 deletions

View File

@@ -3,6 +3,7 @@ from rest_framework import serializers
# Module imports
from .base import BaseSerializer, DynamicBaseSerializer
from django.db.models import Max
from plane.app.serializers.workspace import WorkspaceLiteSerializer
from plane.app.serializers.user import UserLiteSerializer, UserAdminLiteSerializer
from plane.db.models import (
@@ -12,6 +13,7 @@ from plane.db.models import (
ProjectIdentifier,
DeployBoard,
ProjectPublicMember,
IssueSequence
)
from plane.utils.content_validator import (
validate_html_content,
@@ -105,6 +107,7 @@ class ProjectListSerializer(DynamicBaseSerializer):
members = serializers.SerializerMethodField()
cover_image_url = serializers.CharField(read_only=True)
inbox_view = serializers.BooleanField(read_only=True, source="intake_view")
next_work_item_sequence = serializers.SerializerMethodField()
def get_members(self, obj):
project_members = getattr(obj, "members_list", None)
@@ -113,6 +116,11 @@ class ProjectListSerializer(DynamicBaseSerializer):
return [member.member_id for member in project_members if member.is_active and not member.member.is_bot]
return []
def get_next_work_item_sequence(self, obj):
"""Get the next sequence ID that will be assigned to a new issue"""
max_sequence = IssueSequence.objects.filter(project_id=obj.id).aggregate(max_seq=Max("sequence"))["max_seq"]
return (max_sequence + 1) if max_sequence else 1
class Meta:
model = Project
fields = "__all__"

View File

@@ -28,6 +28,7 @@ import { IssueIdentifier } from "@/plane-web/components/issues/issue-details/iss
import { IssueStats } from "@/plane-web/components/issues/issue-layouts/issue-stats";
// types
import { WithDisplayPropertiesHOC } from "../properties/with-display-properties-HOC";
import { calculateIdentifierWidth } from "../utils";
import type { TRenderQuickActions } from "./list-view-types";
interface IssueBlockProps {
@@ -76,7 +77,7 @@ export const IssueBlock = observer(function IssueBlock(props: IssueBlockProps) {
const projectId = routerProjectId?.toString();
// hooks
const { sidebarCollapsed: isSidebarCollapsed } = useAppTheme();
const { getProjectIdentifierById } = useProject();
const { getProjectIdentifierById, currentProjectNextSequenceId } = useProject();
const {
getIsIssuePeeked,
peekIssue,
@@ -150,8 +151,12 @@ export const IssueBlock = observer(function IssueBlock(props: IssueBlockProps) {
}
};
//TODO: add better logic. This is to have a min width for ID/Key based on the length of project identifier
const keyMinWidth = displayProperties?.key ? (projectIdentifier?.length ?? 0) * 7 : 0;
// Calculate width for: projectIdentifier + "-" + dynamic sequence number digits
// Use next_work_item_sequence from backend (static value from project endpoint)
const maxSequenceId = currentProjectNextSequenceId ?? 1;
const keyMinWidth = displayProperties?.key
? calculateIdentifierWidth(projectIdentifier?.length ?? 0, maxSequenceId)
: 0;
const workItemLink = generateWorkItemLink({
workspaceSlug,

View File

@@ -748,3 +748,18 @@ export const isFiltersApplied = (filters: IIssueFilterOptions): boolean =>
if (Array.isArray(value)) return value.length > 0;
return value !== undefined && value !== null && value !== "";
});
/**
* Calculates the minimum width needed for issue identifiers in list layouts
* @param projectIdentifierLength - Length of the project identifier (e.g., "PROJ" = 4)
* @param maxSequenceId - Maximum sequence ID in the project (e.g., 1234)
* @returns Width in pixels needed to display the identifier
*
* @example
* // For "PROJ-1234"
* calculateIdentifierWidth(4, 1234) // Returns width for "PROJ" + "-" + "1234"
*/
export const calculateIdentifierWidth = (projectIdentifierLength: number, maxSequenceId: number): number => {
const sequenceDigits = Math.max(1, Math.floor(Math.log10(maxSequenceId)) + 1);
return projectIdentifierLength * 7 + 7 + sequenceDigits * 7; // project identifier chars + dash + sequence digits
};

View File

@@ -30,6 +30,7 @@ export interface IProjectStore {
joinedProjectIds: string[];
favoriteProjectIds: string[];
currentProjectDetails: TProject | undefined;
currentProjectNextSequenceId: number | undefined;
// actions
getProjectById: (projectId: string | undefined | null) => TProject | undefined;
getPartialProjectById: (projectId: string | undefined | null) => TPartialProject | undefined;
@@ -107,6 +108,7 @@ export class ProjectStore implements IProjectStore {
currentProjectDetails: computed,
joinedProjectIds: computed,
favoriteProjectIds: computed,
currentProjectNextSequenceId: computed,
// helper actions
processProjectAfterCreation: action,
// fetch actions
@@ -216,6 +218,15 @@ export class ProjectStore implements IProjectStore {
return this.projectMap?.[this.rootStore.router.projectId];
}
/**
* Returns the next sequence ID for the current project
* Used for calculating identifier width in list layouts
*/
get currentProjectNextSequenceId() {
if (!this.rootStore.router.projectId) return undefined;
return this.currentProjectDetails?.next_work_item_sequence;
}
/**
* Returns joined project IDs belong to the current workspace
*/

View File

@@ -51,6 +51,7 @@ export interface IProject extends IPartialProject {
is_favorite?: boolean;
members?: string[];
timezone?: string;
next_work_item_sequence?: number;
}
export type TProjectAnalyticsCountParams = {