Skip to content

Commit b464c19

Browse files
jspada200Copilot
andauthored
Frontend and prodtrack implementation (#44)
- Added to the shotgrid implementation - First pass on UI components --------- Signed-off-by: James Spadafora <spadjv@gmail.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 3bb76ac commit b464c19

56 files changed

Lines changed: 8336 additions & 300 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.cursor/rules/always-update-tests.mdc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@ alwaysApply: true
77

88
# Overview
99

10-
Always run tests when making changes to the code. For the Frontend, use npm run test. For the Backend, use make test.
10+
Always run tests when making changes to the code. For the Frontend, use npm run test. For the Backend, use make test. For the frontend use npm run test-ci.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
name: stlyed-components
3+
description: Use styled components for the frontend framework.
4+
---
5+
6+
# Overview
7+
8+
When working in the frontend, use styled components for the frontend framework. Only create custom components if absolutely necessary. Always check the latest styled components documentation for the best practices. Use a common file for variables and mixins but keep component specific styles in the component file.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
name: use-radix-themes-and-primitives
3+
description: Always use radix themes and primitives for the frontend framework.
4+
---
5+
6+
# Overview
7+
8+
Always use radix themes and primitives for the frontend framework. Only create custom components if absolutely necessary. Always check the latest radix themes and primitives documentation for the best practices.

backend/src/dna/models/__init__.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,33 @@
1010
EntityBase,
1111
Note,
1212
Playlist,
13+
Project,
1314
Shot,
1415
Task,
16+
User,
1517
Version,
1618
)
17-
from dna.models.requests import CreateNoteRequest, EntityLink
19+
from dna.models.requests import (
20+
CreateNoteRequest,
21+
EntityLink,
22+
FilterCondition,
23+
FindRequest,
24+
)
1825

1926
__all__ = [
2027
"EntityBase",
28+
"Project",
2129
"Shot",
2230
"Asset",
2331
"Note",
2432
"Task",
2533
"Version",
2634
"Playlist",
35+
"User",
2736
"DNAEntity",
2837
"ENTITY_MODELS",
2938
"EntityLink",
3039
"CreateNoteRequest",
40+
"FilterCondition",
41+
"FindRequest",
3142
]

backend/src/dna/models/entity.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,15 @@ def _serialize_value(self, value: Any) -> Any:
5353
return value
5454

5555

