diff --git a/src/timessquare/domain/githubtree.py b/src/timessquare/domain/githubtree.py index 76e07a7b..4fd5bc94 100644 --- a/src/timessquare/domain/githubtree.py +++ b/src/timessquare/domain/githubtree.py @@ -2,11 +2,9 @@ from __future__ import annotations -from dataclasses import dataclass +from dataclasses import dataclass, field from enum import Enum -from typing import List - -from pydantic import BaseModel, Field +from typing import List, Optional class GitHubNodeType(str, Enum): @@ -25,217 +23,223 @@ class GitHubNodeType(str, Enum): """Page inside a GitHub repository.""" -class GitHubNode(BaseModel): +@dataclass +class GitHubNode: """A node in the GitHub contents tree.""" - node_type: GitHubNodeType = Field( - title="Node type", - description=( - "Indicates whether this a GitHub owner (user or organization), " - "repository, directory in a repositiory, or a page itself." - ), - ) - - title: str = Field(title="Display title") - - path: str = Field( - title="Hierarchical path", - description=( - "The page is POSIX-path formatted without a preceeding or " - "trailing slash. The first path element is always the owner, " - "followed by the repository name, directory, and page name as " - "necessary." - ), - example="lsst-sqre/times-square-demo/matplotlib/gaussian2d", - ) - - contents: List[GitHubNode] = Field( - title="Nodes contained within this node.", - description="For 'page' nodes, this is an empty list.", - default_factory=list, - ) + node_type: GitHubNodeType + """The type of path object in the node.""" - @property - def path_segments(self) -> List[str]: - return self.path.split("/") + path_segments: List[str] + """The segments in the path (i.e. the name of this page, or + this directory) up to this point. - def insert_input(self, tree_input: GitHubTreeInput) -> None: - n = len(self.path_segments) - if n == len(tree_input.path_segments): - # input is a direct child of this node - self.contents.append(tree_input.to_node()) - else: - # find child that contains input - for child in self.contents: - if (len(child.path_segments) >= n + 1) and ( - child.path_segments[n] == tree_input.path_segments[n] - ): - child.insert_input(tree_input) - return - # Create a new child node because the necessary one doesn't - # exist - if n == 1: - node_type = GitHubNodeType.repo - else: - node_type = GitHubNodeType.directory - child = GitHubNode( - node_type=node_type, - title=tree_input.path_segments[n], - path="/".join(tree_input.path_segments[: n + 1]), - contents=[], - ) - child.insert_input(tree_input) - self.contents.append(child) + Path segments are always ordered: + 1. owner + 2. repo + 3. directory or directories, as necessary + 4. page file stem (i.e. filename without extension) + """ -@dataclass -class GitHubTreeInput: - """A domain class used to aid construction of the GitHub contents tree - from the original SQL storage of pages. + title: str + """Presentational title for this node.""" - This class is used by `PageStore.get_github_tree`; `GitHubNode` is the - public product. + github_commit: Optional[str] = None + """The commit SHA if this tree is for a specific commit (a PR preview) + instead of corresponding to the default branch of the repository. """ - path_segments: List[str] - - stem: str + contents: List[GitHubNode] = field(default_factory=list) - title: str + @property + def squareone_path(self) -> str: + """Path to the node in Squareone. + + - If `github_commit` is None, the URL path is relative to + ``/times-square/github/`` (not included. + - If the node contains a non-None `github_commit`, + the path is relative to ``/times-square/github-pr/`` (not included). + """ + if self.github_commit is None: + # a path corresponding to the default branch (i.e. the "live view + # github-backed pages) + return "/".join(self.path_segments) + else: + print(f"commit is not none: {self.github_commit}") + # a path corresponding to the commit view of a repository, + # for PR previews. + # formatted as owner/repo/commit/dirname/filestem + return ( + f"{self.path_segments[0]}/{self.path_segments[1]}/" + f"{self.github_commit}/{'/'.join(self.path_segments[2:])}" + ) @classmethod - def from_sql_row( - cls, - github_owner: str, - github_repo: str, - path_prefix: str, - title: str, - path_stem: str, - ) -> GitHubTreeInput: - path_segments = [github_owner, github_repo] - if path_prefix: - path_segments.extend(path_prefix.split("/")) - - return cls(path_segments=path_segments, stem=path_stem, title=title) - - def to_node(self) -> GitHubNode: - return GitHubNode( - node_type=GitHubNodeType.page, - title=self.title, - path="/".join(self.path_segments + [self.stem]), + def create_with_repo_root( + cls, results: List[GitHubTreeQueryResult] + ) -> GitHubNode: + """Create a tree with this root-node being the first repository in + the results. + + This is appropriate for creating trees for GitHub PR previews. + """ + root_path_segment = [results[0].github_owner, results[0].github_repo] + root = cls( + node_type=GitHubNodeType.repo, + path_segments=root_path_segment, + github_commit=results[0].github_commit, + title=results[0].github_repo, contents=[], ) + for result in results: + root.insert_node(result) + return root - -class GitHubPrNode(BaseModel): - """A node in a GitHub PRs contents tree.""" - - node_type: GitHubNodeType = Field( - title="Node type", - description=( - "Indicates whether this a GitHub owner (user or organization), " - "repository, directory in a repositiory, or a page itself." - ), - ) - - title: str = Field(title="Display title") - - path: str = Field( - title="Squareone URL path", - description=( - "The path reflexts a Squareone /times-square/ URL path for a page " - "or other tree node, without a preceeding or trailing slash. " - "For a normal page, the format is `owner/repo/dirname/page`. " - "For a PR preview page, the format is " - "`owner/repo/commit/dirname/page`." - ), - example=( - "lsst-sqre/times-square-demo/e35e1d5c485531ba9e99081c52dbdc5579e0" - "0556/matplotlib/gaussian2d", - ), - ) - - contents: List[GitHubPrNode] = Field( - title="Nodes contained within this node.", - description="For 'page' nodes, this is an empty list.", - default_factory=list, - ) - - @property - def path_segments(self) -> List[str]: - if self.path == "": - return [] + @classmethod + def create_with_owner_root( + cls, results: List[GitHubTreeQueryResult] + ) -> GitHubNode: + """Create a tree with the root-node being the first GitHub owner in + the results. + + This is appropriate for creating trees for the default branch views + of GitHub-backed pages. + """ + root_path_segment = [results[0].github_owner] + root = cls( + node_type=GitHubNodeType.owner, + path_segments=root_path_segment, + title=results[0].github_owner, + github_commit=results[0].github_commit, + contents=[], + ) + for result in results: + root.insert_node(result) + return root + + def insert_node(self, result: GitHubTreeQueryResult) -> None: + """Insert an SQL page result as a child (direct, or not) of the + current node. + """ + if self.node_type == GitHubNodeType.owner: + self._insert_node_from_owner(result) + elif self.node_type == GitHubNodeType.repo: + self._insert_node_from_repo(result) + elif self.node_type == GitHubNodeType.directory: + self._insert_node_from_directory(result) else: - return self.path.split("/") - - def insert_input(self, tree_input: GitHubPrTreeInput) -> None: - n = len(self.path_segments) - if n == len(tree_input.path_segments): - # input is a direct child of this node - self.contents.append(tree_input.to_node()) + raise ValueError("Cannot insert a node into a page") + + def _insert_node_from_owner(self, result: GitHubTreeQueryResult) -> None: + # Try to insert node into an existing repository node + for repo_node in self.contents: + if repo_node.path_segments[1] == result.github_repo: + repo_node.insert_node(result) + return + + # Create a repo node since one doesn't already exist + repo_node = GitHubNode( + node_type=GitHubNodeType.repo, + path_segments=result.path_segments[:2], + title=result.path_segments[1], + github_commit=result.github_commit, + contents=[], + ) + self.contents.append(repo_node) + repo_node.insert_node(result) + + def _insert_node_from_repo(self, result: GitHubTreeQueryResult) -> None: + if len(result.path_segments) == 3: + # direct child of this node + self.contents.append( + GitHubNode( + node_type=GitHubNodeType.page, + path_segments=result.path_segments, + title=result.title, + github_commit=result.github_commit, + contents=[], + ) + ) + return else: - # find child that contains input - for child in self.contents: - if (len(child.path_segments) >= n + 1) and ( - child.path_segments[n] == tree_input.path_segments[n] + # Find existing directory containing this page + for child_node in self.contents: + n = len(child_node.path_segments) + if ( + child_node.node_type == GitHubNodeType.directory + and child_node.path_segments == result.path_segments[:n] ): - child.insert_input(tree_input) + child_node.insert_node(result) return - # Create a new child directory node because the necessary one - # doesn't exist - child = GitHubPrNode( + # Create a directory node + dir_node = GitHubNode( + node_type=GitHubNodeType.directory, + path_segments=result.path_segments[ + : len(self.path_segments) + 1 + ], + title=result.path_segments[len(self.path_segments)], + github_commit=result.github_commit, + contents=[], + ) + self.contents.append(dir_node) + dir_node.insert_node(result) + + def _insert_node_from_directory( + self, result: GitHubTreeQueryResult + ) -> None: + self_segment_count = len(self.path_segments) + input_segment_count = len(result.path_segments) + + if input_segment_count == self_segment_count + 1: + # a direct child of this directory + self.contents.append( + GitHubNode( + node_type=GitHubNodeType.page, + path_segments=result.path_segments, + title=result.title, + github_commit=result.github_commit, + contents=[], + ) + ) + else: + # Create a directory node + dir_node = GitHubNode( node_type=GitHubNodeType.directory, - title=tree_input.path_segments[n], - path="/".join(tree_input.path_segments[: n + 1]), + path_segments=result.path_segments[: self_segment_count + 1], + title=result.path_segments[self_segment_count], + github_commit=result.github_commit, contents=[], ) - child.insert_input(tree_input) - self.contents.append(child) + self.contents.append(dir_node) + dir_node.insert_node(result) @dataclass -class GitHubPrTreeInput: - """A domain class used to aid contruction of a GitHub PR's contents tree +class GitHubTreeQueryResult: + """A domain class used to aid construction of the GitHub contents tree from the original SQL storage of pages. """ - path_segments: List[str] + # The order of these attributes matches the order of the sql query + # in timessquare.storage.page. - stem: str + github_owner: str - commit: str + github_repo: str - owner: str + github_commit: Optional[str] - repo: str + path_prefix: str title: str - @classmethod - def from_sql_row( - cls, - github_owner: str, - github_repo: str, - github_commit: str, - path_prefix: str, - title: str, - path_stem: str, - ) -> GitHubPrTreeInput: - path_segments = path_prefix.split("/") if path_prefix else [] - - return cls( - path_segments=path_segments, - stem=path_stem, - title=title, - commit=github_commit, - owner=github_owner, - repo=github_owner, - ) + path_stem: str - def to_node(self) -> GitHubPrNode: - return GitHubPrNode( - node_type=GitHubNodeType.page, - title=self.title, - path="/".join(self.path_segments + [self.stem]), - contents=[], - ) + @property + def path_segments(self) -> List[str]: + segments: List[str] = [self.github_owner, self.github_repo] + if len(self.path_prefix) > 0: + segments.extend(self.path_prefix.split("/")) + segments.append(self.path_stem) + return segments diff --git a/src/timessquare/handlers/v1/handlers.py b/src/timessquare/handlers/v1/handlers.py index e1b5d65f..64400a66 100644 --- a/src/timessquare/handlers/v1/handlers.py +++ b/src/timessquare/handlers/v1/handlers.py @@ -13,8 +13,7 @@ ) from .models import ( - GitHubPrTreeRoot, - GitHubTreeRoot, + GitHubContentsRoot, HtmlStatus, Index, Page, @@ -320,11 +319,11 @@ async def get_page_html_status( "/github", summary="Get a tree of GitHub-backed pages", name="get_github_tree", - response_model=GitHubTreeRoot, + response_model=GitHubContentsRoot, ) async def get_github_tree( context: RequestContext = Depends(context_dependency), -) -> GitHubTreeRoot: +) -> GitHubContentsRoot: """Get the tree of GitHub-backed pages. This endpoint is primarily intended to be used by Squareone to power @@ -335,7 +334,7 @@ async def get_github_tree( page_service = context.page_service async with context.session.begin(): github_tree = await page_service.get_github_tree() - return GitHubTreeRoot.from_tree(tree=github_tree) + return GitHubContentsRoot.from_tree(tree=github_tree) @v1_router.get( @@ -368,14 +367,14 @@ async def get_github_page( "/github-pr/{owner}/{repo}/{commit}", summary="Get a tree of GitHub PR preview pages", name="get_github_pr_tree", - response_model=GitHubPrTreeRoot, + response_model=GitHubContentsRoot, ) async def get_github_pr_tree( owner: str = github_owner_parameter, repo: str = github_repo_parameter, commit: str = pr_commit_parameter, context: RequestContext = Depends(context_dependency), -) -> GitHubPrTreeRoot: +) -> GitHubContentsRoot: """Get the tree of GitHub-backed pages for a pull request. This endpoint is primarily intended to be used by Squareone to power @@ -387,7 +386,7 @@ async def get_github_pr_tree( github_tree = await page_service.get_github_pr_tree( owner=owner, repo=repo, commit=commit ) - return GitHubPrTreeRoot.from_tree( + return GitHubContentsRoot.from_tree( tree=github_tree, owner=owner, repo=repo, diff --git a/src/timessquare/handlers/v1/models.py b/src/timessquare/handlers/v1/models.py index 0f13c92a..92c89a1c 100644 --- a/src/timessquare/handlers/v1/models.py +++ b/src/timessquare/handlers/v1/models.py @@ -11,7 +11,7 @@ from pydantic import AnyHttpUrl, BaseModel, EmailStr, Field from safir.metadata import Metadata as SafirMetadata -from timessquare.domain.githubtree import GitHubNode, GitHubPrNode +from timessquare.domain.githubtree import GitHubNode, GitHubNodeType from timessquare.domain.nbhtml import NbHtmlModel from timessquare.domain.page import PageModel, PageSummaryModel, PersonModel @@ -21,7 +21,7 @@ class Index(BaseModel): metadata: SafirMetadata = Field(..., title="Package metadata") - api_docs: AnyHttpUrl = Field(..., tile="Browsable API documentation") + api_docs: AnyHttpUrl = Field(..., title="Browsable API documentation") page_name_field = Field( @@ -409,28 +409,90 @@ class PostPageRequest(BaseModel): cache_ttl: Optional[int] = page_cache_ttl_field -class GitHubTreeRoot(BaseModel): - """The GitHub-backed pages, organized hierarchically.""" +class GitHubContentsNode(BaseModel): + """Information about a node in a GitHub contents tree.""" - contents: List[GitHubNode] + node_type: GitHubNodeType = Field( + ..., + title="Node type", + description="Type of node in the GitHub contents tree.", + example="page", + ) + + path: str = Field( + ..., + title="Path", + description="Squareone URL path", + example="lsst-sqre/times-square-demo/demo", + ) + + title: str = Field( + ..., + title="Title", + description="Presentation title of the node.", + example="Demo", + ) + + contents: List[GitHubContentsNode] = Field( + ..., title="Contents", description="Children of this node" + ) @classmethod - def from_tree(cls, *, tree: List[GitHubNode]) -> GitHubTreeRoot: - return cls(contents=tree) + def from_domain_model(cls, node: GitHubNode) -> GitHubContentsNode: + return cls( + node_type=node.node_type, + path=node.squareone_path, + title=node.title, + contents=[cls.from_domain_model(n) for n in node.contents], + ) -class GitHubPrTreeRoot(BaseModel): +class GitHubContentsRoot(BaseModel): + """The tree of GitHub contents.""" - contents: List[GitHubPrNode] + contents: List[GitHubContentsNode] = Field( + title="Contents", description="Content nodes" + ) - owner: str + owner: Optional[str] = Field( + None, + title="GitHub owner", + description=( + "The GitHub owner for this tree, if this tree applies to a single " + "GitHub owner." + ), + ) - repo: str + repo: Optional[str] = Field( + None, + title="GitHub repo", + description=( + "The GitHub repo for this tree, if this tree applies to a single " + "GitHub repo." + ), + ) - commit: str + commit: Optional[str] = Field( + None, + title="GitHub commit", + description=( + "The GitHub commit for this tree, if this tree is specific to a " + "commit, such as for a PR preview." + ), + ) @classmethod def from_tree( - cls, *, tree: List[GitHubPrNode], owner: str, repo: str, commit: str - ) -> GitHubPrTreeRoot: - return cls(contents=tree, owner=owner, repo=repo, commit=commit) + cls, + *, + tree: List[GitHubNode], + owner: Optional[str] = None, + repo: Optional[str] = None, + commit: Optional[str] = None, + ) -> GitHubContentsRoot: + return cls( + contents=[GitHubContentsNode.from_domain_model(n) for n in tree], + owner=owner, + repo=repo, + commit=commit, + ) diff --git a/src/timessquare/services/page.py b/src/timessquare/services/page.py index e45ef99d..6f95c64a 100644 --- a/src/timessquare/services/page.py +++ b/src/timessquare/services/page.py @@ -10,7 +10,7 @@ from structlog.stdlib import BoundLogger from timessquare.config import config -from timessquare.domain.githubtree import GitHubNode, GitHubPrNode +from timessquare.domain.githubtree import GitHubNode from timessquare.domain.nbhtml import NbDisplaySettings, NbHtmlKey, NbHtmlModel from timessquare.domain.noteburst import ( NoteburstApi, @@ -194,7 +194,7 @@ async def get_github_pr_tree( owner: str, repo: str, commit: str, - ) -> List[GitHubPrNode]: + ) -> List[GitHubNode]: """Get the tree of GitHub-backed pages for a specific pull request.""" return await self._page_store.get_github_pr_tree( owner=owner, repo=repo, commit=commit diff --git a/src/timessquare/storage/page.py b/src/timessquare/storage/page.py index ec7a4cdf..fbafa830 100644 --- a/src/timessquare/storage/page.py +++ b/src/timessquare/storage/page.py @@ -12,9 +12,7 @@ from timessquare.domain.githubtree import ( GitHubNode, GitHubNodeType, - GitHubPrNode, - GitHubPrTreeInput, - GitHubTreeInput, + GitHubTreeQueryResult, ) from timessquare.domain.page import ( PageModel, @@ -275,9 +273,10 @@ async def get_github_tree(self) -> List[GitHubNode]: async def _generate_node_for_owner(self, owner_name: str) -> GitHubNode: statement = ( - select( + select( # order matches GitHubTreeQueryResult SqlPage.github_owner, SqlPage.github_repo, + SqlPage.github_commit, SqlPage.repository_display_path_prefix, SqlPage.title, SqlPage.repository_path_stem, @@ -295,26 +294,35 @@ async def _generate_node_for_owner(self, owner_name: str) -> GitHubNode: result = await self._session.execute(statement) tree_inputs = [ - GitHubTreeInput.from_sql_row(*row) for row in result.all() + GitHubTreeQueryResult( + github_owner=row[0], + github_repo=row[1], + github_commit=row[2], + path_prefix=row[3], + title=row[4], + path_stem=row[5], + ) + for row in result.all() ] owner_node = GitHubNode( node_type=GitHubNodeType.owner, title=owner_name, - path=owner_name, + path_segments=[owner_name], + github_commit=None, contents=[], ) for tree_input in tree_inputs: - owner_node.insert_input(tree_input) + owner_node.insert_node(tree_input) return owner_node async def get_github_pr_tree( self, *, owner: str, repo: str, commit: str - ) -> List[GitHubPrNode]: + ) -> List[GitHubNode]: """Get the tree of GitHub-backed pages for a pull request commit.""" statement = ( - select( + select( # order matches GitHubTreeQueryResult SqlPage.github_owner, SqlPage.github_repo, SqlPage.github_commit, @@ -333,17 +341,29 @@ async def get_github_pr_tree( result = await self._session.execute(statement) tree_inputs = [ - GitHubPrTreeInput.from_sql_row(*row) for row in result.all() + GitHubTreeQueryResult( + github_owner=row[0], + github_repo=row[1], + github_commit=row[2], + path_prefix=row[3], + title=row[4], + path_stem=row[5], + ) + for row in result.all() ] if len(tree_inputs) == 0: return [] # Create a root node for the repo to use its insert_input method # for sorting the tree and creating directories as needed - repo_node = GitHubPrNode( - node_type=GitHubNodeType.repo, title=repo, path=repo, contents=[] + repo_node = GitHubNode( + node_type=GitHubNodeType.repo, + path_segments=[owner, repo], + github_commit=commit, + title=repo, + contents=[], ) for tree_input in tree_inputs: - repo_node.insert_input(tree_input) + repo_node.insert_node(tree_input) - return repo_node.contents[0].contents + return repo_node.contents diff --git a/tests/domain/githubtree_test.py b/tests/domain/githubtree_test.py index c5192eaf..5117fffd 100644 --- a/tests/domain/githubtree_test.py +++ b/tests/domain/githubtree_test.py @@ -5,103 +5,147 @@ from timessquare.domain.githubtree import ( GitHubNode, GitHubNodeType, - GitHubPrNode, - GitHubPrTreeInput, - GitHubTreeInput, + GitHubTreeQueryResult, ) def test_githubtreenode() -> None: - """Test the construction of a GitHubNode from mock GitHubTreeInputs.""" + """Test the construction of a GitHubNode from mock GitHubTreeInputs with + a null GitHub commit. + """ mock_sql_results = [ - { - "github_owner": "lsst-sqre", - "github_repo": "times-square-demo", - "path_prefix": "", - "title": "Alpha", - "path_stem": "alpha", - }, - { - "github_owner": "lsst-sqre", - "github_repo": "times-square-demo", - "path_prefix": "", - "title": "Beta", - "path_stem": "beta", - }, - { - "github_owner": "lsst-sqre", - "github_repo": "times-square-demo", - "path_prefix": "subdir", - "title": "Gamma", - "path_stem": "gamma", - }, + [ + "lsst-sqre", + "times-square-demo", + None, + "", + "Alpha", + "alpha", + ], + [ + "lsst-sqre", + "times-square-demo", + None, + "", + "Beta", + "beta", + ], + [ + "lsst-sqre", + "times-square-demo", + None, + "subdir", + "Gamma", + "gamma", + ], ] tree_inputs = [ - GitHubTreeInput.from_sql_row(**page) for page in mock_sql_results + GitHubTreeQueryResult( + github_owner=str(row[0]), + github_repo=str(row[1]), + github_commit=row[2], + path_prefix=str(row[3]), + title=str(row[4]), + path_stem=str(row[5]), + ) + for row in mock_sql_results ] owner_node = GitHubNode( node_type=GitHubNodeType.owner, title="lsst-sqre", - path="lsst-sqre", + path_segments=["lsst-sqre"], + github_commit=None, contents=[], ) for tree_input in tree_inputs: - owner_node.insert_input(tree_input) + owner_node.insert_node(tree_input) assert owner_node.title == "lsst-sqre" assert owner_node.contents[0].title == "times-square-demo" repo_node = owner_node.contents[0] + assert repo_node.node_type == GitHubNodeType.repo + print(owner_node) + assert repo_node.contents[0].title == "Alpha" + assert repo_node.contents[0].squareone_path == ( + "lsst-sqre/times-square-demo/alpha" + ) + assert repo_node.contents[1].title == "Beta" + assert repo_node.contents[2].title == "subdir" dir_node = repo_node.contents[2] assert dir_node.contents[0].title == "Gamma" + assert dir_node.contents[0].squareone_path == ( + "lsst-sqre/times-square-demo/subdir/gamma" + ) def test_githubprtreenode() -> None: """Test the construction of a GitHubPrNode from mock inputs.""" mock_sql_results = [ - { - "github_owner": "lsst-sqre", - "github_repo": "times-square-demo", - "github_commit": "e35e1d5c485531ba9e99081c52dbdc5579e00556", - "path_prefix": "", - "title": "Alpha", - "path_stem": "alpha", - }, - { - "github_owner": "lsst-sqre", - "github_repo": "times-square-demo", - "github_commit": "e35e1d5c485531ba9e99081c52dbdc5579e00556", - "path_prefix": "", - "title": "Beta", - "path_stem": "beta", - }, - { - "github_owner": "lsst-sqre", - "github_repo": "times-square-demo", - "github_commit": "e35e1d5c485531ba9e99081c52dbdc5579e00556", - "path_prefix": "subdir", - "title": "Gamma", - "path_stem": "gamma", - }, + [ + "lsst-sqre", + "times-square-demo", + "e35e1d5c485531ba9e99081c52dbdc5579e00556", + "", + "Alpha", + "alpha", + ], + [ + "lsst-sqre", + "times-square-demo", + "e35e1d5c485531ba9e99081c52dbdc5579e00556", + "", + "Beta", + "beta", + ], + [ + "lsst-sqre", + "times-square-demo", + "e35e1d5c485531ba9e99081c52dbdc5579e00556", + "subdir", + "Gamma", + "gamma", + ], ] tree_inputs = [ - GitHubPrTreeInput.from_sql_row(**page) for page in mock_sql_results + GitHubTreeQueryResult( + github_owner=str(row[0]), + github_repo=str(row[1]), + github_commit=row[2], + path_prefix=str(row[3]), + title=str(row[4]), + path_stem=str(row[5]), + ) + for row in mock_sql_results ] - repo_node = GitHubPrNode( + repo_node = GitHubNode( node_type=GitHubNodeType.repo, title="times-square-demo", - path="", + path_segments=["lsst-sqre", "times-square-demo"], + github_commit="e35e1d5c485531ba9e99081c52dbdc5579e00556", contents=[], ) for tree_input in tree_inputs: - repo_node.insert_input(tree_input) + repo_node.insert_node(tree_input) assert repo_node.contents[0].title == "Alpha" + assert repo_node.contents[0].squareone_path == ( + "lsst-sqre/times-square-demo/e35e1d5c485531ba9e99081c52dbdc5579e00556" + "/alpha" + ) assert repo_node.contents[1].title == "Beta" + assert repo_node.contents[1].squareone_path == ( + "lsst-sqre/times-square-demo/e35e1d5c485531ba9e99081c52dbdc5579e00556" + "/beta" + ) assert repo_node.contents[2].title == "subdir" dir_node = repo_node.contents[2] assert dir_node.contents[0].title == "Gamma" + assert dir_node.contents[0].squareone_path == ( + "lsst-sqre/times-square-demo/e35e1d5c485531ba9e99081c52dbdc5579e00556" + "/subdir/gamma" + )