Skip to content

Commit

Permalink
feat: playbook as notification recipient (#2422)
Browse files Browse the repository at this point in the history
* feat: new notification statuses

* feat: show recipient in notification send history table

* feat: support notifications in permission form

* fix: link playbooks & notification dispatch
  • Loading branch information
adityathebe authored Dec 24, 2024
1 parent f5fe218 commit da42e5d
Show file tree
Hide file tree
Showing 14 changed files with 180 additions and 22 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,6 @@ middleware.ts
/blob-report/
/playwright/.cache/
.bin/

.envrc
default.nix
16 changes: 16 additions & 0 deletions src/api/query-hooks/useNotificationsQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,22 @@ export function useNotificationsSummaryQuery(
);
}

export function useGetAllNotifications() {
return useQuery<NotificationRules[], Error>(
["notifications", "all"],
async () => {
const filter: NotificationQueryFilterOptions = {
pageIndex: 0,
pageSize: 1000,
sortBy: "name",
sortOrder: "asc"
};
const response = await getNotificationsSummary(filter);
return response.data ?? [];
}
);
}

export function useGetNotificationsByIDQuery(
id: string,
options?: UseQueryOptions<NotificationRules | undefined, Error>
Expand Down
1 change: 1 addition & 0 deletions src/api/services/permissions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export function fetchPermissions(
"team:team_id(id, name, icon)",
`person:person_id(${AVATAR_INFO})`,
`createdBy:created_by(${AVATAR_INFO})`,
`notification:notification_id(id,name,namespace)`,
`connection:connection_id(id,name,type)`
];

Expand Down
2 changes: 2 additions & 0 deletions src/api/types/notifications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Team, User } from "./users";

