-
Notifications
You must be signed in to change notification settings - Fork 23
fix: show email in person dropdown to distinguish duplicate names #2642
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
Changes from all commits
9c6c209
0b8ee56
0cd34a9
e0e9870
60f4acc
b2ee38d
b5ff926
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -1,6 +1,7 @@ | ||||||
import { | ||||||
addPermission, | ||||||
updatePermission | ||||||
updatePermission, | ||||||
fetchPermissionById | ||||||
} from "@flanksource-ui/api/services/permissions"; | ||||||
import { PermissionTable } from "@flanksource-ui/api/types/permissions"; | ||||||
import FormikCheckbox from "@flanksource-ui/components/Forms/Formik/FormikCheckbox"; | ||||||
|
@@ -16,7 +17,7 @@ import { useUser } from "@flanksource-ui/context"; | |||||
import { tables } from "@flanksource-ui/context/UserAccessContext/permissions"; | ||||||
import { Button } from "@flanksource-ui/ui/Buttons/Button"; | ||||||
import { Modal } from "@flanksource-ui/ui/Modal"; | ||||||
import { useMutation, useQueryClient } from "@tanstack/react-query"; | ||||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; | ||||||
import { AxiosError } from "axios"; | ||||||
import clsx from "clsx"; | ||||||
import { Form, Formik, useFormikContext } from "formik"; | ||||||
|
@@ -82,7 +83,7 @@ function PermissionFormContent({ | |||||
return ( | ||||||
<div className="flex flex-col gap-3 p-4"> | ||||||
<PermissionsSubjectControls /> | ||||||
{isResourceIdProvided ? ( | ||||||
{isResourceIdProvided && isReadOnly ? ( | ||||||
<div className="flex flex-col gap-2"> | ||||||
<label className="text-sm font-semibold">Resource</label> | ||||||
<PermissionResource /> | ||||||
|
@@ -123,17 +124,32 @@ export default function PermissionForm({ | |||||
isOpen = false, | ||||||
data | ||||||
}: PermissionFormProps) { | ||||||
const permissionId = data?.id; | ||||||
|
||||||
// Fetch full permission data when editing | ||||||
const { data: fetchedPermission, isLoading: isFetchingPermission } = useQuery( | ||||||
{ | ||||||
queryKey: ["permission", permissionId], | ||||||
queryFn: () => fetchPermissionById(permissionId!), | ||||||
enabled: !!permissionId && isOpen | ||||||
} | ||||||
); | ||||||
|
||||||
// Use fetched data if editing, otherwise use provided data | ||||||
const permissionData = permissionId ? fetchedPermission : data; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the fetch completes with an error or returns no record, fetchedPermission will be undefined and permissionData becomes undefined, causing the form to treat an edit as a creation (title, submit path) and potentially insert a duplicate. Use the stable permissionId or original data as a fallback (e.g. const permissionData = permissionId ? fetchedPermission ?? data : data) and gate add vs update logic on permissionId, not permissionData?.id.
Suggested change
Copilot uses AI. Check for mistakes. Positive FeedbackNegative Feedback |
||||||
|
||||||
// Only show loading when we're actually fetching an existing permission | ||||||
const shouldShowLoading = !!permissionId && isFetchingPermission; | ||||||
|
||||||
const isResourceIdProvided = useMemo(() => { | ||||||
return !!( | ||||||
data?.component_id || | ||||||
data?.config_id || | ||||||
data?.canary_id || | ||||||
data?.canary_id || | ||||||
data?.playbook_id || | ||||||
data?.connection_id || | ||||||
data?.object | ||||||
permissionData?.component_id || | ||||||
permissionData?.config_id || | ||||||
permissionData?.canary_id || | ||||||
permissionData?.playbook_id || | ||||||
permissionData?.connection_id | ||||||
); | ||||||
}, [data]); | ||||||
}, [permissionData]); | ||||||
|
||||||
const { user } = useUser(); | ||||||
const queryClient = useQueryClient(); | ||||||
|
@@ -191,9 +207,25 @@ export default function PermissionForm({ | |||||
|
||||||
const isLoading = adding || updating; | ||||||
|
||||||
if (shouldShowLoading) { | ||||||
return ( | ||||||
<Modal | ||||||
title="Edit Permission" | ||||||
onClose={onClose} | ||||||
open={isOpen} | ||||||
bodyClass="flex flex-col w-full flex-1 h-full overflow-y-auto" | ||||||
helpLink="/reference/permissions" | ||||||
> | ||||||
<div className="flex flex-1 items-center justify-center"> | ||||||
<FaSpinner className="animate-spin text-2xl" /> | ||||||
</div> | ||||||
</Modal> | ||||||
); | ||||||
} | ||||||
|
||||||
return ( | ||||||
<Modal | ||||||
title={data?.id ? "Edit Permission" : "Add Permission"} | ||||||
title={permissionData?.id ? "Edit Permission" : "Add Permission"} | ||||||
onClose={onClose} | ||||||
open={isOpen} | ||||||
bodyClass="flex flex-col w-full flex-1 h-full overflow-y-auto" | ||||||
|
@@ -202,32 +234,32 @@ export default function PermissionForm({ | |||||
<div className="flex flex-1 flex-col gap-2"> | ||||||
<Formik<Partial<PermissionTable>> | ||||||
initialValues={{ | ||||||
action: data?.action, | ||||||
component_id: data?.component_id, | ||||||
config_id: data?.config_id, | ||||||
canary_id: data?.canary_id, | ||||||
playbook_id: data?.playbook_id, | ||||||
deny: data?.deny ?? false, | ||||||
object: data?.object, | ||||||
description: data?.description, | ||||||
connection_id: data?.connection_id, | ||||||
created_at: data?.created_at, | ||||||
created_by: data?.created_by, | ||||||
updated_at: data?.updated_at, | ||||||
updated_by: data?.updated_by, | ||||||
id: data?.id, | ||||||
notification_id: data?.notification_id, | ||||||
person_id: data?.person_id, | ||||||
team_id: data?.team_id, | ||||||
subject: data?.subject, | ||||||
subject_type: data?.subject_type, | ||||||
until: data?.until, | ||||||
source: data?.source || "UI", | ||||||
tags: data?.tags || {}, | ||||||
agents: data?.agents || [] | ||||||
action: permissionData?.action, | ||||||
component_id: permissionData?.component_id, | ||||||
config_id: permissionData?.config_id, | ||||||
canary_id: permissionData?.canary_id, | ||||||
playbook_id: permissionData?.playbook_id, | ||||||
deny: permissionData?.deny ?? false, | ||||||
object: permissionData?.object, | ||||||
description: permissionData?.description, | ||||||
connection_id: permissionData?.connection_id, | ||||||
created_at: permissionData?.created_at, | ||||||
created_by: permissionData?.created_by, | ||||||
updated_at: permissionData?.updated_at, | ||||||
updated_by: permissionData?.updated_by, | ||||||
id: permissionData?.id, | ||||||
notification_id: permissionData?.notification_id, | ||||||
person_id: permissionData?.person_id, | ||||||
team_id: permissionData?.team_id, | ||||||
subject: permissionData?.subject, | ||||||
subject_type: permissionData?.subject_type, | ||||||
until: permissionData?.until, | ||||||
source: permissionData?.source || "UI", | ||||||
tags: permissionData?.tags || {}, | ||||||
agents: permissionData?.agents || [] | ||||||
}} | ||||||
onSubmit={(v) => { | ||||||
if (!data?.id) { | ||||||
if (!permissionData?.id) { | ||||||
return add({ | ||||||
...v | ||||||
} as PermissionTable); | ||||||
Comment on lines
+262
to
265
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Relying on permissionData?.id to choose add vs update can misclassify an edit when permissionData failed to load; this will attempt an add instead of update. Use the original permissionId prop (or a separate flag like !!permissionId) to decide the branch, ensuring failed fetches do not create duplicates. Copilot uses AI. Check for mistakes. Positive FeedbackNegative Feedback |
||||||
|
@@ -243,13 +275,13 @@ export default function PermissionForm({ | |||||
<PermissionFormContent | ||||||
isResourceIdProvided={isResourceIdProvided} | ||||||
agentOptions={agentOptions} | ||||||
source={data?.source} | ||||||
source={permissionData?.source} | ||||||
/> | ||||||
</div> | ||||||
<CanEditResource | ||||||
id={data?.id} | ||||||
id={permissionData?.id} | ||||||
resourceType={"permissions"} | ||||||
source={data?.source} | ||||||
source={permissionData?.source} | ||||||
className="flex items-center bg-gray-100 px-5 py-4" | ||||||
> | ||||||
<AuthorizationAccessCheck | ||||||
|
@@ -259,12 +291,12 @@ export default function PermissionForm({ | |||||
<div | ||||||
className={clsx( | ||||||
"flex items-center bg-gray-100 px-5 py-4", | ||||||
data?.id ? "justify-between" : "justify-end" | ||||||
permissionData?.id ? "justify-between" : "justify-end" | ||||||
)} | ||||||
> | ||||||
{data?.id && ( | ||||||
{permissionData?.id && ( | ||||||
<DeletePermission | ||||||
permissionId={data.id} | ||||||
permissionId={permissionData.id} | ||||||
onDeleted={onClose} | ||||||
/> | ||||||
)} | ||||||
|
@@ -275,7 +307,13 @@ export default function PermissionForm({ | |||||
) : undefined | ||||||
} | ||||||
type="submit" | ||||||
text={data?.id ? "Save" : isLoading ? "Saving ..." : "Save"} | ||||||
text={ | ||||||
permissionData?.id | ||||||
? "Save" | ||||||
: isLoading | ||||||
? "Saving ..." | ||||||
: "Save" | ||||||
} | ||||||
className="btn-primary" | ||||||
/> | ||||||
</div> | ||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the API returns an empty array or an error scenario masked as a 200 with no rows, this returns undefined silently, causing the edit flow to proceed as if creating a new permission. Consider validating the response and throwing (or returning a rejected promise) when no record is found: e.g. if (!response.data.length) throw new Error('Permission not found'); so the UI can show an error state instead of falling back to 'Add'.
Copilot uses AI. Check for mistakes.