56+
class Project(EntityBase):
57+
"""Project entity model.
58+
59+
Represents a project in the production tracking system.
60+
"""
61+
62+
name: Optional[str] = Field(default=None, description="Project name")
63+
64+
5665
class Task(EntityBase):
5766
"""Task entity model.
5867
@@ -153,6 +162,9 @@ class Version(EntityBase):
153162
frame_path: Optional[str] = Field(
154163
default=None, description="Path to frame sequence"
155164
)
165+
thumbnail: Optional[str] = Field(
166+
default=None, description="URL to thumbnail image (signed URL from ShotGrid)"
167+
)
156168
project: Optional[dict[str, Any]] = Field(
157169
default=None, description="Project information"
158170
)
@@ -200,15 +212,28 @@ def versions_none_to_list(cls, v):
200212
return v if v is not None else []
201213

202214

215+
class User(EntityBase):
216+
"""User entity model.
217+
218+
Represents a human user in the production tracking system.
219+
"""
220+
221+
name: Optional[str] = Field(default=None, description="User's full name")
222+
email: Optional[str] = Field(default=None, description="User's email address")
223+
login: Optional[str] = Field(default=None, description="User's login/username")
224+
225+
203226
# Type alias for any DNA entity
204-
DNAEntity = Union[Shot, Asset, Note, Task, Version, Playlist]
227+
DNAEntity = Union[Project, Shot, Asset, Note, Task, Version, Playlist, User]
205228

206229
# Entity type name to model class mapping
207230
ENTITY_MODELS: dict[str, type[EntityBase]] = {
231+
"project": Project,
208232
"shot": Shot,
209233
"asset": Asset,
210234
"note": Note,
211235
"task": Task,
212236
"version": Version,
213237
"playlist": Playlist,
238+
"user": User,
214239
}

backend/src/dna/models/requests.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,22 @@ class CreateNoteRequest(BaseModel):
2323
note_links: Optional[list[EntityLink]] = Field(
2424
default=None, description="Entities to link this note to"
2525
)
26+
27+
28+
class FilterCondition(BaseModel):
29+
"""A single filter condition for entity queries."""
30+
31+
field: str = Field(description="DNA field name to filter on")
32+
operator: str = Field(description="Filter operator (e.g., 'is', 'contains', 'in')")
33+
value: Any = Field(description="Value to filter by")
34+
35+
36+
class FindRequest(BaseModel):
37+
"""Request model for finding entities."""
38+
39+
entity_type: str = Field(
40+
description="DNA entity type to search (e.g., 'project', 'shot', 'version')"
41+
)
42+
filters: list[FilterCondition] = Field(
43+
default_factory=list, description="List of filter conditions"
44+
)

backend/src/dna/prodtrack_providers/prodtrack_provider_base.py

Lines changed: 65 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import os
2-
from typing import TYPE_CHECKING
2+
from typing import TYPE_CHECKING, Any
33

44
if TYPE_CHECKING:
5-
from dna.models.entity import EntityBase
5+
from dna.models.entity import EntityBase, Playlist, Project, User, Version
66

77

88
class ProdtrackProviderBase:
@@ -23,13 +23,73 @@ def add_entity(self, entity_type: str, entity: "EntityBase") -> "EntityBase":
2323
"""Add an entity to the production tracking system."""
2424
raise NotImplementedError("Subclasses must implement this method.")
2525

26+
def find(
27+
self, entity_type: str, filters: list[dict[str, Any]]
28+
) -> list["EntityBase"]:
29+
"""Find entities matching the given filters.
30+
31+
Args:
32+
entity_type: The DNA entity type to search for (e.g., 'shot', 'version')
33+
filters: List of filter conditions in DNA format
34+
35+
Returns:
36+
List of matching entities
37+
"""
38+
raise NotImplementedError("Subclasses must implement this method.")
39+
40+
def get_user_by_email(self, user_email: str) -> "User":
41+
"""Get a user by their email address.
42+
43+
Args:
44+
user_email: The email address of the user
45+
46+
Returns:
47+
User entity with name, email, and login
48+
49+
Raises:
50+
ValueError: If user is not found
51+
"""
52+
raise NotImplementedError("Subclasses must implement this method.")
53+
54+
def get_projects_for_user(self, user_email: str) -> list["Project"]:
55+
"""Get projects accessible by a user.
56+
57+
Args:
58+
user_email: The email address of the user
59+
60+
Returns:
61+
List of Project entities the user has access to
62+
"""
63+
raise NotImplementedError("Subclasses must implement this method.")
64+
65+
def get_playlists_for_project(self, project_id: int) -> list["Playlist"]:
66+
"""Get playlists for a project.
67+
68+
Args:
69+
project_id: The ID of the project
70+
71+
Returns:
72+
List of Playlist entities for the project
73+
"""
74+
raise NotImplementedError("Subclasses must implement this method.")
75+
76+
def get_versions_for_playlist(self, playlist_id: int) -> list["Version"]:
77+
"""Get versions for a playlist.
78+
79+
Args:
80+
playlist_id: The ID of the playlist
81+
82+
Returns:
83+
List of Version entities in the playlist
84+
"""
85+
raise NotImplementedError("Subclasses must implement this method.")
86+
2687

2788
def get_prodtrack_provider() -> ProdtrackProviderBase:
2889
"""Get the production tracking provider."""
2990
from dna.prodtrack_providers.shotgrid import ShotgridProvider
3091

31-
provider_type = os.getenv("PRODTRACK_PROVIDER")
92+
provider_type = os.getenv("PRODTRACK_PROVIDER", "shotgrid")
3293
if provider_type == "shotgrid":
3394
return ShotgridProvider()
34-
else:
35-
raise ValueError(f"Unknown production tracking provider: {provider_type}")
95+
raise ValueError(f"Unknown production tracking provider: {provider_type}")

0 commit comments

Comments
 (0)