diff --git a/frontend/app/src/config/constants.tsx b/frontend/app/src/config/constants.tsx index dfd8e7db9d..9f017c3449 100644 --- a/frontend/app/src/config/constants.tsx +++ b/frontend/app/src/config/constants.tsx @@ -10,8 +10,6 @@ export const PROFILE_KIND = "CoreProfile"; export const TASK_TARGET = "CoreTaskTarget"; -export const DATA_CHECK_OBJECT = "CoreDataCheck"; - export const ACCOUNT_GENERIC_OBJECT = "CoreGenericAccount"; export const ACCOUNT_OBJECT = "CoreAccount"; diff --git a/frontend/app/src/entities/branches/ui/branches-provider.tsx b/frontend/app/src/entities/branches/ui/branches-provider.tsx index 6c90bd3a09..81c96df1b1 100644 --- a/frontend/app/src/entities/branches/ui/branches-provider.tsx +++ b/frontend/app/src/entities/branches/ui/branches-provider.tsx @@ -3,18 +3,35 @@ import { QSP } from "@/config/qsp"; import { useGetBranches } from "@/entities/branches/domain/get-branches.query"; import { currentBranchAtom } from "@/entities/branches/stores"; import { findSelectedBranch } from "@/entities/branches/utils"; +import { Branch } from "@/shared/api/graphql/generated/graphql"; import ErrorScreen from "@/shared/components/errors/error-screen"; import { InfrahubLoading } from "@/shared/components/loading/infrahub-loading"; import { ALERT_TYPES, Alert } from "@/shared/components/ui/alert"; -import { useSetAtom } from "jotai"; +import { useAtom } from "jotai"; import React, { useEffect } from "react"; import { useNavigate } from "react-router"; import { toast } from "react-toastify"; import { StringParam, useQueryParam } from "use-query-params"; +type BranchContext = { + currentBranch: Branch; + setCurrentBranch: (branch: Branch) => void; +}; + +export const BranchContext = React.createContext(null); + +export function useCurrentBranch() { + const context = React.use(BranchContext); + if (!context) { + throw new Error("useCurrentBranch must be used within a BranchesProvider."); + } + + return context; +} + export const BranchesProvider = ({ children }: { children?: React.ReactNode }) => { const { data: branches, isPending, error } = useGetBranches(); - const setCurrentBranch = useSetAtom(currentBranchAtom); + const [currentBranch, setCurrentBranch] = useAtom(currentBranchAtom); const [branchInQueryString] = useQueryParam(QSP.BRANCH, StringParam); const navigate = useNavigate(); @@ -22,27 +39,28 @@ export const BranchesProvider = ({ children }: { children?: React.ReactNode }) = if (isPending || error) return; const selectedBranch = findSelectedBranch(branches, branchInQueryString); - if (branchInQueryString && !selectedBranch) { - toast( - - Branch {branchInQueryString} not found, you have been redirected to the main - branch. - - } - /> - ); - const mainBranch = findSelectedBranch(branches, DEFAULT_BRANCH_NAME); - setCurrentBranch(mainBranch); - navigate("/"); + if (selectedBranch) { + setCurrentBranch(selectedBranch); + return; } - setCurrentBranch(selectedBranch); + toast( + + Branch {branchInQueryString} not found, you have been redirected to the main + branch. + + } + /> + ); + const mainBranch = findSelectedBranch(branches, DEFAULT_BRANCH_NAME); + setCurrentBranch(mainBranch); + navigate("/"); }, [branches, branchInQueryString]); - if (isPending) { + if (isPending || !currentBranch) { return loading branches...; } @@ -50,5 +68,5 @@ export const BranchesProvider = ({ children }: { children?: React.ReactNode }) = return ; } - return children; + return {children}; }; diff --git a/frontend/app/src/entities/branches/ui/hooks/use-current-branch.ts b/frontend/app/src/entities/branches/ui/hooks/use-current-branch.ts deleted file mode 100644 index 447442b1c7..0000000000 --- a/frontend/app/src/entities/branches/ui/hooks/use-current-branch.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { currentBranchAtom } from "@/entities/branches/stores"; -import { useAtomValue } from "jotai"; - -export const useCurrentBranch = () => { - return useAtomValue(currentBranchAtom); -}; diff --git a/frontend/app/src/entities/diff/api/get-diff-tree-from-api.ts b/frontend/app/src/entities/diff/api/get-diff-tree-from-api.ts index 9422615592..e6e7f3c848 100644 --- a/frontend/app/src/entities/diff/api/get-diff-tree-from-api.ts +++ b/frontend/app/src/entities/diff/api/get-diff-tree-from-api.ts @@ -1,5 +1,6 @@ import { DiffTreeQueryFilters } from "@/shared/api/graphql/generated/graphql"; import graphqlClient from "@/shared/api/graphql/graphqlClientApollo"; +import { PaginationParams } from "@/shared/api/types"; import { gql } from "@apollo/client"; export const DIFF_TREE_QUERY = gql` @@ -127,11 +128,9 @@ export const DIFF_TREE_QUERY = gql` } `; -export type GetDiffTreeFromApiParams = { +export type GetDiffTreeFromApiParams = PaginationParams & { branchName: string; filters?: DiffTreeQueryFilters; - limit?: number; - offset?: number; }; export const getDiffTreeFromApi = async ({ diff --git a/frontend/app/src/entities/diff/domain/get-diff-tree.ts b/frontend/app/src/entities/diff/domain/get-diff-tree.ts index 3ba253b856..2810e3a55e 100644 --- a/frontend/app/src/entities/diff/domain/get-diff-tree.ts +++ b/frontend/app/src/entities/diff/domain/get-diff-tree.ts @@ -1,14 +1,13 @@ import { getDiffTreeFromApi } from "@/entities/diff/api/get-diff-tree-from-api"; import { DiffTree, DiffTreeQueryFilters } from "@/shared/api/graphql/generated/graphql"; +import { PaginationParams } from "@/shared/api/types"; import { infiniteQueryOptions, useInfiniteQuery } from "@tanstack/react-query"; export const DIFF_TREE_PER_PAGE = 300; -export type GetDiffTreeParams = { +export type GetDiffTreeParams = PaginationParams & { branchName: string; filters?: DiffTreeQueryFilters; - limit?: number; - offset: number; }; export type GetDiffTree = (params: GetDiffTreeParams) => Promise; diff --git a/frontend/app/src/entities/diff/node-diff/conflict.tsx b/frontend/app/src/entities/diff/node-diff/conflict.tsx index 4954b777c9..b08592af20 100644 --- a/frontend/app/src/entities/diff/node-diff/conflict.tsx +++ b/frontend/app/src/entities/diff/node-diff/conflict.tsx @@ -1,5 +1,5 @@ import { useAuth } from "@/entities/authentication/ui/useAuth"; -import { currentBranchAtom } from "@/entities/branches/stores"; +import { useCurrentBranch } from "@/entities/branches/ui/branches-provider"; import { resolveConflict } from "@/entities/diff/api/resolveConflict"; import { proposedChangedState } from "@/entities/proposed-changes/stores/proposedChanges.atom"; import graphqlClient from "@/shared/api/graphql/graphqlClientApollo"; @@ -16,7 +16,7 @@ import { useState } from "react"; import { toast } from "react-toastify"; export const Conflict = ({ conflict }: any) => { - const currentBranch = useAtomValue(currentBranchAtom); + const { currentBranch } = useCurrentBranch(); const date = useAtomValue(datetimeAtom); const [isLoading, setIsLoading] = useState(false); const proposedChangesDetails = useAtomValue(proposedChangedState); diff --git a/frontend/app/src/entities/ipam/ip-namespace-selector.tsx b/frontend/app/src/entities/ipam/ip-namespace-selector.tsx index 9abd09ae0d..a5d051d082 100644 --- a/frontend/app/src/entities/ipam/ip-namespace-selector.tsx +++ b/frontend/app/src/entities/ipam/ip-namespace-selector.tsx @@ -1,4 +1,3 @@ -import { currentBranchAtom } from "@/entities/branches/stores"; import { GET_IP_NAMESPACES } from "@/entities/ipam/api/ip-namespaces"; import { IpamNamespace } from "@/shared/api/graphql/generated/graphql"; import useQuery from "@/shared/api/graphql/useQuery"; @@ -11,7 +10,7 @@ import { ComboboxTrigger, } from "@/shared/components/ui/combobox"; import { Icon } from "@iconify-icon/react"; -import { useAtomValue, useSetAtom } from "jotai"; +import { useSetAtom } from "jotai"; import { useEffect, useId } from "react"; import { useNavigate, useParams } from "react-router"; import { StringParam, useQueryParam } from "use-query-params"; @@ -20,8 +19,7 @@ import { constructPathForIpam } from "./common/utils"; import { IPAM_QSP, IPAM_ROUTE, IPAM_TABS, NAMESPACE_GENERIC } from "./constants"; export default function IpNamespaceSelector() { - const currentBranchName = useAtomValue(currentBranchAtom); - const { loading, data, error } = useQuery(GET_IP_NAMESPACES, { skip: !currentBranchName }); + const { loading, data, error } = useQuery(GET_IP_NAMESPACES); if (loading) { return ; diff --git a/frontend/app/src/entities/nodes/api/generateRelationshipListQuery.ts b/frontend/app/src/entities/nodes/api/generateRelationshipListQuery.ts index 2f3303cbb5..5ad672d030 100644 --- a/frontend/app/src/entities/nodes/api/generateRelationshipListQuery.ts +++ b/frontend/app/src/entities/nodes/api/generateRelationshipListQuery.ts @@ -1,3 +1,4 @@ +import { PaginationParams } from "@/shared/api/types"; import { jsonToGraphQLQuery } from "json-to-graphql-query"; export const generateRelationshipListQuery = ({ @@ -7,11 +8,9 @@ export const generateRelationshipListQuery = ({ offset = 0, search = "", peerField, -}: { +}: PaginationParams & { peer: string; parent?: { name?: string; value?: string }; - limit?: number; - offset?: number; search?: string; peerField?: string; }): string => { diff --git a/frontend/app/src/entities/nodes/hierarchical-tree.tsx b/frontend/app/src/entities/nodes/hierarchical-tree.tsx index 3c68dce33b..55d883ccb1 100644 --- a/frontend/app/src/entities/nodes/hierarchical-tree.tsx +++ b/frontend/app/src/entities/nodes/hierarchical-tree.tsx @@ -1,4 +1,4 @@ -import { currentBranchAtom } from "@/entities/branches/stores"; +import { useCurrentBranch } from "@/entities/branches/ui/branches-provider"; import { TREE_ROOT_ID } from "@/entities/ipam/constants"; import { EMPTY_TREE, PrefixNode, updateTreeData } from "@/entities/ipam/ipam-tree/utils"; import { @@ -30,7 +30,7 @@ export type HierarchicalTreeProps = { export const HierarchicalTree = ({ schema, currentNodeId, className }: HierarchicalTreeProps) => { const navigate = useNavigate(); - const currentBranch = useAtomValue(currentBranchAtom); + const { currentBranch } = useCurrentBranch(); const currentDate = useAtomValue(datetimeAtom); const [filters] = useFilters(); const hasAutoGeneratedFiltered = filters.some( diff --git a/frontend/app/src/entities/nodes/object/api/delete-object-from-api.ts b/frontend/app/src/entities/nodes/object/api/delete-object-from-api.ts index 6936eb3093..8b6909072f 100644 --- a/frontend/app/src/entities/nodes/object/api/delete-object-from-api.ts +++ b/frontend/app/src/entities/nodes/object/api/delete-object-from-api.ts @@ -1,4 +1,5 @@ import graphqlClient from "@/shared/api/graphql/graphqlClientApollo"; +import { ContextParams } from "@/shared/api/types"; import { gql } from "@apollo/client"; import { jsonToGraphQLQuery } from "json-to-graphql-query"; @@ -22,7 +23,7 @@ export function deleteObjectFromApi({ objectId, branchName, atDate, -}: { objectKind: string; objectId: string; branchName: string; atDate: Date | null }) { +}: ContextParams & { objectKind: string; objectId: string }) { return graphqlClient.mutate({ mutation: gql(getDeleteObjectQuery(objectKind, objectId)), context: { diff --git a/frontend/app/src/entities/nodes/object/domain/delete-object.mutation.ts b/frontend/app/src/entities/nodes/object/domain/delete-object.mutation.ts index 2bb7cddbbf..3f4d79484d 100644 --- a/frontend/app/src/entities/nodes/object/domain/delete-object.mutation.ts +++ b/frontend/app/src/entities/nodes/object/domain/delete-object.mutation.ts @@ -1,8 +1,8 @@ -import { getCurrentBranchName } from "@/entities/branches/domain/get-current-branch"; +import { useCurrentBranch } from "@/entities/branches/ui/branches-provider"; import { queryClient } from "@/shared/api/rest/client"; -import { store } from "@/shared/stores"; import { datetimeAtom } from "@/shared/stores/time.atom"; import { useMutation } from "@tanstack/react-query"; +import { useAtomValue } from "jotai"; import { deleteObject } from "./delete-object"; export interface DeleteObjectParams { @@ -11,22 +11,24 @@ export interface DeleteObjectParams { } export function useDeleteObject() { - const currentBranchName = getCurrentBranchName(); - const timeMachineDate = store.get(datetimeAtom); + const { currentBranch } = useCurrentBranch(); + const timeMachineDate = useAtomValue(datetimeAtom); return useMutation({ mutationFn: async ({ objectKind, objectId }: DeleteObjectParams) => { await deleteObject({ objectKind, objectId, - branchName: currentBranchName, + branchName: currentBranch.name, atDate: timeMachineDate, }); return { objectKind, objectId }; }, onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ["objects"] }); + queryClient.invalidateQueries({ + predicate: (query) => query.queryKey.includes("objects"), + }); }, }); } diff --git a/frontend/app/src/entities/nodes/object/domain/delete-object.ts b/frontend/app/src/entities/nodes/object/domain/delete-object.ts index 0cfb890f20..52bf391ac1 100644 --- a/frontend/app/src/entities/nodes/object/domain/delete-object.ts +++ b/frontend/app/src/entities/nodes/object/domain/delete-object.ts @@ -1,11 +1,12 @@ import { deleteObjectFromApi } from "@/entities/nodes/object/api/delete-object-from-api"; +import { ContextParams } from "@/shared/api/types"; -export type DeleteObject = (data: { - objectKind: string; - objectId: string; - branchName: string; - atDate: Date | null; -}) => Promise; +export type DeleteObject = ( + data: ContextParams & { + objectKind: string; + objectId: string; + } +) => Promise; export const deleteObject: DeleteObject = async (data) => { await deleteObjectFromApi(data); diff --git a/frontend/app/src/entities/nodes/object/domain/get-objects.query.ts b/frontend/app/src/entities/nodes/object/domain/get-objects.query.ts index c587c71789..d4d69d21f1 100644 --- a/frontend/app/src/entities/nodes/object/domain/get-objects.query.ts +++ b/frontend/app/src/entities/nodes/object/domain/get-objects.query.ts @@ -1,26 +1,31 @@ -import { getCurrentBranchName } from "@/entities/branches/domain/get-current-branch"; +import { useCurrentBranch } from "@/entities/branches/ui/branches-provider"; import { IModelSchema } from "@/entities/schema/stores/schema.atom"; +import { ContextParams } from "@/shared/api/types"; import { Filter } from "@/shared/hooks/useFilters"; -import { store } from "@/shared/stores"; import { datetimeAtom } from "@/shared/stores/time.atom"; -import { infiniteQueryOptions } from "@tanstack/react-query"; +import { infiniteQueryOptions, useInfiniteQuery } from "@tanstack/react-query"; +import { useAtomValue } from "jotai"; import { OBJECTS_PER_PAGE, getObjects } from "./get-objects"; +type GetObjectsQueryParams = ContextParams & { + schema: IModelSchema; + filters?: Array; +}; + export function getObjectsInfiniteQueryOptions({ schema, filters, -}: { schema: IModelSchema; filters?: Array }) { - const currentBranchName = getCurrentBranchName(); - const timeMachineDate = store.get(datetimeAtom); - + branchName, + atDate, +}: GetObjectsQueryParams) { return infiniteQueryOptions({ - queryKey: ["objects", schema.kind, currentBranchName, timeMachineDate, JSON.stringify(filters)], + queryKey: [branchName, atDate, "objects", schema.kind, JSON.stringify(filters)], queryFn: ({ pageParam }) => { return getObjects({ schema, offset: pageParam, - branchName: currentBranchName, - atDate: timeMachineDate, + branchName, + atDate, filters, }); }, @@ -33,3 +38,16 @@ export function getObjectsInfiniteQueryOptions({ }, }); } + +export function useObjects(params: Omit) { + const { currentBranch } = useCurrentBranch(); + const timeMachineDate = useAtomValue(datetimeAtom); + + return useInfiniteQuery( + getObjectsInfiniteQueryOptions({ + ...params, + branchName: currentBranch.name, + atDate: timeMachineDate, + }) + ); +} diff --git a/frontend/app/src/entities/nodes/object/domain/get-objects.ts b/frontend/app/src/entities/nodes/object/domain/get-objects.ts index c09e85a9fb..2bc56dc247 100644 --- a/frontend/app/src/entities/nodes/object/domain/get-objects.ts +++ b/frontend/app/src/entities/nodes/object/domain/get-objects.ts @@ -4,6 +4,7 @@ import { NodeObject } from "@/entities/nodes/types"; import { IModelSchema } from "@/entities/schema/stores/schema.atom"; import graphqlClient from "@/shared/api/graphql/graphqlClientApollo"; import { addAttributesToRequest, addRelationshipsToRequest } from "@/shared/api/graphql/utils"; +import { ContextParams, PaginationParams } from "@/shared/api/types"; import { Filter } from "@/shared/hooks/useFilters"; import { gql } from "@apollo/client"; import { jsonToGraphQLQuery } from "json-to-graphql-query"; @@ -14,15 +15,22 @@ export const OBJECTS_PER_PAGE = 40; //////////////////////////////////////////////////////////////////////////////////////////////////// -export type GetObjects = (args: { - schema: IModelSchema; - offset?: number; - branchName: string; - atDate?: Date | null; - filters?: Array; -}) => Promise>; +export type GetObjects = ( + args: ContextParams & + PaginationParams & { + schema: IModelSchema; + filters?: Array; + } +) => Promise>; -export const getObjects: GetObjects = async ({ schema, offset, branchName, atDate, filters }) => { +export const getObjects: GetObjects = async ({ + schema, + limit = OBJECTS_PER_PAGE, + offset, + branchName, + atDate, + filters, +}) => { const attributesVisible = getAttributesVisibleInListView(schema.attributes ?? []); const relationshipsVisible = getRelationshipsVisibleInListView(schema.relationships ?? []); @@ -35,7 +43,7 @@ export const getObjects: GetObjects = async ({ schema, offset, branchName, atDat __name: `GetObjects${schemaKind}`, [schemaKindToQuery]: { __args: { - limit: OBJECTS_PER_PAGE, + limit, offset, ...(filters ? filters.reduce( diff --git a/frontend/app/src/entities/nodes/object/ui/object-table/object-table.tsx b/frontend/app/src/entities/nodes/object/ui/object-table/object-table.tsx index c53ac11bce..281ba0c59e 100644 --- a/frontend/app/src/entities/nodes/object/ui/object-table/object-table.tsx +++ b/frontend/app/src/entities/nodes/object/ui/object-table/object-table.tsx @@ -1,10 +1,9 @@ -import { getObjectsInfiniteQueryOptions } from "@/entities/nodes/object/domain/get-objects.query"; +import { useObjects } from "@/entities/nodes/object/domain/get-objects.query"; import { ObjectTableEmpty } from "@/entities/nodes/object/ui/object-table/object-table-empty"; import { Permission } from "@/entities/permission/types"; import { IModelSchema } from "@/entities/schema/stores/schema.atom"; import { InfiniteDataTable } from "@/shared/components/table/infinite-data-table"; import useFilters from "@/shared/hooks/useFilters"; -import { useInfiniteQuery } from "@tanstack/react-query"; import React from "react"; import { getObjectTableColumns } from "./get-object-table-columns"; @@ -15,9 +14,10 @@ export interface ObjectsTableProps { export const ObjectTable = ({ schema, permission }: ObjectsTableProps) => { const [filters] = useFilters(); - const { isPending, data, fetchNextPage, hasNextPage, isFetchingNextPage } = useInfiniteQuery( - getObjectsInfiniteQueryOptions({ schema, filters }) - ); + const { isPending, data, fetchNextPage, hasNextPage, isFetchingNextPage } = useObjects({ + schema, + filters, + }); const columns = React.useMemo(() => getObjectTableColumns(schema, permission), [schema.hash]); const flatData = React.useMemo(() => data?.pages?.flat() ?? [], [data]); diff --git a/frontend/app/src/entities/nodes/object/ui/objects-manager.tsx b/frontend/app/src/entities/nodes/object/ui/objects-manager.tsx index a44ecd8d8e..596a4d8694 100644 --- a/frontend/app/src/entities/nodes/object/ui/objects-manager.tsx +++ b/frontend/app/src/entities/nodes/object/ui/objects-manager.tsx @@ -1,4 +1,3 @@ -import { getObjectsInfiniteQueryOptions } from "@/entities/nodes/object/domain/get-objects.query"; import { ActiveFilterTags } from "@/entities/nodes/object/ui/filters/active-filter-tags"; import { FilterResetButton } from "@/entities/nodes/object/ui/filters/filter-reset-button"; import { FilterSearchInput } from "@/entities/nodes/object/ui/filters/filter-search-input"; @@ -46,7 +45,9 @@ export function ObjectsManager({ schema }: ObjectsTableManagerProps) { { - queryClient.invalidateQueries(getObjectsInfiniteQueryOptions({ schema, filters })); + queryClient.invalidateQueries({ + predicate: (query) => query.queryKey.includes("objects"), + }); }} permission={permission} className="ml-auto" diff --git a/frontend/app/src/entities/nodes/relationships/api/get-relationships-from-api.ts b/frontend/app/src/entities/nodes/relationships/api/get-relationships-from-api.ts index 5fa09cd72c..a0a3c887cd 100644 --- a/frontend/app/src/entities/nodes/relationships/api/get-relationships-from-api.ts +++ b/frontend/app/src/entities/nodes/relationships/api/get-relationships-from-api.ts @@ -1,16 +1,14 @@ import { generateRelationshipListQuery } from "@/entities/nodes/api/generateRelationshipListQuery"; import graphqlClient from "@/shared/api/graphql/graphqlClientApollo"; +import { ContextParams, PaginationParams } from "@/shared/api/types"; import { gql } from "@apollo/client"; -export type getRelationshipsFromApiParams = { - peer: string; - limit?: number; - offset?: number; - search?: string; - branchName: string; - atDate: Date | null; - parent?: { name: string; value: string }; -}; +export type getRelationshipsFromApiParams = ContextParams & + PaginationParams & { + peer: string; + search?: string; + parent?: { name: string; value: string }; + }; export const getRelationshipsFromApi = async ({ peer, diff --git a/frontend/app/src/entities/nodes/relationships/domain/get-relationships/get-relationships.query.ts b/frontend/app/src/entities/nodes/relationships/domain/get-relationships/get-relationships.query.ts index 7a85b9797a..62e8119dd3 100644 --- a/frontend/app/src/entities/nodes/relationships/domain/get-relationships/get-relationships.query.ts +++ b/frontend/app/src/entities/nodes/relationships/domain/get-relationships/get-relationships.query.ts @@ -1,24 +1,28 @@ -import { getCurrentBranchName } from "@/entities/branches/domain/get-current-branch"; +import { useCurrentBranch } from "@/entities/branches/ui/branches-provider"; import { GetRelationshipsParams, RELATIONSHIPS_PER_PAGE, getRelationships, } from "@/entities/nodes/relationships/domain/get-relationships/get-relationships"; -import { store } from "@/shared/stores"; +import { ContextParams, PaginationParams } from "@/shared/api/types"; import { datetimeAtom } from "@/shared/stores/time.atom"; -import { infiniteQueryOptions } from "@tanstack/react-query"; +import { infiniteQueryOptions, useInfiniteQuery } from "@tanstack/react-query"; +import { useAtomValue } from "jotai"; -export function relationshipsInfiniteQueryOptions({ +export type GetRelationshipsQueryParams = Omit; + +export function getRelationshipsInfiniteQueryOptions({ peer, search, parentId, -}: GetRelationshipsParams) { - const currentBranchName = getCurrentBranchName(); - const timeMachineDate = store.get(datetimeAtom); - + branchName, + atDate, +}: GetRelationshipsQueryParams) { return infiniteQueryOptions({ - queryKey: [currentBranchName, timeMachineDate, "relationships", peer, search, parentId], - queryFn: ({ pageParam }) => getRelationships({ peer, offset: pageParam, search, parentId }), + queryKey: [branchName, atDate, "relationships", peer, search, parentId], + queryFn: ({ pageParam }) => { + return getRelationships({ peer, offset: pageParam, search, parentId, branchName, atDate }); + }, initialPageParam: 0, getNextPageParam: (lastPage, _, lastPageParam) => { if (lastPage.length < RELATIONSHIPS_PER_PAGE) { @@ -28,3 +32,16 @@ export function relationshipsInfiniteQueryOptions({ }, }); } + +export function useRelationships(params: Omit) { + const { currentBranch } = useCurrentBranch(); + const timeMachineDate = useAtomValue(datetimeAtom); + + return useInfiniteQuery( + getRelationshipsInfiniteQueryOptions({ + ...params, + branchName: currentBranch.name, + atDate: timeMachineDate, + }) + ); +} diff --git a/frontend/app/src/entities/nodes/relationships/domain/get-relationships/get-relationships.test.ts b/frontend/app/src/entities/nodes/relationships/domain/get-relationships/get-relationships.test.ts index 2a86c95fa2..91dfd7a593 100644 --- a/frontend/app/src/entities/nodes/relationships/domain/get-relationships/get-relationships.test.ts +++ b/frontend/app/src/entities/nodes/relationships/domain/get-relationships/get-relationships.test.ts @@ -1,13 +1,8 @@ -import { getCurrentBranchName } from "@/entities/branches/domain/get-current-branch"; import { getRelationshipsFromApi } from "@/entities/nodes/relationships/api/get-relationships-from-api"; import { getRelationships } from "@/entities/nodes/relationships/domain/get-relationships/get-relationships"; -import { store } from "@/shared/stores"; -import { datetimeAtom } from "@/shared/stores/time.atom"; import { beforeEach, describe, expect, it, vi } from "vitest"; -vi.mock("@/entities/branches/domain/get-current-branch"); vi.mock("@/entities/nodes/relationships/api/get-relationships-from-api"); -vi.mock("@/shared/stores"); describe("getRelationships", () => { beforeEach(() => { @@ -20,8 +15,8 @@ describe("getRelationships", () => { const offset = 0; const search = "search-term"; const parentId = "parent-123"; - const mockBranch = "main"; - const mockDate = "2024-01-01"; + const branchName = "main"; + const atDate = new Date("2024-01-01"); const mockResponse = { data: { "test-peer": { @@ -40,23 +35,19 @@ describe("getRelationships", () => { networkStatus: 7, }; - vi.mocked(getCurrentBranchName).mockReturnValue(mockBranch); - vi.mocked(store.get).mockReturnValue(mockDate); vi.mocked(getRelationshipsFromApi).mockResolvedValue(mockResponse); // WHEN - const result = await getRelationships({ peer, offset, search, parentId }); + const result = await getRelationships({ peer, offset, search, parentId, branchName, atDate }); // THEN - expect(getCurrentBranchName).toHaveBeenCalledOnce(); - expect(store.get).toHaveBeenCalledWith(datetimeAtom); expect(getRelationshipsFromApi).toHaveBeenCalledWith({ peer, + branchName, + atDate, limit: 20, offset, search, - branchName: mockBranch, - atDate: mockDate, parent: { name: "parent", value: parentId }, }); expect(result).toEqual([ @@ -71,8 +62,8 @@ describe("getRelationships", () => { it("should fetch relationships without optional parameters", async () => { // GIVEN const peer = "test-peer"; - const mockBranch = "main"; - const mockDate = "2024-01-01"; + const branchName = "main"; + const atDate = new Date("2024-01-01"); const mockResponse = { data: { "test-peer": { @@ -83,21 +74,19 @@ describe("getRelationships", () => { networkStatus: 7, }; - vi.mocked(getCurrentBranchName).mockReturnValue(mockBranch); - vi.mocked(store.get).mockReturnValue(mockDate); vi.mocked(getRelationshipsFromApi).mockResolvedValue(mockResponse); // WHEN - const result = await getRelationships({ peer }); + const result = await getRelationships({ peer, branchName, atDate }); // THEN expect(getRelationshipsFromApi).toHaveBeenCalledWith({ peer, + branchName, + atDate, limit: 20, offset: undefined, search: undefined, - branchName: mockBranch, - atDate: mockDate, parent: undefined, }); expect(result).toEqual([]); diff --git a/frontend/app/src/entities/nodes/relationships/domain/get-relationships/get-relationships.ts b/frontend/app/src/entities/nodes/relationships/domain/get-relationships/get-relationships.ts index 4ad40726ab..308adb2bea 100644 --- a/frontend/app/src/entities/nodes/relationships/domain/get-relationships/get-relationships.ts +++ b/frontend/app/src/entities/nodes/relationships/domain/get-relationships/get-relationships.ts @@ -1,8 +1,6 @@ -import { getCurrentBranchName } from "@/entities/branches/domain/get-current-branch"; import { getRelationshipsFromApi } from "@/entities/nodes/relationships/api/get-relationships-from-api"; import { RelationshipNode } from "@/entities/nodes/relationships/domain/types"; -import { store } from "@/shared/stores"; -import { datetimeAtom } from "@/shared/stores/time.atom"; +import { ContextParams, PaginationParams } from "@/shared/api/types"; //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -10,27 +8,31 @@ export const RELATIONSHIPS_PER_PAGE = 20; //////////////////////////////////////////////////////////////////////////////////////////////////// -export type GetRelationshipsParams = { - peer: string; - search?: string; - parentId?: string; -}; - -export type GetRelationships = ( - params: GetRelationshipsParams & { limit?: number; offset?: number } -) => Promise>; - -export const getRelationships: GetRelationships = async ({ peer, offset, search, parentId }) => { - const currentBranchName = getCurrentBranchName(); - const timeMachineDate = store.get(datetimeAtom); - +export type GetRelationshipsParams = ContextParams & + PaginationParams & { + peer: string; + search?: string; + parentId?: string; + }; + +export type GetRelationships = (params: GetRelationshipsParams) => Promise>; + +export const getRelationships: GetRelationships = async ({ + branchName, + atDate, + limit = RELATIONSHIPS_PER_PAGE, + offset, + peer, + search, + parentId, +}) => { const { data } = await getRelationshipsFromApi({ peer, - limit: RELATIONSHIPS_PER_PAGE, + limit, offset, search, - branchName: currentBranchName, - atDate: timeMachineDate, + branchName, + atDate, parent: parentId ? { name: "parent", value: parentId } : undefined, }); diff --git a/frontend/app/src/entities/nodes/relationships/ui/relationship-combobox-list.tsx b/frontend/app/src/entities/nodes/relationships/ui/relationship-combobox-list.tsx index b210ba78ce..d8e3156df2 100644 --- a/frontend/app/src/entities/nodes/relationships/ui/relationship-combobox-list.tsx +++ b/frontend/app/src/entities/nodes/relationships/ui/relationship-combobox-list.tsx @@ -1,11 +1,10 @@ -import { relationshipsInfiniteQueryOptions } from "@/entities/nodes/relationships/domain/get-relationships/get-relationships.query"; +import { useRelationships } from "@/entities/nodes/relationships/domain/get-relationships/get-relationships.query"; import { RelationshipNode } from "@/entities/nodes/relationships/domain/types"; import { useSchema } from "@/entities/schema/hooks/useSchema"; import ErrorScreen from "@/shared/components/errors/error-screen"; import { ComboboxEmpty, ComboboxItem, ComboboxList } from "@/shared/components/ui/combobox"; import { Spinner } from "@/shared/components/ui/spinner"; import { debounce } from "@/shared/utils/common"; -import { useInfiniteQuery } from "@tanstack/react-query"; import React, { forwardRef } from "react"; export interface RelationshipComboboxListProps { @@ -20,7 +19,7 @@ export const RelationshipComboboxList = forwardRef; diff --git a/frontend/app/src/entities/nodes/relationships/ui/relationship-hierarchical-combobox-list.tsx b/frontend/app/src/entities/nodes/relationships/ui/relationship-hierarchical-combobox-list.tsx index fe84b7dbdc..14b7181a5b 100644 --- a/frontend/app/src/entities/nodes/relationships/ui/relationship-hierarchical-combobox-list.tsx +++ b/frontend/app/src/entities/nodes/relationships/ui/relationship-hierarchical-combobox-list.tsx @@ -1,4 +1,5 @@ -import { relationshipsInfiniteQueryOptions } from "@/entities/nodes/relationships/domain/get-relationships/get-relationships.query"; +import { useCurrentBranch } from "@/entities/branches/ui/branches-provider"; +import { getRelationshipsInfiniteQueryOptions } from "@/entities/nodes/relationships/domain/get-relationships/get-relationships.query"; import { RelationshipNode } from "@/entities/nodes/relationships/domain/types"; import { useSchema } from "@/entities/schema/hooks/useSchema"; import { iNodeSchema, schemaState } from "@/entities/schema/stores/schema.atom"; @@ -65,10 +66,20 @@ const HierarchicalExplorer = ({ }: HierarchicalExplorerProps) => { const peer = topLevelNode ? topLevelSchema.children : topLevelSchema.kind; const nodeSchemas = useAtomValue(schemaState); + const { currentBranch } = useCurrentBranch(); + const branchName = currentBranch.name; const [search, setSearch] = useState(""); const queryOptions = search - ? relationshipsInfiniteQueryOptions({ peer: topLevelSchema.hierarchy as string, search }) - : relationshipsInfiniteQueryOptions({ peer: peer as string, parentId: topLevelNode?.id }); + ? getRelationshipsInfiniteQueryOptions({ + peer: topLevelSchema.hierarchy as string, + search, + branchName, + }) + : getRelationshipsInfiniteQueryOptions({ + peer: peer as string, + parentId: topLevelNode?.id, + branchName, + }); const { isPending, data, error, fetchNextPage, hasNextPage, isFetchingNextPage } = useInfiniteQuery(queryOptions); diff --git a/frontend/app/src/entities/permission/api/get-permissions-from-api.ts b/frontend/app/src/entities/permission/api/get-permissions-from-api.ts index 1b56d7658e..9731053e32 100644 --- a/frontend/app/src/entities/permission/api/get-permissions-from-api.ts +++ b/frontend/app/src/entities/permission/api/get-permissions-from-api.ts @@ -1,12 +1,9 @@ import { getObjectPermissionsQuery } from "@/entities/permission/queries/getObjectPermissions"; import graphqlClient from "@/shared/api/graphql/graphqlClientApollo"; +import { ContextParams } from "@/shared/api/types"; import { gql } from "@apollo/client"; -export type GetPermissionsFromApiParams = { - kind: string; - branchName?: string | null; - atDate?: Date | null; -}; +export type GetPermissionsFromApiParams = ContextParams & { kind: string }; export const getPermissionsFromApi = ({ kind, diff --git a/frontend/app/src/entities/permission/domain/get-object-permissions.query.ts b/frontend/app/src/entities/permission/domain/get-object-permissions.query.ts index 89afe11da0..1d70230641 100644 --- a/frontend/app/src/entities/permission/domain/get-object-permissions.query.ts +++ b/frontend/app/src/entities/permission/domain/get-object-permissions.query.ts @@ -1,28 +1,41 @@ import { useAuth } from "@/entities/authentication/ui/useAuth"; -import { getCurrentBranchName } from "@/entities/branches/domain/get-current-branch"; +import { useCurrentBranch } from "@/entities/branches/ui/branches-provider"; import { getObjectPermissions } from "@/entities/permission/domain/get-object-permissions"; -import { store } from "@/shared/stores"; +import { ContextParams } from "@/shared/api/types"; import { datetimeAtom } from "@/shared/stores/time.atom"; import { queryOptions, useQuery } from "@tanstack/react-query"; +import { useAtomValue } from "jotai"; -export interface GetObjectPermissionsParams { +export type GetObjectPermissionsParams = ContextParams & { kind: string; userId?: string; -} - -export const getObjectPermissionsQueryOptions = ({ kind, userId }: GetObjectPermissionsParams) => { - const currentBranchName = getCurrentBranchName(); - const timeMachineDate = store.get(datetimeAtom); +}; +export const getObjectPermissionsQueryOptions = ({ + kind, + userId, + branchName, + atDate, +}: GetObjectPermissionsParams) => { return queryOptions({ - queryKey: ["permissions", userId, kind, currentBranchName, timeMachineDate], + queryKey: [branchName, atDate, "permissions", kind, userId], queryFn: () => { - return getObjectPermissions({ kind, branchName: currentBranchName, atDate: timeMachineDate }); + return getObjectPermissions({ kind, branchName, atDate }); }, }); }; export const useGetObjectPermissions = (kind: string) => { const auth = useAuth(); - return useQuery(getObjectPermissionsQueryOptions({ kind, userId: auth.user?.id })); + const { currentBranch } = useCurrentBranch(); + const timeMachineDate = useAtomValue(datetimeAtom); + + return useQuery( + getObjectPermissionsQueryOptions({ + kind, + userId: auth.user?.id, + branchName: currentBranch.name, + atDate: timeMachineDate, + }) + ); }; diff --git a/frontend/app/src/entities/permission/domain/get-object-permissions.ts b/frontend/app/src/entities/permission/domain/get-object-permissions.ts index fbf173f32a..c68338273b 100644 --- a/frontend/app/src/entities/permission/domain/get-object-permissions.ts +++ b/frontend/app/src/entities/permission/domain/get-object-permissions.ts @@ -1,12 +1,9 @@ import { getPermissionsFromApi } from "@/entities/permission/api/get-permissions-from-api"; import { Permission } from "@/entities/permission/types"; import { getPermission } from "@/entities/permission/utils"; +import { ContextParams } from "@/shared/api/types"; -export type GetObjectPermissions = (args: { - kind: string; - branchName?: string | null; - atDate?: Date | null; -}) => Promise; +export type GetObjectPermissions = (args: ContextParams & { kind: string }) => Promise; export const getObjectPermissions: GetObjectPermissions = async ({ kind, branchName, atDate }) => { const { data } = await getPermissionsFromApi({ kind, branchName, atDate }); diff --git a/frontend/app/src/entities/proposed-changes/ui/proposed-change-edit-form.tsx b/frontend/app/src/entities/proposed-changes/ui/proposed-change-edit-form.tsx index 8b55dfd3d7..c95b845542 100644 --- a/frontend/app/src/entities/proposed-changes/ui/proposed-change-edit-form.tsx +++ b/frontend/app/src/entities/proposed-changes/ui/proposed-change-edit-form.tsx @@ -1,5 +1,6 @@ import { ACCOUNT_GENERIC_OBJECT, PROPOSED_CHANGES_OBJECT } from "@/config/constants"; -import { branchesState, currentBranchAtom } from "@/entities/branches/stores"; +import { branchesState } from "@/entities/branches/stores"; +import { useCurrentBranch } from "@/entities/branches/ui/branches-provider"; import { updateObjectWithId } from "@/entities/nodes/api/updateObjectWithId"; import { AttributeType } from "@/entities/nodes/getObjectItemDisplayValue"; import { schemaState } from "@/entities/schema/stores/schema.atom"; @@ -22,7 +23,7 @@ type ProposedChangeEditFormProps = { export const ProposedChangeEditForm = ({ initialData, onSuccess }: ProposedChangeEditFormProps) => { const nodes = useAtomValue(schemaState); const branches = useAtomValue(branchesState); - const currentBranch = useAtomValue(currentBranchAtom); + const { currentBranch } = useCurrentBranch(); const date = useAtomValue(datetimeAtom); const proposedChangeSchema = nodes.find(({ kind }) => kind === PROPOSED_CHANGES_OBJECT); @@ -114,7 +115,7 @@ export const ProposedChangeEditForm = ({ initialData, onSuccess }: ProposedChang await graphqlClient.mutate({ mutation, - context: { branch: currentBranch?.name, date }, + context: { branch: currentBranch.name, date }, }); toast( diff --git a/frontend/app/src/entities/tasks/domain/is-task-running-on-branch.query.ts b/frontend/app/src/entities/tasks/domain/is-task-running-on-branch.query.ts index 8e785d2723..3489801144 100644 --- a/frontend/app/src/entities/tasks/domain/is-task-running-on-branch.query.ts +++ b/frontend/app/src/entities/tasks/domain/is-task-running-on-branch.query.ts @@ -3,7 +3,7 @@ import { queryOptions } from "@tanstack/react-query"; export const isTaskRunningOnBranchQueryOptions = (branch: string) => { return queryOptions({ - queryKey: ["is-task-running", branch], + queryKey: [branch, "is-task-running"], queryFn: () => isTaskRunningOnBranch(branch), }); }; diff --git a/frontend/app/src/entities/tasks/ui/task-status.test.tsx b/frontend/app/src/entities/tasks/ui/task-status.test.tsx index f5860244ba..83179e6b7f 100644 --- a/frontend/app/src/entities/tasks/ui/task-status.test.tsx +++ b/frontend/app/src/entities/tasks/ui/task-status.test.tsx @@ -1,4 +1,4 @@ -import { useCurrentBranch } from "@/entities/branches/ui/hooks/use-current-branch"; +import { useCurrentBranch } from "@/entities/branches/ui/branches-provider"; import { getBranchTaskStatusFromApi } from "@/entities/tasks/api/get-branch-task-status-from-api"; import { NetworkStatus } from "@apollo/client"; import { describe, expect, test } from "vitest"; @@ -6,7 +6,7 @@ import { render } from "../../../../tests/components/render"; import { generateBranch } from "../../../../tests/fake/branch"; import { TaskStatus } from "./task-status"; -vi.mock("@/entities/branches/ui/hooks/use-current-branch"); +vi.mock("@/entities/branches/ui/branches-provider"); vi.mock("@/entities/tasks/api/get-branch-task-status-from-api"); describe("TaskStatus", () => { @@ -16,7 +16,7 @@ describe("TaskStatus", () => { test("renders task status with pulse when tasks are running", async () => { // GIVEN const branch = generateBranch({ name: "branch1" }); - useCurrentBranchMock.mockReturnValue(branch); + useCurrentBranchMock.mockReturnValue({ currentBranch: branch, setCurrentBranch: () => {} }); getBranchTaskStatusFromApiMock.mockResolvedValue({ data: { InfrahubTaskBranchStatus: { count: 1 } }, loading: false, @@ -44,7 +44,10 @@ describe("TaskStatus", () => { test("renders task status without pulse when no tasks are running", async () => { // GIVEN - useCurrentBranchMock.mockReturnValue(generateBranch({ name: "branch1" })); + useCurrentBranchMock.mockReturnValue({ + currentBranch: generateBranch({ name: "branch1" }), + setCurrentBranch: () => {}, + }); getBranchTaskStatusFromApiMock.mockResolvedValue({ data: { InfrahubTaskBranchStatus: { count: 0 } }, loading: false, @@ -66,7 +69,10 @@ describe("TaskStatus", () => { test("renders error icon with tooltip when query fails", async () => { // GIVEN - useCurrentBranchMock.mockReturnValue(generateBranch({ name: "branch1" })); + useCurrentBranchMock.mockReturnValue({ + currentBranch: generateBranch({ name: "branch1" }), + setCurrentBranch: () => {}, + }); getBranchTaskStatusFromApiMock.mockResolvedValue({ data: null!, error: {} as any, diff --git a/frontend/app/src/entities/tasks/ui/task-status.tsx b/frontend/app/src/entities/tasks/ui/task-status.tsx index d1d37f99b1..a37735b2c4 100644 --- a/frontend/app/src/entities/tasks/ui/task-status.tsx +++ b/frontend/app/src/entities/tasks/ui/task-status.tsx @@ -1,6 +1,6 @@ import TasksStatusIcon from "@/assets/icons/tasks-status.svg?react"; import { QSP } from "@/config/qsp"; -import { useCurrentBranch } from "@/entities/branches/ui/hooks/use-current-branch"; +import { useCurrentBranch } from "@/entities/branches/ui/branches-provider"; import { isTaskRunningOnBranchQueryOptions } from "@/entities/tasks/domain/is-task-running-on-branch.query"; import { constructPath } from "@/shared/api/rest/fetch"; import { LinkButton, LinkButtonProps } from "@/shared/components/buttons/button-primitive"; @@ -11,21 +11,20 @@ import { Icon } from "@iconify-icon/react"; import { useQuery } from "@tanstack/react-query"; export function TaskStatus() { - const branch = useCurrentBranch(); + const { currentBranch } = useCurrentBranch(); const { error, isPending, data: isTaskRunningOnBranch, } = useQuery({ - ...isTaskRunningOnBranchQueryOptions(branch?.name ?? ""), - enabled: !!branch?.name, + ...isTaskRunningOnBranchQueryOptions(currentBranch.name), refetchInterval: 10_000, }); const filter = { name: "branch__value", - value: branch?.name, + value: currentBranch.name, }; const commonButtonProps: LinkButtonProps = { diff --git a/frontend/app/src/shared/api/types.ts b/frontend/app/src/shared/api/types.ts new file mode 100644 index 0000000000..7404dc85e8 --- /dev/null +++ b/frontend/app/src/shared/api/types.ts @@ -0,0 +1,9 @@ +export type ContextParams = { + branchName: string; + atDate?: Date | null; +}; + +export type PaginationParams = { + limit?: number; + offset?: number; +}; diff --git a/frontend/app/src/shared/components/branch-selector.tsx b/frontend/app/src/shared/components/branch-selector.tsx index 317f2b5a1e..73a0a481c6 100644 --- a/frontend/app/src/shared/components/branch-selector.tsx +++ b/frontend/app/src/shared/components/branch-selector.tsx @@ -1,15 +1,16 @@ import { QSP } from "@/config/qsp"; -import { branchesState, currentBranchAtom } from "@/entities/branches/stores"; +import { branchesState } from "@/entities/branches/stores"; import { branchesToSelectOptions } from "@/entities/branches/utils"; import { Branch } from "@/shared/api/graphql/generated/graphql"; import { Popover, PopoverContent, PopoverTrigger } from "@/shared/components/ui/popover"; import { Icon } from "@iconify-icon/react"; -import { useAtomValue, useSetAtom } from "jotai"; +import { useAtomValue } from "jotai"; import { useEffect, useState } from "react"; import { StringParam, useQueryParam } from "use-query-params"; import { useAuth } from "@/entities/authentication/ui/useAuth"; import { getBranchesQueryOptions } from "@/entities/branches/domain/get-branches.query"; +import { useCurrentBranch } from "@/entities/branches/ui/branches-provider"; import { queryClient } from "@/shared/api/rest/client"; import { constructPath } from "@/shared/api/rest/fetch"; import { ComboboxItem } from "@/shared/components/ui/combobox"; @@ -30,7 +31,7 @@ type DisplayForm = { }; export default function BranchSelector() { - const currentBranch = useAtomValue(currentBranchAtom); + const { currentBranch } = useCurrentBranch(); const [isOpen, setIsOpen] = useState(false); const [displayForm, setDisplayForm] = useState({ open: false }); @@ -54,7 +55,7 @@ export default function BranchSelector() { >
- {currentBranch?.name} + {currentBranch.name}
@@ -88,7 +89,7 @@ function BranchSelect({ setFormOpen: (displayForm: DisplayForm) => void; }) { const branches = useAtomValue(branchesState); - const setCurrentBranch = useSetAtom(currentBranchAtom); + const { setCurrentBranch } = useCurrentBranch(); const [, setBranchInQueryString] = useQueryParam(QSP.BRANCH, StringParam); const handleBranchChange = (branch: Branch) => { @@ -146,12 +147,12 @@ function BranchSelect({ } function BranchOption({ branch, onChange }: { branch: Branch; onChange: () => void }) { - const currentBranch = useAtomValue(currentBranchAtom); + const { currentBranch } = useCurrentBranch(); return ( diff --git a/frontend/app/src/shared/components/display/slide-over.tsx b/frontend/app/src/shared/components/display/slide-over.tsx index 33014e7116..6a7fd9a154 100644 --- a/frontend/app/src/shared/components/display/slide-over.tsx +++ b/frontend/app/src/shared/components/display/slide-over.tsx @@ -1,11 +1,10 @@ -import { currentBranchAtom } from "@/entities/branches/stores"; +import { useCurrentBranch } from "@/entities/branches/ui/branches-provider"; import { IModelSchema } from "@/entities/schema/stores/schema.atom"; import { ObjectHelpButton } from "@/shared/components/menu/object-help-button"; import { Badge } from "@/shared/components/ui/badge"; import usePrevious from "@/shared/hooks/usePrevious"; import { Dialog, Transition } from "@headlessui/react"; import { Icon } from "@iconify-icon/react"; -import { useAtomValue } from "jotai/index"; import React, { Fragment, useRef, useState } from "react"; import ModalDelete from "../modals/modal-delete"; @@ -129,14 +128,14 @@ export const SlideOverTitle = ({ title, subtitle, }: SlideOverTitleProps) => { - const currentBranch = useAtomValue(currentBranchAtom); + const { currentBranch } = useCurrentBranch(); return (
- {currentBranch?.name} + {currentBranch.name} Promise; - -const getMenu: GetMenu = async ({ branchName }) => { - const { data, error } = await apiClient.GET("/api/menu", { - params: { - query: { - branch: branchName, - }, - }, - }); - - if (error) throw error; - - return data as MenuData; -}; - -export function menuQueryOptions() { - const currentBranch = store.get(currentBranchAtom); - - return queryOptions({ - queryKey: ["menu", currentBranch?.name], - queryFn: () => getMenu({ branchName: currentBranch?.name ?? DEFAULT_BRANCH_NAME }), - enabled: !!currentBranch, - }); -} diff --git a/frontend/app/src/shared/components/layout/menu-navigation/menu-navigation.tsx b/frontend/app/src/shared/components/layout/menu-navigation/menu-navigation.tsx index be4b7cf644..a49907b971 100644 --- a/frontend/app/src/shared/components/layout/menu-navigation/menu-navigation.tsx +++ b/frontend/app/src/shared/components/layout/menu-navigation/menu-navigation.tsx @@ -1,18 +1,17 @@ import ErrorScreen from "@/shared/components/errors/error-screen"; import { MenuSectionInternal } from "@/shared/components/layout/menu-navigation/components/menu-section-internal"; import { MenuSectionObject } from "@/shared/components/layout/menu-navigation/components/menu-section-object"; -import { menuQueryOptions } from "@/shared/components/layout/menu-navigation/get-menu"; +import { useMenu } from "@/shared/components/menu/domain/get-menu.query"; import { Divider } from "@/shared/components/ui/divider"; import { ScrollArea } from "@/shared/components/ui/scroll-area"; import { Spinner } from "@/shared/components/ui/spinner"; -import { useQuery } from "@tanstack/react-query"; export interface MenuNavigationProps { isCollapsed?: boolean; } export default function MenuNavigation({ isCollapsed }: MenuNavigationProps) { - const { data: menu, isPending, error } = useQuery(menuQueryOptions()); + const { data: menu, isPending, error } = useMenu(); if (isPending) return ; if (error) return ; diff --git a/frontend/app/src/shared/components/menu/domain/get-menu.query.ts b/frontend/app/src/shared/components/menu/domain/get-menu.query.ts new file mode 100644 index 0000000000..6e3d08f72e --- /dev/null +++ b/frontend/app/src/shared/components/menu/domain/get-menu.query.ts @@ -0,0 +1,25 @@ +import { useCurrentBranch } from "@/entities/branches/ui/branches-provider"; +import { ContextParams } from "@/shared/api/types"; +import { getMenu } from "@/shared/components/menu/domain/get-menu"; +import { datetimeAtom } from "@/shared/stores/time.atom"; +import { queryOptions, useQuery } from "@tanstack/react-query"; +import { useAtomValue } from "jotai"; + +export function menuQueryOptions({ branchName, atDate }: ContextParams) { + return queryOptions({ + queryKey: [branchName, atDate, "menu"], + queryFn: () => getMenu({ branchName, atDate }), + }); +} + +export function useMenu() { + const { currentBranch } = useCurrentBranch(); + const timeMachineDate = useAtomValue(datetimeAtom); + + return useQuery( + menuQueryOptions({ + branchName: currentBranch.name, + atDate: timeMachineDate, + }) + ); +} diff --git a/frontend/app/src/shared/components/menu/domain/get-menu.ts b/frontend/app/src/shared/components/menu/domain/get-menu.ts new file mode 100644 index 0000000000..b27975c8f1 --- /dev/null +++ b/frontend/app/src/shared/components/menu/domain/get-menu.ts @@ -0,0 +1,20 @@ +import { apiClient } from "@/shared/api/rest/client"; +import { ContextParams } from "@/shared/api/types"; +import { MenuData } from "@/shared/components/layout/menu-navigation/types"; + +type GetMenu = (params: ContextParams) => Promise; + +export const getMenu: GetMenu = async ({ branchName, atDate }) => { + const { data, error } = await apiClient.GET("/api/menu", { + params: { + query: { + branch: branchName, + date: atDate, + }, + }, + }); + + if (error) throw error; + + return data as MenuData; +}; diff --git a/frontend/app/src/shared/components/search/search-actions.tsx b/frontend/app/src/shared/components/search/search-actions.tsx index 91bab7e17f..2e82e7fda2 100644 --- a/frontend/app/src/shared/components/search/search-actions.tsx +++ b/frontend/app/src/shared/components/search/search-actions.tsx @@ -1,12 +1,11 @@ import { IModelSchema, genericsState, schemaState } from "@/entities/schema/stores/schema.atom"; import { constructPath } from "@/shared/api/rest/fetch"; -import { menuQueryOptions } from "@/shared/components/layout/menu-navigation/get-menu"; import { MenuItem } from "@/shared/components/layout/menu-navigation/types"; +import { useMenu } from "@/shared/components/menu/domain/get-menu.query"; import { SearchAnywhereGroup } from "@/shared/components/search/search-anywhere-group"; import { SearchAnywhereItem } from "@/shared/components/search/search-anywhere-item"; import { Badge } from "@/shared/components/ui/badge"; import { Icon } from "@iconify-icon/react"; -import { useQuery } from "@tanstack/react-query"; import { useCommandState } from "cmdk"; import { useAtomValue } from "jotai"; import { useMemo } from "react"; @@ -17,7 +16,7 @@ export const SearchActions = () => { const generics = useAtomValue(genericsState); const models: IModelSchema[] = [...nodes, ...generics]; - const { data: menuData, isPending, isError } = useQuery(menuQueryOptions()); + const { data: menuData, isPending, isError } = useMenu(); const menuItems = useMemo(() => { if (!menuData) return []; diff --git a/frontend/app/tests/components/render.tsx b/frontend/app/tests/components/render.tsx index 6eeb7e0656..cf64986b9e 100644 --- a/frontend/app/tests/components/render.tsx +++ b/frontend/app/tests/components/render.tsx @@ -7,39 +7,47 @@ import { Slide, ToastContainer } from "react-toastify"; import { QueryParamProvider } from "use-query-params"; import { render as renderFromVitest } from "vitest-browser-react"; +import { BranchContext } from "../../src/entities/branches/ui/branches-provider"; import { queryClient } from "../../src/shared/api/rest/client"; import { ReactRouter7Adapter } from "../../src/shared/lib/use-query-params"; import { store } from "../../src/shared/stores"; +import { generateBranch } from "../fake/branch"; import "/src/app/styles/index.css"; import "react-toastify/dist/ReactToastify.css"; export const render = (component: React.ReactElement, options = {}) => renderFromVitest(component, { - wrapper: ({ children }) => ( - - - - - - {children} - - - - - ), + wrapper: ({ children }) => { + const [currentBranch, setCurrentBranch] = React.useState(generateBranch()); + + return ( + + + + + + + {children} + + + + + + ); + }, ...options, });