Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
151 changes: 132 additions & 19 deletions datajunction-server/datajunction_server/api/graphql/queries/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from datajunction_server.api.graphql.resolvers.nodes import find_nodes_by
from datajunction_server.api.graphql.scalars import Connection
from datajunction_server.api.graphql.scalars.node import Node, NodeSortField
from datajunction_server.models.node import NodeCursor, NodeMode, NodeType
from datajunction_server.models.node import NodeCursor, NodeMode, NodeStatus, NodeType

DEFAULT_LIMIT = 1000
UPPER_LIMIT = 10000
Expand Down Expand Up @@ -51,6 +51,60 @@ async def find_nodes(
"Accepts dimension node names or dimension attributes",
),
] = None,
edited_by: Annotated[
str | None,
strawberry.argument(
description="Filter to nodes edited by this user",
),
] = None,
namespace: Annotated[
str | None,
strawberry.argument(
description="Filter to nodes in this namespace",
),
] = None,
mode: Annotated[
NodeMode | None,
strawberry.argument(
description="Filter to nodes with this mode (published or draft)",
),
] = None,
owned_by: Annotated[
str | None,
strawberry.argument(
description="Filter to nodes owned by this user",
),
] = None,
missing_description: Annotated[
bool,
strawberry.argument(
description="Filter to nodes missing descriptions (for data quality checks)",
),
] = False,
missing_owner: Annotated[
bool,
strawberry.argument(
description="Filter to nodes without any owners (for data quality checks)",
),
] = False,
statuses: Annotated[
list[NodeStatus] | None,
strawberry.argument(
description="Filter to nodes with these statuses (e.g., VALID, INVALID)",
),
] = None,
has_materialization: Annotated[
bool,
strawberry.argument(
description="Filter to nodes that have materializations configured",
),
] = False,
orphaned_dimension: Annotated[
bool,
strawberry.argument(
description="Filter to dimension nodes that are not linked to by any other node",
),
] = False,
limit: Annotated[
int | None,
strawberry.argument(description="Limit nodes"),
Expand All @@ -76,12 +130,21 @@ async def find_nodes(
limit = UPPER_LIMIT

return await find_nodes_by( # type: ignore
info,
names,
fragment,
node_types,
tags,
info=info,
names=names,
fragment=fragment,
node_types=node_types,
tags=tags,
dimensions=dimensions,
edited_by=edited_by,
namespace=namespace,
mode=mode,
owned_by=owned_by,
missing_description=missing_description,
missing_owner=missing_owner,
statuses=statuses,
has_materialization=has_materialization,
orphaned_dimension=orphaned_dimension,
limit=limit,
order_by=order_by,
ascending=ascending,
Expand Down Expand Up @@ -113,6 +176,13 @@ async def find_nodes_paginated(
description="Filter to nodes tagged with these tags",
),
] = None,
dimensions: Annotated[
list[str] | None,
strawberry.argument(
description="Filter to nodes that have ALL of these dimensions. "
"Accepts dimension node names or dimension attributes",
),
] = None,
edited_by: Annotated[
str | None,
strawberry.argument(
Expand All @@ -131,6 +201,42 @@ async def find_nodes_paginated(
description="Filter to nodes with this mode (published or draft)",
),
] = None,
owned_by: Annotated[
str | None,
strawberry.argument(
description="Filter to nodes owned by this user",
),
] = None,
missing_description: Annotated[
bool,
strawberry.argument(
description="Filter to nodes missing descriptions (for data quality checks)",
),
] = False,
missing_owner: Annotated[
bool,
strawberry.argument(
description="Filter to nodes without any owners (for data quality checks)",
),
] = False,
statuses: Annotated[
list[NodeStatus] | None,
strawberry.argument(
description="Filter to nodes with these statuses (e.g., VALID, INVALID)",
),
] = None,
has_materialization: Annotated[
bool,
strawberry.argument(
description="Filter to nodes that have materializations configured",
),
] = False,
orphaned_dimension: Annotated[
bool,
strawberry.argument(
description="Filter to dimension nodes that are not linked to by any other node",
),
] = False,
after: str | None = None,
before: str | None = None,
limit: Annotated[
Expand All @@ -148,19 +254,26 @@ async def find_nodes_paginated(
if not limit or limit < 0:
limit = 100
nodes_list = await find_nodes_by(
info,
names,
fragment,
node_types,
tags,
edited_by,
namespace,
limit + 1,
before,
after,
order_by,
ascending,
mode,
info=info,
names=names,
fragment=fragment,
node_types=node_types,
tags=tags,
dimensions=dimensions,
edited_by=edited_by,
namespace=namespace,
limit=limit + 1,
before=before,
after=after,
order_by=order_by,
ascending=ascending,
mode=mode,
owned_by=owned_by,
missing_description=missing_description,
missing_owner=missing_owner,
statuses=statuses,
has_materialization=has_materialization,
orphaned_dimension=orphaned_dimension,
)
return Connection.from_list(
items=nodes_list,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from datajunction_server.database.node import Column, ColumnAttribute
from datajunction_server.database.node import Node as DBNode
from datajunction_server.database.node import NodeRevision as DBNodeRevision
from datajunction_server.models.node import NodeMode, NodeType
from datajunction_server.models.node import NodeMode, NodeStatus, NodeType


async def find_nodes_by(
Expand All @@ -34,7 +34,13 @@ async def find_nodes_by(
order_by: NodeSortField = NodeSortField.CREATED_AT,
ascending: bool = False,
mode: Optional[NodeMode] = None,
owned_by: Optional[str] = None,
missing_description: bool = False,
missing_owner: bool = False,
dimensions: Optional[List[str]] = None,
statuses: Optional[List[NodeStatus]] = None,
has_materialization: bool = False,
orphaned_dimension: bool = False,
) -> List[DBNode]:
"""
Finds nodes based on the search parameters. This function also tries to optimize
Expand Down Expand Up @@ -64,6 +70,12 @@ async def find_nodes_by(
ascending=ascending,
options=options,
mode=mode,
owned_by=owned_by,
missing_description=missing_description,
missing_owner=missing_owner,
statuses=statuses,
has_materialization=has_materialization,
orphaned_dimension=orphaned_dimension,
dimensions=dimensions,
)

Expand Down
50 changes: 50 additions & 0 deletions datajunction-server/datajunction_server/api/graphql/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,33 @@ type Query {
"""
dimensions: [String!] = null

"""Filter to nodes edited by this user"""
editedBy: String = null

"""Filter to nodes in this namespace"""
namespace: String = null

"""Filter to nodes with this mode (published or draft)"""
mode: NodeMode = null

"""Filter to nodes owned by this user"""
ownedBy: String = null

"""Filter to nodes missing descriptions (for data quality checks)"""
missingDescription: Boolean! = false

"""Filter to nodes without any owners (for data quality checks)"""
missingOwner: Boolean! = false

"""Filter to nodes with these statuses (e.g., VALID, INVALID)"""
statuses: [NodeStatus!] = null

"""Filter to nodes that have materializations configured"""
hasMaterialization: Boolean! = false

"""Filter to dimension nodes that are not linked to by any other node"""
orphanedDimension: Boolean! = false

"""Limit nodes"""
limit: Int = 1000
orderBy: NodeSortField! = CREATED_AT
Expand All @@ -427,6 +454,11 @@ type Query {
"""Filter to nodes tagged with these tags"""
tags: [String!] = null

"""
Filter to nodes that have ALL of these dimensions. Accepts dimension node names or dimension attributes
"""
dimensions: [String!] = null

"""Filter to nodes edited by this user"""
editedBy: String = null

Expand All @@ -435,6 +467,24 @@ type Query {

"""Filter to nodes with this mode (published or draft)"""
mode: NodeMode = null

"""Filter to nodes owned by this user"""
ownedBy: String = null

"""Filter to nodes missing descriptions (for data quality checks)"""
missingDescription: Boolean! = false

"""Filter to nodes without any owners (for data quality checks)"""
missingOwner: Boolean! = false

"""Filter to nodes with these statuses (e.g., VALID, INVALID)"""
statuses: [NodeStatus!] = null

"""Filter to nodes that have materializations configured"""
hasMaterialization: Boolean! = false

"""Filter to dimension nodes that are not linked to by any other node"""
orphanedDimension: Boolean! = false
after: String = null
before: String = null

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,22 +197,24 @@ async def load_dimension_links_batch(
Returns a dict mapping link_id to DimensionLink object.

Note: Most dimension nodes should already be in ctx.nodes from query2.
We load current+query for pre-parsing cache, but skip heavy relationships.
We load current+query for pre-parsing cache, and columns for type lookups.
"""
if not link_ids:
return {}

# Load dimension links with minimal eager loading
# Need current.query for pre-parsing, but skip columns/dimension_links/etc
# Load dimension links with eager loading for columns (needed for type lookups)
stmt = (
select(DimensionLink)
.where(DimensionLink.id.in_(link_ids))
.options(
joinedload(DimensionLink.dimension).options(
joinedload(Node.current).options(
# Only load what's needed for table references and parsing
# Load what's needed for table references, parsing, and type lookups
joinedload(NodeRevision.catalog),
joinedload(NodeRevision.availability),
selectinload(NodeRevision.columns).options(
load_only(Column.name, Column.type),
),
),
),
)
Expand Down
Loading
Loading