export type NotificationRules = {
id: string;
namespace?: string;
name: string;
title?: string;
events: string[];
Expand Down Expand Up @@ -68,6 +69,7 @@ export type NotificationSendHistory = {
first_observed: string;
source_event: string;
resource_id: string;
playbook_run_id?: string;
person_id?: string | undefined;
error?: string | undefined;
duration_millis?: number | undefined;
Expand Down
3 changes: 3 additions & 0 deletions src/api/types/permissions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ConfigItem } from "./configs";
import { PlaybookSpec } from "./playbooks";
import { Topology } from "./topology";
import { Team, User } from "./users";
import { NotificationRules } from "./notifications";

export type PermissionTable = {
id: string;
Expand All @@ -17,6 +18,7 @@ export type PermissionTable = {
created_by: string;
connection_id?: string;
person_id?: string;
notification_id?: string;
team_id?: string;
updated_by: string;
created_at: string;
Expand All @@ -36,6 +38,7 @@ export type PermissionAPIResponse = PermissionTable & {
playbook: Pick<PlaybookSpec, "id" | "name" | "icon" | "title">;
team: Pick<Team, "id" | "name" | "icon">;
connection: Pick<Connection, "id" | "name" | "type">;
notification: Pick<NotificationRules, "id" | "name" | "namespace">;
person: User;
createdBy: User;
};
1 change: 1 addition & 0 deletions src/api/types/playbooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export interface PlaybookRun extends CreatedAt, Avatar, Agent {
check_id?: string;
config_id?: string;
component_id?: string;
notification_send_id?: string;
parameters?: Record<string, unknown>;
/* relationships */
playbooks?: PlaybookSpec;
Expand Down
42 changes: 42 additions & 0 deletions src/components/Forms/Formik/FormikNotificationDropdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { useMemo } from "react";
import FormikSelectDropdown from "./FormikSelectDropdown";
import { useGetAllNotifications } from "@flanksource-ui/api/query-hooks/useNotificationsQuery";

type FormikEventsDropdownProps = {
name: string;
label?: string;
required?: boolean;
hint?: string;
className?: string;
};

export default function FormikNotificationDropdown({
name,
label,
required = false,
hint,
className = "flex flex-col space-y-2 py-2"
}: FormikEventsDropdownProps) {
const { data: notificationRules, isLoading } = useGetAllNotifications();

const options = useMemo(
() =>
notificationRules?.map((rule) => ({
label: rule.name,
value: rule.id
})),
[notificationRules]
);

return (
<FormikSelectDropdown
name={name}
className={className}
options={options}
label={label}
isLoading={isLoading}
required={required}
hint={hint}
/>
);
}
21 changes: 19 additions & 2 deletions src/components/Notifications/NotificationSendHistory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const notificationSendHistoryColumns: MRT_ColumnDef<NotificationSendHistoryApiRe
{
header: "Age",
accessorKey: "created_at",
size: 100,
size: 40,
Cell: ({ row }) => {
const dateString = row.original.created_at;
const count = row.original.count;
Expand All @@ -32,7 +32,7 @@ const notificationSendHistoryColumns: MRT_ColumnDef<NotificationSendHistoryApiRe
},
{
header: "Resource",
size: 300,
size: 250,
Cell: ({ row }) => {
return (
<div
Expand All @@ -59,6 +59,23 @@ const notificationSendHistoryColumns: MRT_ColumnDef<NotificationSendHistoryApiRe
const sourceEvent = row.original.source_event;
return <span>{sourceEvent}</span>;
}
},
{
header: "Recipient",
size: 200,
Cell: ({ row }) => {
const { playbook_run_id, person_id } = row.original;

const recipient = playbook_run_id
? `playbook/${playbook_run_id}`
: person_id
? `person/${person_id}`
: "";

// TODO: use a proper component.
// show connection recipient but the backend doesn't currently store that.
return <span>{recipient}</span>;
}
}
// {
// header: "Body",
Expand Down
36 changes: 30 additions & 6 deletions src/components/Notifications/NotificationsStatusCell.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { NotificationSendHistoryApiResponse } from "@flanksource-ui/api/types/notifications";
import { MRTCellProps } from "@flanksource-ui/ui/MRTDataTable/MRTCellProps";
import { FaBan, FaBellSlash, FaDotCircle } from "react-icons/fa";
import { FaBellSlash, FaDotCircle } from "react-icons/fa";

export const notificationSendHistoryStatus = {
sent: {
Expand All @@ -15,17 +15,35 @@ export const notificationSendHistoryStatus = {
value: "error",
id: "error"
},
sending: {
label: "Sending",
Icon: <FaDotCircle className="fill-gray-600" />,
value: "sending",
id: "sending"
},
pending: {
label: "Pending",
Icon: <FaDotCircle className="fill-gray-600" />,
value: "pending",
id: "pending"
},
cancelled: {
label: "Cancelled",
Icon: <FaBan className="fill-red-600" />,
value: "cancelled",
id: "cancelled"
pending_playbook_run: {
label: "Pending Playbook Run",
Icon: <FaDotCircle className="fill-gray-600" />,
value: "pending_playbook_run",
id: "pending_playbook_run"
},
pending_playbook_completion: {
label: "Playbook In Progress",
Icon: <FaDotCircle className="fill-gray-600" />,
value: "pending_playbook_completion",
id: "pending_playbook_completion"
},
"evaluating-waitfor": {
label: "Evaluating WaitFor",
Icon: <FaDotCircle className="fill-gray-600" />,
value: "evaluating-waitfor",
id: "evaluating-waitfor"
},
"repeat-interval": {
label: "Repeated",
Expand All @@ -38,6 +56,12 @@ export const notificationSendHistoryStatus = {
Icon: <FaBellSlash className="fill-gray-600" />,
value: "silenced",
id: "silenced"
},
skipped: {
label: "Skipped",
Icon: <FaBellSlash className="fill-gray-600" />,
value: "skipped",
id: "skipped"
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ export default function PermissionForm({
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,
until: data?.until,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import FormikPeopleDropdown from "@flanksource-ui/components/Forms/Formik/FormikPeopleDropdown";
import FormikTeamsDropdown from "@flanksource-ui/components/Forms/Formik/FormikTeamsDropdown";
import FormikNotificationDropdown from "@flanksource-ui/components/Forms/Formik/FormikNotificationDropdown";
import { Switch } from "@flanksource-ui/ui/FormControls/Switch";
import { useFormikContext } from "formik";
import { useEffect, useState } from "react";
Expand All @@ -9,10 +10,14 @@ export default function PermissionsSubjectControls() {

const teamId = values.team_id;
const personId = values.person_id;
const notificationId = values.notification_id;

const [switchOption, setSwitchOption] = useState<"Team" | "Person">(() => {
const [switchOption, setSwitchOption] = useState<
"Team" | "Person" | "Notification"
>(() => {
if (teamId) return "Team";
if (personId) return "Person";
if (notificationId) return "Notification";
return "Team";
});

Expand All @@ -21,16 +26,18 @@ export default function PermissionsSubjectControls() {
setSwitchOption("Team");
} else if (personId) {
setSwitchOption("Person");
} else if (notificationId) {
setSwitchOption("Notification");
}
}, [teamId, personId]);
}, [teamId, personId, notificationId]);

return (
<div className="flex flex-col gap-2">
<label className={`form-label`}>Subject</label>
<label className="form-label">Subject</label>
<div>
<div className="flex w-full flex-row">
<Switch
options={["Team", "Person"]}
options={["Team", "Person", "Notification"]}
className="w-auto"
itemsClassName=""
defaultValue="Go Template"
Expand All @@ -39,18 +46,27 @@ export default function PermissionsSubjectControls() {
setSwitchOption(v);
if (v === "Team") {
setFieldValue("person_id", undefined);
setFieldValue("notification_id", undefined);
} else if (v === "Person") {
setFieldValue("team_id", undefined);
setFieldValue("notification_id", undefined);
} else {
setFieldValue("team_id", undefined);
setFieldValue("person_id", undefined);
}
}}
/>
</div>

{switchOption === "Team" ? (
{switchOption === "Team" && (
<FormikTeamsDropdown required name="team_id" />
) : (
)}
{switchOption === "Person" && (
<FormikPeopleDropdown required name="person_id" />
)}
{switchOption === "Notification" && (
<FormikNotificationDropdown required name="notification_id" />
)}
</div>
</div>
);
Expand Down
19 changes: 19 additions & 0 deletions src/components/Permissions/PermissionsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { TopologyLink } from "../Topology/TopologyLink";
import { permissionObjectList } from "./ManagePermissions/Forms/FormikPermissionSelectResourceFields";
import { permissionsActionsList } from "./PermissionsView";
import { BsBan } from "react-icons/bs";
import { Link } from "react-router-dom";

const permissionsTableColumns: MRT_ColumnDef<PermissionAPIResponse>[] = [
{
Expand Down Expand Up @@ -55,6 +56,7 @@ const permissionsTableColumns: MRT_ColumnDef<PermissionAPIResponse>[] = [
Cell: ({ row }) => {
const team = row.original.team;
const person = row.original.person;
const notification = row.original.notification;

if (person) {
return (
Expand All @@ -74,6 +76,23 @@ const permissionsTableColumns: MRT_ColumnDef<PermissionAPIResponse>[] = [
);
}

if (notification) {
return (
<div className="flex flex-row items-center gap-2">
<span>
<Link
className="link"
to="/notifications/rules?id=f488a83b-ee27-40c9-932e-3da77f02a5b9"
>
{"notification: " +
(notification.namespace ? notification.namespace + "/" : "") +
notification.name}
</Link>
</span>
</div>
);
}

return null;
}
},
Expand Down
16 changes: 8 additions & 8 deletions src/components/Permissions/PermissionsView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ import PermissionForm from "./ManagePermissions/Forms/PermissionForm";
import PermissionsTable from "./PermissionsTable";

export const permissionsActionsList: FormikSelectDropdownOption[] = [
{ value: "ActionRead", label: "read" },
{ value: "ActionUpdate", label: "update" },
{ value: "ActionCreate", label: "create" },
{ value: "ActionDelete", label: "delete" },
{ value: "ActionAll", label: "*" },
{ value: "ActionCRUD", label: "create,read,update,delete" },
{ value: "ActionRun", label: "playbook:run" },
{ value: "ActionApprove", label: "playbook:approve" }
{ value: "read", label: "read" },
{ value: "update", label: "update" },
{ value: "create", label: "create" },
{ value: "delete", label: "delete" },
{ value: "*", label: "*" },
{ value: "create,read,update,delete", label: "create,read,update,delete" },
{ value: "playbook:run", label: "playbook:run" },
{ value: "playbook:approve", label: "playbook:approve" }
];

type PermissionsViewProps = {
Expand Down
13 changes: 13 additions & 0 deletions src/components/Playbooks/Runs/Actions/PlaybookRunsActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,19 @@ export default function PlaybookRunsActions({
}
/>
)}
{data.notification_send_id && (
<VerticalDescription
label="Triggered By"
value={
<Link
className="link"
to={`/notifications?id=${data.notification_send_id}`}
>
{"Notification"}
</Link>
}
/>
)}
</div>
<div className="ml-auto flex h-auto flex-col justify-center gap-2">
<div className="flex flex-row gap-2">
Expand Down

0 comments on commit da42e5d

Please sign in to comment.