Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WEB-2711]fix: guest mentions and assignees #6315

Merged
merged 4 commits into from
Jan 6, 2025
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
61 changes: 34 additions & 27 deletions apiserver/plane/app/views/search/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -277,28 +277,14 @@ def get(self, request, slug):
for field in fields:
q |= Q(**{f"{field}__icontains": query})

base_filters = Q(
q,
is_active=True,
workspace__slug=slug,
member__is_bot=False,
project_id=project_id,
role__gt=10,
)
if issue_id:
issue_created_by = (
Issue.objects.filter(id=issue_id)
.values_list("created_by_id", flat=True)
.first()
)
# Add condition to include `issue_created_by` in the query
filters = Q(member_id=issue_created_by) | base_filters
else:
filters = base_filters

# Query to fetch users
users = (
ProjectMember.objects.filter(filters)
ProjectMember.objects.filter(
q,
is_active=True,
workspace__slug=slug,
member__is_bot=False,
project_id=project_id,
)
.annotate(
member__avatar_url=Case(
When(
Expand All @@ -318,14 +304,35 @@ def get(self, request, slug):
)
)
.order_by("-created_at")
.values(
"member__avatar_url",
"member__display_name",
"member__id",
)[:count]
)

response_data["user_mention"] = list(users)
if issue_id:
issue_created_by = (
Issue.objects.filter(id=issue_id)
.values_list("created_by_id", flat=True)
.first()
)
users = (
users.filter(Q(role__gt=10) | Q(member_id=issue_created_by))
.distinct()
.values(
"member__avatar_url",
"member__display_name",
"member__id",
)
)
else:
users = (
users.filter(Q(role__gt=10))
.distinct()
.values(
"member__avatar_url",
"member__display_name",
"member__id",
)
)

response_data["user_mention"] = list(users[:count])

elif query_type == "project":
fields = ["name", "identifier"]
Expand Down
1 change: 1 addition & 0 deletions packages/types/src/search.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,5 @@ export type TSearchEntityRequestPayload = {
query_type: TSearchEntities[];
query: string;
team_id?: string;
issue_id?: string;
};
Original file line number Diff line number Diff line change
Expand Up @@ -33,31 +33,34 @@ export const ChangeIssueAssignee: React.FC<Props> = observer((props) => {
} = useMember();

const options =
projectMemberIds?.map((userId) => {
const memberDetails = getProjectMemberDetails(userId);
projectMemberIds
?.map((userId) => {
if (!projectId) return;
const memberDetails = getProjectMemberDetails(userId, projectId.toString());

return {
value: `${memberDetails?.member?.id}`,
query: `${memberDetails?.member?.display_name}`,
content: (
<>
<div className="flex items-center gap-2">
<Avatar
name={memberDetails?.member?.display_name}
src={getFileURL(memberDetails?.member?.avatar_url ?? "")}
showTooltip={false}
/>
{memberDetails?.member?.display_name}
</div>
{issue.assignee_ids.includes(memberDetails?.member?.id ?? "") && (
<div>
<Check className="h-3 w-3" />
return {
value: `${memberDetails?.member?.id}`,
query: `${memberDetails?.member?.display_name}`,
content: (
<>
<div className="flex items-center gap-2">
<Avatar
name={memberDetails?.member?.display_name}
src={getFileURL(memberDetails?.member?.avatar_url ?? "")}
showTooltip={false}
/>
{memberDetails?.member?.display_name}
</div>
)}
</>
),
};
}) ?? [];
{issue.assignee_ids.includes(memberDetails?.member?.id ?? "") && (
<div>
<Check className="h-3 w-3" />
</div>
)}
</>
),
};
})
.filter((o) => o !== undefined) ?? [];

const handleUpdateIssue = async (formData: Partial<TIssue>) => {
if (!workspaceSlug || !projectId || !issue) return;
Expand All @@ -80,15 +83,18 @@ export const ChangeIssueAssignee: React.FC<Props> = observer((props) => {

return (
<>
{options.map((option) => (
<Command.Item
key={option.value}
onSelect={() => handleIssueAssignees(option.value)}
className="focus:outline-none"
>
{option.content}
</Command.Item>
))}
{options.map(
(option) =>
option && (
<Command.Item
key={option.value}
onSelect={() => handleIssueAssignees(option.value)}
className="focus:outline-none"
>
{option.content}
</Command.Item>
)
)}
</>
);
});
79 changes: 46 additions & 33 deletions web/core/components/dropdowns/member/member-options.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { getFileURL } from "@/helpers/file.helper";
// hooks
import { useUser, useMember } from "@/hooks/store";
import { usePlatformOS } from "@/hooks/use-platform-os";
import { EUserPermissions } from "@/plane-web/constants";

interface Props {
className?: string;
Expand All @@ -39,7 +40,7 @@ export const MemberOptions: React.FC<Props> = observer((props: Props) => {
const { workspaceSlug } = useParams();
const {
getUserDetails,
project: { getProjectMemberIds, fetchProjectMembers },
project: { getProjectMemberIds, fetchProjectMembers, getProjectMemberDetails },
workspace: { workspaceMemberIds },
} = useMember();
const { data: currentUser } = useUser();
Expand Down Expand Up @@ -78,23 +79,32 @@ export const MemberOptions: React.FC<Props> = observer((props: Props) => {
}
};

const options = memberIds?.map((userId) => {
const userDetails = getUserDetails(userId);
const options = memberIds
?.map((userId) => {
const userDetails = getUserDetails(userId);
if (projectId) {
const role = getProjectMemberDetails(userId, projectId)?.role;
const isGuest = role === EUserPermissions.GUEST;
if (isGuest) return;
}

return {
value: userId,
query: `${userDetails?.display_name} ${userDetails?.first_name} ${userDetails?.last_name}`,
content: (
<div className="flex items-center gap-2">
<Avatar name={userDetails?.display_name} src={getFileURL(userDetails?.avatar_url ?? "")} />
<span className="flex-grow truncate">{currentUser?.id === userId ? t("you") : userDetails?.display_name}</span>
</div>
),
};
});
return {
value: userId,
query: `${userDetails?.display_name} ${userDetails?.first_name} ${userDetails?.last_name}`,
content: (
<div className="flex items-center gap-2">
<Avatar name={userDetails?.display_name} src={getFileURL(userDetails?.avatar_url ?? "")} />
<span className="flex-grow truncate">
{currentUser?.id === userId ? t("you") : userDetails?.display_name}
</span>
</div>
),
};
})
.filter((o) => !!o);

const filteredOptions =
query === "" ? options : options?.filter((o) => o.query.toLowerCase().includes(query.toLowerCase()));
query === "" ? options : options?.filter((o) => o?.query.toLowerCase().includes(query.toLowerCase()));

return createPortal(
<Combobox.Options data-prevent-outside-click static>
Expand Down Expand Up @@ -125,24 +135,27 @@ export const MemberOptions: React.FC<Props> = observer((props: Props) => {
<div className="mt-2 max-h-48 space-y-1 overflow-y-scroll">
{filteredOptions ? (
filteredOptions.length > 0 ? (
filteredOptions.map((option) => (
<Combobox.Option
key={option.value}
value={option.value}
className={({ active, selected }) =>
`flex w-full cursor-pointer select-none items-center justify-between gap-2 truncate rounded px-1 py-1.5 ${
active ? "bg-custom-background-80" : ""
} ${selected ? "text-custom-text-100" : "text-custom-text-200"}`
}
>
{({ selected }) => (
<>
<span className="flex-grow truncate">{option.content}</span>
{selected && <Check className="h-3.5 w-3.5 flex-shrink-0" />}
</>
)}
</Combobox.Option>
))
filteredOptions.map(
(option) =>
option && (
<Combobox.Option
key={option.value}
value={option.value}
className={({ active, selected }) =>
`flex w-full cursor-pointer select-none items-center justify-between gap-2 truncate rounded px-1 py-1.5 ${
active ? "bg-custom-background-80" : ""
} ${selected ? "text-custom-text-100" : "text-custom-text-200"}`
}
>
{({ selected }) => (
<>
<span className="flex-grow truncate">{option.content}</span>
{selected && <Check className="h-3.5 w-3.5 flex-shrink-0" />}
</>
)}
</Combobox.Option>
)
)
) : (
<p className="px-1.5 py-1 italic text-custom-text-400">{t("no_matching_results")}</p>
)
Expand Down
4 changes: 3 additions & 1 deletion web/core/components/editor/embeds/mentions/user.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ type Props = {

export const EditorUserMention: React.FC<Props> = observer((props) => {
const { id } = props;
// router
const { projectId } = useParams();
// states
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
const [referenceElement, setReferenceElement] = useState<HTMLAnchorElement | null>(null);
Expand All @@ -44,7 +46,7 @@ export const EditorUserMention: React.FC<Props> = observer((props) => {
});
// derived values
const userDetails = getUserDetails(id);
const roleDetails = getProjectMemberDetails(id)?.role;
const roleDetails = projectId ? getProjectMemberDetails(id, projectId.toString())?.role : null;
const profileLink = `/${workspaceSlug}/profile/${id}`;

if (!userDetails) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ interface LiteTextEditorWrapperProps
isSubmitting?: boolean;
showToolbarInitially?: boolean;
uploadFile: (file: File) => Promise<string>;
issue_id?: string;
}

export const LiteTextEditor = React.forwardRef<EditorRefApi, LiteTextEditorWrapperProps>((props, ref) => {
Expand All @@ -38,6 +39,7 @@ export const LiteTextEditor = React.forwardRef<EditorRefApi, LiteTextEditorWrapp
workspaceSlug,
workspaceId,
projectId,
issue_id,
accessSpecifier,
handleAccessChange,
showAccessSpecifier = false,
Expand All @@ -58,6 +60,7 @@ export const LiteTextEditor = React.forwardRef<EditorRefApi, LiteTextEditorWrapp
await workspaceService.searchEntity(workspaceSlug?.toString() ?? "", {
...payload,
project_id: projectId?.toString() ?? "",
issue_id: issue_id,
}),
});
// file size
Expand Down
1 change: 1 addition & 0 deletions web/core/components/issues/description-input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ export const IssueDescriptionInput: FC<IssueDescriptionInputProps> = observer((p
await workspaceService.searchEntity(workspaceSlug?.toString() ?? "", {
...payload,
project_id: projectId?.toString() ?? "",
issue_id: issueId?.toString(),
})
}
containerClassName={containerClassName}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export const IssueActivityCommentRoot: FC<TIssueActivityCommentRoot> = observer(
activityComment.activity_type === "COMMENT" ? (
<IssueCommentCard
projectId={projectId}
issueId={issueId}
key={activityComment.id}
workspaceSlug={workspaceSlug}
commentId={activityComment.id}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { IssueCommentBlock } from "./comment-block";

type TIssueCommentCard = {
projectId: string;
issueId: string;
workspaceSlug: string;
commentId: string;
activityOperations: TActivityOperations;
Expand All @@ -34,6 +35,7 @@ export const IssueCommentCard: FC<TIssueCommentCard> = observer((props) => {
const {
workspaceSlug,
projectId,
issueId,
commentId,
activityOperations,
ends,
Expand Down Expand Up @@ -144,6 +146,7 @@ export const IssueCommentCard: FC<TIssueCommentCard> = observer((props) => {
<LiteTextEditor
workspaceId={workspaceId}
projectId={projectId}
issue_id={issueId}
workspaceSlug={workspaceSlug}
ref={editorRef}
id={comment.id}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ export const IssueCommentCreate: FC<TIssueCommentCreate> = (props) => {
id={"add_comment_" + issueId}
value={"<p></p>"}
projectId={projectId}
issue_id={issueId}
workspaceSlug={workspaceSlug}
onEnterKeyPress={(e) => {
if (!isEmpty && !isSubmitting) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export const IssueCommentRoot: FC<TIssueCommentRoot> = observer((props) => {
commentIds.map((commentId, index) => (
<IssueCommentCard
projectId={projectId}
issueId={issueId}
key={commentId}
workspaceSlug={workspaceSlug}
commentId={commentId}
Expand Down
2 changes: 1 addition & 1 deletion web/core/components/issues/issue-modal/base.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
import React, { useEffect, useRef, useState } from "react";
import { observer } from "mobx-react";
import { useParams, usePathname } from "next/navigation";
import { EIssuesStoreType } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
// types
import { EIssuesStoreType } from "@plane/constants";
import type { TBaseIssue, TIssue } from "@plane/types";
// ui
import { EModalPosition, EModalWidth, ModalCore, TOAST_TYPE, setToast } from "@plane/ui";
Expand Down
Loading
Loading