mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-15 09:35:37 +00:00
Merge pull request #3453 from Infisical/daniel/reminders
feat(reminders): specify recipients
This commit is contained in:
+76
-9
@@ -6,10 +6,28 @@ import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { z } from "zod";
|
||||
|
||||
import { Button, FormControl, Input, Modal, ModalContent, TextArea } from "@app/components/v2";
|
||||
import {
|
||||
Button,
|
||||
FilterableSelect,
|
||||
FormControl,
|
||||
Input,
|
||||
Modal,
|
||||
ModalContent,
|
||||
TextArea
|
||||
} from "@app/components/v2";
|
||||
import { useWorkspace } from "@app/context";
|
||||
import { useGetWorkspaceUsers } from "@app/hooks/api";
|
||||
|
||||
const ReminderFormSchema = z.object({
|
||||
note: z.string().optional().nullable(),
|
||||
recipients: z
|
||||
.array(
|
||||
z.object({
|
||||
label: z.string(),
|
||||
value: z.string().uuid()
|
||||
})
|
||||
)
|
||||
.optional(),
|
||||
days: z
|
||||
.number()
|
||||
.min(1, { message: "Must be at least 1 day" })
|
||||
@@ -22,6 +40,7 @@ interface ReminderFormProps {
|
||||
isOpen: boolean;
|
||||
repeatDays?: number | null;
|
||||
note?: string | null;
|
||||
recipients?: string[] | null;
|
||||
onOpenChange: (isOpen: boolean, data?: TReminderFormSchema) => void;
|
||||
}
|
||||
|
||||
@@ -29,12 +48,16 @@ export const CreateReminderForm = ({
|
||||
isOpen,
|
||||
onOpenChange,
|
||||
repeatDays,
|
||||
note
|
||||
note,
|
||||
recipients
|
||||
}: ReminderFormProps) => {
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
|
||||
const { data: members = [] } = useGetWorkspaceUsers(currentWorkspace?.id);
|
||||
|
||||
const {
|
||||
register,
|
||||
control,
|
||||
reset,
|
||||
setValue,
|
||||
handleSubmit,
|
||||
formState: { isSubmitting }
|
||||
@@ -51,13 +74,23 @@ export const CreateReminderForm = ({
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
reset({
|
||||
days: repeatDays || undefined,
|
||||
note: note || ""
|
||||
});
|
||||
// On initial load, filter the members to only include the recipients
|
||||
if (members.length) {
|
||||
const filteredMembers = members.filter((m) => recipients?.find((r) => r === m.user.id));
|
||||
setValue(
|
||||
"recipients",
|
||||
filteredMembers.map((m) => ({
|
||||
label: m.user.username || m.user.email,
|
||||
value: m.user.id
|
||||
}))
|
||||
);
|
||||
}
|
||||
}, [isOpen]);
|
||||
}, [members, isOpen, recipients]);
|
||||
|
||||
useEffect(() => {
|
||||
if (repeatDays) setValue("days", repeatDays);
|
||||
if (note) setValue("note", note);
|
||||
}, [repeatDays, note]);
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onOpenChange={onOpenChange}>
|
||||
@@ -74,6 +107,7 @@ export const CreateReminderForm = ({
|
||||
render={({ field, fieldState }) => (
|
||||
<>
|
||||
<FormControl
|
||||
isRequired
|
||||
className="mb-0"
|
||||
label="Reminder Interval (in days)"
|
||||
isError={Boolean(fieldState.error)}
|
||||
@@ -83,6 +117,7 @@ export const CreateReminderForm = ({
|
||||
onChange={(el) => setValue("days", parseInt(el.target.value, 10))}
|
||||
type="number"
|
||||
placeholder="31"
|
||||
defaultValue={repeatDays || undefined}
|
||||
value={field.value || undefined}
|
||||
/>
|
||||
</FormControl>
|
||||
@@ -105,11 +140,43 @@ export const CreateReminderForm = ({
|
||||
placeholder="Remember to rotate the AWS secret every month."
|
||||
className="border border-mineshaft-600 text-sm"
|
||||
rows={8}
|
||||
defaultValue={note || ""}
|
||||
reSize="none"
|
||||
cols={30}
|
||||
{...register("note")}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<Controller
|
||||
control={control}
|
||||
name="recipients"
|
||||
render={({ field }) => (
|
||||
<FormControl
|
||||
tooltipText={
|
||||
<div>
|
||||
Select users to receive reminders.
|
||||
<br />
|
||||
<br /> If none are selected, all project members will receive the reminder.
|
||||
</div>
|
||||
}
|
||||
label="Recipients"
|
||||
className="mb-0"
|
||||
>
|
||||
<FilterableSelect
|
||||
className="w-full"
|
||||
placeholder="Select reminder recipients..."
|
||||
isMulti
|
||||
name="recipients"
|
||||
options={members.map((member) => ({
|
||||
label: member.user.username || member.user.email,
|
||||
value: member.user.id
|
||||
}))}
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-7 flex items-center space-x-4">
|
||||
<Button
|
||||
|
||||
+21
-3
@@ -1,3 +1,4 @@
|
||||
import { useEffect } from "react";
|
||||
import { Controller, useFieldArray, useForm } from "react-hook-form";
|
||||
import { subject } from "@casl/ability";
|
||||
import { faCircleQuestion, faEye } from "@fortawesome/free-regular-svg-icons";
|
||||
@@ -220,11 +221,12 @@ export const SecretDetailSidebar = ({
|
||||
|
||||
const handleReminderSubmit = async (
|
||||
reminderRepeatDays: number | null | undefined,
|
||||
reminderNote: string | null | undefined
|
||||
reminderNote: string | null | undefined,
|
||||
reminderRecipients: string[] | undefined
|
||||
) => {
|
||||
await onSaveSecret(
|
||||
secret,
|
||||
{ ...secret, reminderRepeatDays, reminderNote, isReminderEvent: true },
|
||||
{ ...secret, reminderRepeatDays, reminderNote, isReminderEvent: true, reminderRecipients },
|
||||
() => {}
|
||||
);
|
||||
};
|
||||
@@ -233,6 +235,16 @@ export const SecretDetailSidebar = ({
|
||||
|
||||
const secretReminderRepeatDays = watch("reminderRepeatDays");
|
||||
const secretReminderNote = watch("reminderNote");
|
||||
const secretReminderRecipients = watch("reminderRecipients");
|
||||
useEffect(() => {
|
||||
setValue(
|
||||
"reminderRecipients",
|
||||
secret?.secretReminderRecipients?.map((el) => el.user.id),
|
||||
{
|
||||
shouldDirty: false
|
||||
}
|
||||
);
|
||||
}, [secret?.secretReminderRecipients]);
|
||||
|
||||
const getModifiedByIcon = (userType: string | undefined | null) => {
|
||||
switch (userType) {
|
||||
@@ -294,14 +306,20 @@ export const SecretDetailSidebar = ({
|
||||
<CreateReminderForm
|
||||
repeatDays={secretReminderRepeatDays}
|
||||
note={secretReminderNote}
|
||||
recipients={secretReminderRecipients}
|
||||
isOpen={createReminderFormOpen}
|
||||
onOpenChange={(_, data) => {
|
||||
setCreateReminderFormOpen.toggle();
|
||||
|
||||
if (data) {
|
||||
const recipients = data.recipients?.length
|
||||
? data.recipients.map((recipient) => recipient.value)
|
||||
: undefined;
|
||||
|
||||
setValue("reminderRepeatDays", data.days, { shouldDirty: false });
|
||||
setValue("reminderNote", data.note, { shouldDirty: false });
|
||||
handleReminderSubmit(data.days, data.note);
|
||||
setValue("reminderRecipients", recipients, { shouldDirty: false });
|
||||
handleReminderSubmit(data.days, data.note, recipients);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
+17
-1
@@ -49,6 +49,8 @@ export const SecretListView = ({
|
||||
isProtectedBranch = false,
|
||||
importedBy
|
||||
}: Props) => {
|
||||
console.log("secretssssss", secrets);
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
const { popUp, handlePopUpToggle, handlePopUpOpen, handlePopUpClose } = usePopUp([
|
||||
"deleteSecret",
|
||||
@@ -85,6 +87,7 @@ export const SecretListView = ({
|
||||
comment,
|
||||
reminderRepeatDays,
|
||||
reminderNote,
|
||||
reminderRecipients,
|
||||
tags,
|
||||
skipMultilineEncoding,
|
||||
newKey,
|
||||
@@ -96,6 +99,7 @@ export const SecretListView = ({
|
||||
comment: string;
|
||||
reminderRepeatDays: number | null;
|
||||
reminderNote: string | null;
|
||||
reminderRecipients?: string[] | null;
|
||||
tags: string[];
|
||||
skipMultilineEncoding: boolean;
|
||||
newKey: string;
|
||||
@@ -131,6 +135,7 @@ export const SecretListView = ({
|
||||
secretComment: comment,
|
||||
secretReminderRepeatDays: reminderRepeatDays,
|
||||
secretReminderNote: reminderNote,
|
||||
secretReminderRecipients: reminderRecipients,
|
||||
skipMultilineEncoding,
|
||||
secretMetadata
|
||||
});
|
||||
@@ -172,6 +177,7 @@ export const SecretListView = ({
|
||||
comment,
|
||||
reminderRepeatDays,
|
||||
reminderNote,
|
||||
reminderRecipients,
|
||||
secretMetadata,
|
||||
isReminderEvent
|
||||
} = modSecret;
|
||||
@@ -180,6 +186,12 @@ export const SecretListView = ({
|
||||
const tagIds = tags?.map(({ id }) => id);
|
||||
const oldTagIds = (orgSecret?.tags || []).map(({ id }) => id);
|
||||
const isSameTags = JSON.stringify(tagIds) === JSON.stringify(oldTagIds);
|
||||
|
||||
const isSameRecipients =
|
||||
!reminderRecipients?.some(
|
||||
(newId) => !orgSecret.secretReminderRecipients?.find((oldId) => newId === oldId.user.id)
|
||||
) && reminderRecipients?.length === orgSecret.secretReminderRecipients?.length;
|
||||
|
||||
const isSharedSecUnchanged =
|
||||
(
|
||||
[
|
||||
@@ -189,9 +201,12 @@ export const SecretListView = ({
|
||||
"skipMultilineEncoding",
|
||||
"reminderRepeatDays",
|
||||
"reminderNote",
|
||||
"reminderRecipients",
|
||||
"secretMetadata"
|
||||
] as const
|
||||
).every((el) => orgSecret[el] === modSecret[el]) && isSameTags;
|
||||
).every((el) => orgSecret[el] === modSecret[el]) &&
|
||||
isSameTags &&
|
||||
isSameRecipients;
|
||||
|
||||
try {
|
||||
// personal secret change
|
||||
@@ -224,6 +239,7 @@ export const SecretListView = ({
|
||||
comment,
|
||||
reminderRepeatDays,
|
||||
reminderNote,
|
||||
reminderRecipients,
|
||||
secretId: orgSecret.id,
|
||||
newKey: hasKeyChanged ? key : undefined,
|
||||
skipMultilineEncoding: modSecret.skipMultilineEncoding,
|
||||
|
||||
+1
@@ -47,6 +47,7 @@ export const formSchema = z.object({
|
||||
.nullable()
|
||||
.optional(),
|
||||
reminderNote: z.string().trim().nullable().optional(),
|
||||
reminderRecipients: z.array(z.string().uuid()).optional(),
|
||||
secretMetadata: z
|
||||
.object({
|
||||
key: z.string().trim().min(1),
|
||||
|
||||
Reference in New Issue
Block a user