diff --git a/frontend/app/src/entities/events/api/get-events-from-api.ts b/frontend/app/src/entities/events/api/get-events-from-api.ts index 407c092b6d..f59f9c7186 100644 --- a/frontend/app/src/entities/events/api/get-events-from-api.ts +++ b/frontend/app/src/entities/events/api/get-events-from-api.ts @@ -78,17 +78,33 @@ const EVENTS_QUERY = gql` } payload } - ... on BranchCreatedEvent { + ... on StandardEvent { payload } - ... on StandardEvent { + ... on BranchCreatedEvent { payload + created_branch } ... on BranchDeletedEvent { payload + deleted_branch } ... on BranchRebasedEvent { payload + rebased_branch + } + ... on BranchMergedEvent { + source_branch + } + ... on GroupEvent { + ancestors { + id + kind + } + members { + id + kind + } } ... on GroupEvent { ancestors { diff --git a/frontend/app/src/entities/events/ui/branch-event.tsx b/frontend/app/src/entities/events/ui/branch-event.tsx index a488631c01..21e71d1fd7 100644 --- a/frontend/app/src/entities/events/ui/branch-event.tsx +++ b/frontend/app/src/entities/events/ui/branch-event.tsx @@ -1,21 +1,35 @@ import { NodeLabel } from "@/entities/nodes/object/ui/node-label"; import { EventNodeInterface } from "@/shared/api/graphql/generated/graphql"; +import { Link } from "@/shared/components/ui/link"; import { ReactNode } from "react"; export const BRANCH_EVENTS_MAPPING: Record ReactNode> = { "infrahub.branch.created": (props) => (
- created the branch {props.branch}{" "} + created the branch{" "} + + {props.created_branch ?? "-"} +
), "infrahub.branch.rebased": (props) => (
- rebased the branch {props.branch}{" "} + rebased the branch{" "} + + {props.rebased_branch ?? "-"} + +
+ ), + "infrahub.branch.merged": (props) => ( +
+ merged the branch{" "} + {props.source_branch ?? "-"}
), "infrahub.branch.deleted": (props) => (
- deleted the branch {props.branch}{" "} + deleted the branch{" "} + {props.deleted_branch ?? "-"}
), }; diff --git a/frontend/app/src/entities/events/ui/event.tsx b/frontend/app/src/entities/events/ui/event.tsx index 47242936d6..5fc58e1ff4 100644 --- a/frontend/app/src/entities/events/ui/event.tsx +++ b/frontend/app/src/entities/events/ui/event.tsx @@ -42,6 +42,7 @@ export type EventType = BranchEventType | NodeEventType | GroupEvent; export const EventDetails = ({ id, event, + branch, occurred_at, account_id, primary_node, @@ -61,6 +62,7 @@ export const EventDetails = ({ } /> + } /> {account_id && ( { - {BRANCH_EVENTS_MAPPING[event] && BRANCH_EVENTS_MAPPING[event](props)} + {(BRANCH_EVENTS_MAPPING[event] && BRANCH_EVENTS_MAPPING[event](props)) ?? event} ); diff --git a/frontend/app/src/entities/events/ui/global-event.tsx b/frontend/app/src/entities/events/ui/global-event.tsx index ac2ef2e530..1f8356a167 100644 --- a/frontend/app/src/entities/events/ui/global-event.tsx +++ b/frontend/app/src/entities/events/ui/global-event.tsx @@ -3,13 +3,34 @@ import { Tooltip } from "@/shared/components/ui/tooltip"; import { classNames } from "@/shared/utils/common"; import { Icon } from "@iconify-icon/react"; import { format } from "date-fns"; -import { BRANCH_EVENTS, STANDARD_EVENTS } from "../utils/constants"; +import { BRANCH_EVENTS, GROUP_EVENTS, STANDARD_EVENTS } from "../utils/constants"; import { EventType } from "./event"; import { BranchEvent } from "./global-branch-event"; +import { GroupEvent } from "./global-group-event"; import { NodeEvent } from "./global-node-event"; import { StandardEvent } from "./global-standard-event"; -export const Event = ({ __typename, ...props }: EventType) => { +const GlobalEventDisplay = ({ __typename, ...props }: EventType) => { + if ("attributes" in props) { + return ; + } + + if (BRANCH_EVENTS.includes(__typename)) { + return ; + } + + if (STANDARD_EVENTS.includes(__typename)) { + return ; + } + + if (GROUP_EVENTS.includes(__typename)) { + return ; + } + + return {props.event}; +}; + +export const Event = (props: EventType) => { return (
{ )} >
- + {format(new Date(props.occurred_at), "MMM dd, HH:mm:ss")}
- {"attributes" in props && } - - {BRANCH_EVENTS.includes(__typename) && } - - {STANDARD_EVENTS.includes(__typename) && } +
- + {props.branch && ( + <> + - {props.branch} + {props.branch} + + )}
diff --git a/frontend/app/src/entities/events/ui/global-group-event.tsx b/frontend/app/src/entities/events/ui/global-group-event.tsx new file mode 100644 index 0000000000..b3a77f1ff3 --- /dev/null +++ b/frontend/app/src/entities/events/ui/global-group-event.tsx @@ -0,0 +1,24 @@ +import { NodeLabel } from "@/entities/nodes/object/ui/node-label"; +import { EventNodeInterface } from "@/shared/api/graphql/generated/graphql"; +import { Icon } from "@iconify-icon/react"; +import { GROUP_EVENTS_MAPPING } from "./group-event"; + +export const GroupEvent = (props: EventNodeInterface) => { + const { event, account_id } = props; + + return ( + <> +
+
+ + +
+ +
+ + {(GROUP_EVENTS_MAPPING[event] && GROUP_EVENTS_MAPPING[event](props)) ?? event} +
+
+ + ); +}; diff --git a/frontend/app/src/entities/events/ui/global-node-event.tsx b/frontend/app/src/entities/events/ui/global-node-event.tsx index 33d87355f8..2126cbc272 100644 --- a/frontend/app/src/entities/events/ui/global-node-event.tsx +++ b/frontend/app/src/entities/events/ui/global-node-event.tsx @@ -1,13 +1,11 @@ -import { QSP } from "@/config/qsp"; import { NodeLabel } from "@/entities/nodes/object/ui/node-label"; import { schemaKindLabelState } from "@/entities/schema/stores/schemaKindLabel.atom"; import { useSchema } from "@/entities/schema/ui/hooks/useSchema"; import { NodeMutatedEvent } from "@/shared/api/graphql/generated/graphql"; -import { constructPath } from "@/shared/api/rest/fetch"; import { Link } from "@/shared/components/ui/link"; import { Icon } from "@iconify-icon/react"; import { useAtomValue } from "jotai"; -import { NODE_EVENTS_MAPPING } from "./node-event"; +import { NODE_EVENTS_MAPPING, getLink } from "./node-event"; export const EventAttributes = ({ attributes }: Pick) => { return ( @@ -45,30 +43,36 @@ export const NodeEvent = (props: NodeMutatedEvent) => { className="overflow-hidden text-ellipsis whitespace-nowrap font-semibold" /> -
{NODE_EVENTS_MAPPING[event] ?? "-"}
+
{NODE_EVENTS_MAPPING[event] ?? event}
{schemaLabels[props.payload.data.node_kind] ?? "-"}
- + {event.includes("deleted") ? ( - + ) : ( + + + + )}
); }; diff --git a/frontend/app/src/entities/events/ui/global-standard-event.tsx b/frontend/app/src/entities/events/ui/global-standard-event.tsx index 0861fcf02d..95fe5f6b78 100644 --- a/frontend/app/src/entities/events/ui/global-standard-event.tsx +++ b/frontend/app/src/entities/events/ui/global-standard-event.tsx @@ -4,10 +4,6 @@ import { Icon } from "@iconify-icon/react"; import { STANDARD_EVENTS_MAPPING } from "./standard-event"; const getStandardEventIcon = (event: string) => { - if (event.includes("group")) { - return ; - } - if (event.includes("schema")) { return ; } @@ -24,13 +20,15 @@ export const StandardEvent = (props: EventNodeInterface) => {
{getStandardEventIcon(event)} -
- -
- - {STANDARD_EVENTS_MAPPING[event] && STANDARD_EVENTS_MAPPING[event](props)} + {account_id ? ( +
+ +
+ ) : ( + "-" + )} - {!STANDARD_EVENTS_MAPPING[event] && event} + {(STANDARD_EVENTS_MAPPING[event] && STANDARD_EVENTS_MAPPING[event](props)) ?? event}
diff --git a/frontend/app/src/entities/events/ui/group-event.tsx b/frontend/app/src/entities/events/ui/group-event.tsx new file mode 100644 index 0000000000..209ba8294b --- /dev/null +++ b/frontend/app/src/entities/events/ui/group-event.tsx @@ -0,0 +1,80 @@ +import { NodeLabel } from "@/entities/nodes/object/ui/node-label"; +import { EventNodeInterface } from "@/shared/api/graphql/generated/graphql"; +import { constructPath } from "@/shared/api/rest/fetch"; +import { Link } from "@/shared/components/ui/link"; +import { ReactElement } from "react"; + +export const GROUP_EVENTS_MAPPING: Record ReactElement> = { + "infrahub.group.member_added": ({ related_nodes, primary_node }) => { + return ( +
+ added{" "} +
+ {related_nodes.slice(0, 5).map(({ id, kind }) => { + return ( + + + + ); + })} + {related_nodes.slice(6).length > 0 && ( + (+{related_nodes.slice(6).length}) + )} +
{" "} + in group{" "} + + + + + +
+ ); + }, + "infrahub.group.member_removed": ({ related_nodes, primary_node }) => { + return ( +
+ removed{" "} +
+ {related_nodes.slice(0, 5).map(({ id, kind }) => { + return ( + + + + ); + })} + {related_nodes.slice(6).length > 0 && ( + (+{related_nodes.slice(6).length}) + )} +
{" "} + from group{" "} + + + + + +
+ ); + }, +}; + +export const GroupEvent = (props: EventNodeInterface) => { + const { event, account_id } = props; + + return ( + <> +
+
+
+ +
+ +
+ {GROUP_EVENTS_MAPPING[event] && GROUP_EVENTS_MAPPING[event](props)} + + {!GROUP_EVENTS_MAPPING[event] && event} +
+
+
+ + ); +}; diff --git a/frontend/app/src/entities/events/ui/node-event.tsx b/frontend/app/src/entities/events/ui/node-event.tsx index 74107ab230..4ad4011f52 100644 --- a/frontend/app/src/entities/events/ui/node-event.tsx +++ b/frontend/app/src/entities/events/ui/node-event.tsx @@ -1,3 +1,5 @@ +import { ACCOUNT_OBJECT, PROPOSED_CHANGES_OBJECT } from "@/config/constants"; +import { QSP } from "@/config/qsp"; import { NodeLabel } from "@/entities/nodes/object/ui/node-label"; import { schemaKindLabelState } from "@/entities/schema/stores/schemaKindLabel.atom"; import { NodeMutatedEvent } from "@/shared/api/graphql/generated/graphql"; @@ -34,30 +36,65 @@ export const EventAttributes = ({ attributes }: Pick { + if (kind === PROPOSED_CHANGES_OBJECT) { + return constructPath(`/proposed-changes/${id}`); + } + + if (kind === ACCOUNT_OBJECT) { + return constructPath("/role-management", [ + { + name: QSP.BRANCH, + value: branch, + }, + ]); + } + + return constructPath(`/objects/${kind}/${id}`, [ + { + name: QSP.BRANCH, + value: branch, + }, + ]); +}; + export const NodeEvent = (props: NodeMutatedEvent) => { const { event, account_id } = props; const schemaLabels = useAtomValue(schemaKindLabelState); return ( <> -
-
-
- -
+
+
+ +
-
{NODE_EVENTS_MAPPING[event] ?? "-"}
+
{NODE_EVENTS_MAPPING[event] ?? "-"}
-
{schemaLabels[props.payload.data.node_kind] ?? "-"}
+
{schemaLabels[props.payload.data.node_kind] ?? "-"}
+ {event.includes("deleted") ? ( + + ) : ( - + -
+ )}
); diff --git a/frontend/app/src/entities/events/ui/node-events.tsx b/frontend/app/src/entities/events/ui/node-events.tsx index 1182aed9ea..543bb20729 100644 --- a/frontend/app/src/entities/events/ui/node-events.tsx +++ b/frontend/app/src/entities/events/ui/node-events.tsx @@ -62,7 +62,7 @@ export const NodeEvents = ({ parentId }: { parentId?: string }) => { ))} - {!parentId && flatData?.count > MAX_EVENTS && ( + {!parentId && (
{ ])} className="p-1 text-sm text-gray-400 text-center" > - More events... + More activities...
)} diff --git a/frontend/app/src/entities/events/ui/standard-event.tsx b/frontend/app/src/entities/events/ui/standard-event.tsx index 4421c3a91e..ea25f76913 100644 --- a/frontend/app/src/entities/events/ui/standard-event.tsx +++ b/frontend/app/src/entities/events/ui/standard-event.tsx @@ -1,3 +1,4 @@ +import { QSP } from "@/config/qsp"; import { NodeLabel } from "@/entities/nodes/object/ui/node-label"; import { EventNodeInterface } from "@/shared/api/graphql/generated/graphql"; import { constructPath } from "@/shared/api/rest/fetch"; @@ -6,62 +7,6 @@ import { ReactElement } from "react"; export const STANDARD_EVENTS_MAPPING: Record ReactElement> = { - "infrahub.group.member_added": ({ related_nodes, primary_node }) => { - return ( -
- added{" "} -
- {related_nodes.slice(0, 5).map(({ id, kind }) => { - return ( - - - - ); - })} - {related_nodes.slice(6).length > 0 && ( - (+{related_nodes.slice(6).length}) - )} -
{" "} - in group{" "} - - - - - -
- ); - }, - "infrahub.group.member_removed": ({ related_nodes, primary_node }) => { - return ( -
- removed{" "} -
- {related_nodes.slice(0, 5).map(({ id, kind }) => { - return ( - - - - ); - })} - {related_nodes.slice(6).length > 0 && ( - (+{related_nodes.slice(6).length}) - )} -
{" "} - from group{" "} - - - - - -
- ); - }, "infrahub.schema.update": () => { return
updated the schema
; }, @@ -85,6 +30,23 @@ export const STANDARD_EVENTS_MAPPING: Record ); }, + "infrahub.repository.update_commit": (props) => { + return ( +
+ updated the commit + {props.payload?.commit} + from repository + + {props.payload?.repository_name} + +
+ ); + }, }; export const StandardEvent = (props: EventNodeInterface) => { diff --git a/frontend/app/src/entities/events/utils/constants.ts b/frontend/app/src/entities/events/utils/constants.ts index 38bb858a74..7ce07837d5 100644 --- a/frontend/app/src/entities/events/utils/constants.ts +++ b/frontend/app/src/entities/events/utils/constants.ts @@ -2,17 +2,23 @@ export const NODE_MUTATED_EVENT = "NodeMutatedEvent"; export const BRANCH_DELETED_EVENT = "BranchDeletedEvent"; export const BRANCH_CREATED_EVENT = "BranchCreatedEvent"; export const BRANCH_REBASEDED_EVENT = "BranchRebasedEvent"; -export const GROUP_DELETED_EVENT = "GroupDeletedEvent"; -export const GROUP_CREATED_EVENT = "GroupCreatedEvent"; -export const GROUP_REBASEDED_EVENT = "GroupRebasedEvent"; +export const BRANCH_MERGED_EVENT = "BranchMergedEvent"; +export const GROUP_EVENT = "GroupEvent"; export const STANDARD_EVENT = "StandardEvent"; export const INFRAHUB_EVENT = "InfrahubEvent"; -export const BRANCH_EVENTS = [BRANCH_DELETED_EVENT, BRANCH_CREATED_EVENT, BRANCH_REBASEDED_EVENT]; +export const BRANCH_EVENTS = [ + BRANCH_DELETED_EVENT, + BRANCH_CREATED_EVENT, + BRANCH_REBASEDED_EVENT, + BRANCH_MERGED_EVENT, +]; export const STANDARD_EVENTS = [STANDARD_EVENT]; +export const GROUP_EVENTS = [GROUP_EVENT]; + export const EVENT_TYPE_CHOICES = [ { label: "Node created", diff --git a/frontend/app/src/entities/nodes/object/api/get-display-label.query.ts b/frontend/app/src/entities/nodes/object/api/get-display-label.query.ts index 4aa6281026..be13c507e6 100644 --- a/frontend/app/src/entities/nodes/object/api/get-display-label.query.ts +++ b/frontend/app/src/entities/nodes/object/api/get-display-label.query.ts @@ -5,7 +5,7 @@ import { queryOptions, useQuery } from "@tanstack/react-query"; import { useAtomValue } from "jotai"; import { getNodeLabelFromApi } from "./get-display-label"; -type NodeLabelProps = { objectid?: string; kind: string; enabled?: boolean }; +type NodeLabelProps = { objectid?: string; kind: string; enabled?: boolean; branch?: string }; export function getNodeLabelQueryOptions({ objectid, @@ -28,7 +28,7 @@ export function getNodeLabelQueryOptions({ }); } -export const useNodeLabel = ({ objectid, kind, enabled }: NodeLabelProps) => { +export const useNodeLabel = ({ objectid, kind, enabled, branch }: NodeLabelProps) => { const { currentBranch } = useCurrentBranch(); const timeMachineDate = useAtomValue(datetimeAtom); @@ -37,7 +37,7 @@ export const useNodeLabel = ({ objectid, kind, enabled }: NodeLabelProps) => { objectid, kind, enabled, - branchName: currentBranch.name, + branchName: branch ?? currentBranch.name, atDate: timeMachineDate, }) ); diff --git a/frontend/app/src/entities/nodes/object/ui/node-label.tsx b/frontend/app/src/entities/nodes/object/ui/node-label.tsx index bddbe94b40..ab3b19dc4f 100644 --- a/frontend/app/src/entities/nodes/object/ui/node-label.tsx +++ b/frontend/app/src/entities/nodes/object/ui/node-label.tsx @@ -6,18 +6,19 @@ import { useNodeLabel } from "../api/get-display-label.query"; type NodeLabelProps = { id?: string; kind?: string; + branch?: string; className?: string; }; -export const NodeLabel = ({ id, kind = NODE_OBJECT, className }: NodeLabelProps) => { - const { isLoading, error, data } = useNodeLabel({ objectid: id, kind, enabled: !!id }); +export const NodeLabel = ({ id, kind = NODE_OBJECT, branch, className }: NodeLabelProps) => { + const { isLoading, error, data } = useNodeLabel({ objectid: id, kind, enabled: !!id, branch }); if (isLoading) { return ; } if (!id) { - return
Name id provided
; + return
No id provided
; } if (error || !data?.display_label) { diff --git a/frontend/app/src/entities/schema/ui/decorators/withSchemaContext.tsx b/frontend/app/src/entities/schema/ui/decorators/withSchemaContext.tsx index 6c6e8e5058..ded4dd9c56 100644 --- a/frontend/app/src/entities/schema/ui/decorators/withSchemaContext.tsx +++ b/frontend/app/src/entities/schema/ui/decorators/withSchemaContext.tsx @@ -100,7 +100,12 @@ export const withSchemaContext = (AppComponent: any) => (props: any) => { SchemaNode: "Node", }; - const schemaLabels = [...schema.map((s) => s.label), ...generics.map((s) => s.label)]; + const schemaLabels = [ + ...schema.map((s) => s.label), + ...generics.map((s) => s.label), + ...profiles.map((s) => s.label), + ...templates.map((s) => s.label), + ]; const schemaKindLabelTuples = R.zip(schemaKinds, schemaLabels); const schemaKindLabelMap = R.fromPairs(schemaKindLabelTuples); diff --git a/frontend/app/tests/e2e/activity-log/global-activity-log.spec.ts b/frontend/app/tests/e2e/activity-log/global-activity-log.spec.ts index f7675a468c..6d62318988 100644 --- a/frontend/app/tests/e2e/activity-log/global-activity-log.spec.ts +++ b/frontend/app/tests/e2e/activity-log/global-activity-log.spec.ts @@ -33,7 +33,7 @@ test.describe("Global Activity Log - List view and filter usage", () => { await test.step("Choose filters", async () => { await page.getByRole("button", { name: "Primary Node" }).click(); await page.getByPlaceholder("Filter...").fill("tag"); - await page.getByRole("option", { name: "Tag" }).click(); + await page.getByRole("option", { name: "Tag", exact: true }).click(); await page.getByRole("option", { name: "blue" }).click(); await page.getByRole("button", { name: "Apply" }).click();