Skip to content

Commit

Permalink
feat: project archiving and deletion (#2585)
Browse files Browse the repository at this point in the history
Adds deleteProject and archiveProject mutations. No deletion currently happens, we simply mark the project as archived and don't return it from the projects field on Query.
  • Loading branch information
axiomofjoy authored Mar 14, 2024
1 parent b82e787 commit 121f904
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 8 deletions.
2 changes: 2 additions & 0 deletions app/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,8 @@ type Mutation {
Given a list of clusters, export the corresponding data subset in Parquet format. File name is optional, but if specified, should be without file extension. By default the exported file name is current timestamp.
"""
exportClusters(clusters: [ClusterInput!]!, fileName: String): ExportedFile!
deleteProject(id: GlobalID!): Query!
archiveProject(id: GlobalID!): Query!
}

"""A node in the graph with a globally unique ID"""
Expand Down
8 changes: 8 additions & 0 deletions src/phoenix/core/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ class Project:
def __init__(self) -> None:
self._spans = _Spans()
self._evals = _Evals()
self._is_archived = False

@property
def last_updated_at(self) -> Optional[datetime]:
Expand Down Expand Up @@ -192,6 +193,13 @@ def get_document_evaluation_scores(
def export_evaluations(self) -> List[Evaluations]:
return self._evals.export_evaluations()

def archive(self) -> None:
self._is_archived = True

@property
def is_archived(self) -> bool:
return self._is_archived


class _Spans:
def __init__(self) -> None:
Expand Down
22 changes: 19 additions & 3 deletions src/phoenix/core/traces.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,26 @@ def get_project(self, project_name: str) -> Optional["Project"]:
with self._lock:
return self._projects.get(project_name)

def get_projects(self) -> Iterator[Tuple[str, "Project"]]:
def get_projects(self) -> Iterator[Tuple[int, str, "Project"]]:
with self._lock:
projects = tuple(self._projects.items())
yield from projects
for project_id, (project_name, project) in enumerate(self._projects.items()):
if project.is_archived:
continue
yield project_id, project_name, project

def archive_project(self, id: int) -> Optional["Project"]:
with self._lock:
active_projects = {
project_id: project
for project_id, _, project in self.get_projects()
if not project.is_archived
}
if len(active_projects) <= 1:
return None
if project := active_projects.get(id):
project.archive()
return project
return None

def put(
self,
Expand Down
32 changes: 27 additions & 5 deletions src/phoenix/server/api/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ def projects(
[]
if (traces := info.context.traces) is None
else [
Project(id_attr=i, name=name, project=project)
for i, (name, project) in enumerate(traces.get_projects())
Project(id_attr=project_id, name=project_name, project=project)
for project_id, project_name, project in traces.get_projects()
]
)
return connection_from_list(data=data, args=args)
Expand Down Expand Up @@ -86,8 +86,11 @@ def node(self, id: GlobalID, info: Info[Context, None]) -> Node:
return to_gql_embedding_dimension(node_id, embedding_dimension)
elif type_name == "Project":
if (traces := info.context.traces) is not None:
projects = list(traces.get_projects())
if node_id < len(projects):
projects = {
project_id: (project_name, project)
for project_id, project_name, project in traces.get_projects()
}
if node_id in projects:
name, project = projects[node_id]
return Project(id_attr=node_id, name=name, project=project)
raise Exception(f"Unknown project: {id}")
Expand Down Expand Up @@ -224,7 +227,26 @@ def hdbscan_clustering(


@strawberry.type
class Mutation(ExportEventsMutation): ...
class Mutation(ExportEventsMutation):
@strawberry.mutation
def delete_project(self, info: Info[Context, None], id: GlobalID) -> Query:
if (traces := info.context.traces) is None:
return Query()
type_name, node_id = from_global_id(str(id))
if type_name != "Project":
return Query()
traces.archive_project(node_id)
return Query()

@strawberry.mutation
def archive_project(self, info: Info[Context, None], id: GlobalID) -> Query:
if (traces := info.context.traces) is None:
return Query()
type_name, node_id = from_global_id(str(id))
if type_name != "Project":
return Query()
traces.archive_project(node_id)
return Query()


schema = strawberry.Schema(query=Query, mutation=Mutation)

0 comments on commit 121f904

Please sign in to comment.