Skip to content

Commit

Permalink
[WEB-2711]fix: guest mentions and assignees (#6315)
Browse files Browse the repository at this point in the history
* chore: issue search filter

* * fix: restricting guest user from assignees and mentions

---------

Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
  • Loading branch information
mathalav55 and NarayanBavisetti authored Jan 6, 2025
1 parent d26fb8c commit 625cbf8
Show file tree
Hide file tree
Showing 15 changed files with 153 additions and 105 deletions.
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
Loading

0 comments on commit 625cbf8

Please sign in to comment.