From dea4e8be126b4bbcdef6da56fe68dd0ac9e2efe6 Mon Sep 17 00:00:00 2001 From: Przemyslaw Olszewski Date: Sun, 30 Aug 2026 22:16:10 +0200 Subject: [PATCH] fix: Show project documents in workspace --- diff --git a/src/collab.rs b/src/collab.rs --- a/src/collab.rs +++ b/src/collab.rs @@ -1,299 +1,329 @@ -use cynic::{MutationBuilder, QueryBuilder}; - -use crate::graphql::collab_schema as schema; -use crate::graphql::typed_collab_graphql; - -#[derive(Clone, cynic::Enum, PartialEq, Eq)] -#[cynic(schema_path = "collab-schema.graphql")] -pub enum OwnerKind { - User, - Organization, -} - -#[derive(Clone, cynic::Enum, PartialEq, Eq)] -#[cynic(schema_path = "collab-schema.graphql")] -pub enum RepositoryRole { - Source, - Infra, -} - -#[derive(Clone, cynic::Enum, PartialEq, Eq)] -#[cynic(schema_path = "collab-schema.graphql")] -pub enum ProjectKind { - Continuous, - Finite, -} - -#[derive(Clone, cynic::Enum, PartialEq, Eq)] -#[cynic(schema_path = "collab-schema.graphql")] -pub enum IssuePriority { - None, - Low, - Normal, - High, - Urgent, -} - -#[derive(Clone, cynic::QueryFragment)] -#[cynic(schema_path = "collab-schema.graphql")] -pub struct Workspace { - pub id: cynic::Id, - pub name: String, - pub slug: String, - pub default_environment: String, -} - -#[derive(Clone, cynic::QueryFragment)] -#[cynic(schema_path = "collab-schema.graphql")] -pub struct CheckoutRepository { - pub owner: String, - pub name: String, - pub role: RepositoryRole, - pub branch: String, -} - -#[derive(Clone, cynic::QueryFragment)] -#[cynic(schema_path = "collab-schema.graphql")] -pub struct Checkout { - pub repositories: Vec, -} - -#[derive(Clone, cynic::QueryFragment)] -#[cynic(schema_path = "collab-schema.graphql")] -pub struct Environment { - pub name: String, - pub default_source_branch: String, - pub infra_branch: Option, -} - -#[derive(Clone, cynic::QueryFragment)] -#[cynic(schema_path = "collab-schema.graphql")] -pub struct Project { - pub id: cynic::Id, - pub key: String, - pub name: String, - pub slug: String, - pub description: String, - pub lifecycle: String, -} - -#[derive(Clone, cynic::QueryFragment)] -#[cynic(schema_path = "collab-schema.graphql")] -pub struct Document { - pub path: String, - pub revision_id: cynic::Id, - pub content: String, -} - -#[derive(Clone, cynic::QueryFragment)] -#[cynic(schema_path = "collab-schema.graphql")] -pub struct Issue { - pub number: i32, - pub title: String, -} - -#[derive(Clone, cynic::QueryFragment)] -#[cynic(schema_path = "collab-schema.graphql")] -pub struct Version { - pub name: String, - pub slug: String, - pub state: String, -} - -#[derive(cynic::QueryFragment)] -#[cynic(schema_path = "collab-schema.graphql", graphql_type = "Query")] -struct WorkspacesResponse { - workspaces: Vec, -} - -pub async fn workspaces() -> Result, String> { - let response: WorkspacesResponse = typed_collab_graphql(QueryBuilder::build(())).await?; - Ok(response.workspaces) -} - -#[derive(cynic::QueryVariables)] -struct WorkspaceVariables { - id: cynic::Id, - workspace_id: cynic::Id, -} - -#[derive(cynic::QueryFragment)] -#[cynic( - schema_path = "collab-schema.graphql", - graphql_type = "Query", - variables = "WorkspaceVariables" -)] -struct WorkspaceResponse { - #[arguments(id: $id)] - workspace: Workspace, - #[arguments(workspaceId: $workspace_id)] - checkout: Checkout, - #[arguments(workspaceId: $workspace_id)] - environments: Vec, - #[arguments(workspaceId: $workspace_id)] - projects: Vec, - #[arguments(workspaceId: $workspace_id)] - documents: Vec, -} - -pub async fn workspace( - id: cynic::Id, -) -> Result< - ( - Workspace, - Vec, - Vec, - Vec, - Vec, - ), - String, -> { - let response: WorkspaceResponse = - typed_collab_graphql(QueryBuilder::build(WorkspaceVariables { - id: id.clone(), - workspace_id: id, - })) - .await?; - Ok(( - response.workspace, - response.checkout.repositories, - response.environments, - response.projects, - response.documents, - )) -} - -#[derive(cynic::QueryVariables)] -struct IssuesVariables { - project_id: cynic::Id, -} - -#[derive(cynic::QueryFragment)] -#[cynic( - schema_path = "collab-schema.graphql", - graphql_type = "Query", - variables = "IssuesVariables" -)] -struct IssuesResponse { - #[arguments(projectId: $project_id)] - issues: Vec, -} - -pub async fn issues(project_id: cynic::Id) -> Result, String> { - let response: IssuesResponse = - typed_collab_graphql(QueryBuilder::build(IssuesVariables { project_id })).await?; - Ok(response.issues) -} - -#[derive(cynic::QueryVariables)] -struct VersionsVariables { - project_id: cynic::Id, -} - -#[derive(cynic::QueryFragment)] -#[cynic( - schema_path = "collab-schema.graphql", - graphql_type = "Query", - variables = "VersionsVariables" -)] -struct VersionsResponse { - #[arguments(projectId: $project_id)] - versions: Vec, -} - -pub async fn versions(project_id: cynic::Id) -> Result, String> { - let response: VersionsResponse = - typed_collab_graphql(QueryBuilder::build(VersionsVariables { project_id })).await?; - Ok(response.versions) -} - -#[derive(cynic::InputObject)] -#[cynic(schema_path = "collab-schema.graphql")] -struct CreateWorkspaceInput { - owner_kind: OwnerKind, - owner_id: cynic::Id, - name: String, - slug: String, - default_environment: String, - default_source_branch: String, -} - -#[derive(cynic::QueryVariables)] -struct CreateWorkspaceVariables { - input: CreateWorkspaceInput, -} - -#[derive(cynic::QueryFragment)] -#[cynic( - schema_path = "collab-schema.graphql", - graphql_type = "Mutation", - variables = "CreateWorkspaceVariables" -)] -struct CreateWorkspaceResponse { - #[arguments(input: $input)] - create_workspace: Workspace, -} - -pub async fn create_workspace(owner_id: &str, name: &str, slug: &str) -> Result { - let response: CreateWorkspaceResponse = - typed_collab_graphql(MutationBuilder::build(CreateWorkspaceVariables { - input: CreateWorkspaceInput { - owner_kind: OwnerKind::User, - owner_id: cynic::Id::new(owner_id), - name: name.to_owned(), - slug: slug.to_owned(), - default_environment: "development".to_owned(), - default_source_branch: "main".to_owned(), - }, - })) - .await?; - Ok(response.create_workspace) -} - -#[derive(cynic::InputObject)] -#[cynic(schema_path = "collab-schema.graphql")] -struct CreateProjectInput { - workspace_id: cynic::Id, - key: String, - name: String, - slug: String, - description: String, - kind: ProjectKind, -} - -#[derive(cynic::QueryVariables)] -struct CreateProjectVariables { - input: CreateProjectInput, -} - -#[derive(cynic::QueryFragment)] -#[cynic( - schema_path = "collab-schema.graphql", - graphql_type = "Mutation", - variables = "CreateProjectVariables" -)] -struct CreateProjectResponse { - #[arguments(input: $input)] - create_project: Project, -} - -pub async fn create_project( - workspace_id: &str, - key: &str, - name: &str, - slug: &str, -) -> Result { - let response: CreateProjectResponse = - typed_collab_graphql(MutationBuilder::build(CreateProjectVariables { - input: CreateProjectInput { - workspace_id: cynic::Id::new(workspace_id), - key: key.to_owned(), - name: name.to_owned(), - slug: slug.to_owned(), - description: String::new(), - kind: ProjectKind::Continuous, - }, - })) - .await?; - Ok(response.create_project) -} +use cynic::{MutationBuilder, QueryBuilder}; + +use crate::graphql::collab_schema as schema; +use crate::graphql::typed_collab_graphql; + +#[derive(Clone, cynic::Enum, PartialEq, Eq)] +#[cynic(schema_path = "collab-schema.graphql")] +pub enum OwnerKind { + User, + Organization, +} + +#[derive(Clone, cynic::Enum, PartialEq, Eq)] +#[cynic(schema_path = "collab-schema.graphql")] +pub enum RepositoryRole { + Source, + Infra, +} + +#[derive(Clone, cynic::Enum, PartialEq, Eq)] +#[cynic(schema_path = "collab-schema.graphql")] +pub enum ProjectKind { + Continuous, + Finite, +} + +#[derive(Clone, cynic::Enum, PartialEq, Eq)] +#[cynic(schema_path = "collab-schema.graphql")] +pub enum IssuePriority { + None, + Low, + Normal, + High, + Urgent, +} + +#[derive(Clone, cynic::QueryFragment)] +#[cynic(schema_path = "collab-schema.graphql")] +pub struct Workspace { + pub id: cynic::Id, + pub name: String, + pub slug: String, + pub default_environment: String, +} + +#[derive(Clone, cynic::QueryFragment)] +#[cynic(schema_path = "collab-schema.graphql")] +pub struct CheckoutRepository { + pub owner: String, + pub name: String, + pub role: RepositoryRole, + pub branch: String, +} + +#[derive(Clone, cynic::QueryFragment)] +#[cynic(schema_path = "collab-schema.graphql")] +pub struct Checkout { + pub repositories: Vec, +} + +#[derive(Clone, cynic::QueryFragment)] +#[cynic(schema_path = "collab-schema.graphql")] +pub struct Environment { + pub name: String, + pub default_source_branch: String, + pub infra_branch: Option, +} + +#[derive(Clone, cynic::QueryFragment)] +#[cynic(schema_path = "collab-schema.graphql")] +pub struct Project { + pub id: cynic::Id, + pub key: String, + pub name: String, + pub slug: String, + pub description: String, + pub lifecycle: String, +} + +#[derive(Clone, cynic::QueryFragment)] +#[cynic(schema_path = "collab-schema.graphql")] +pub struct Document { + pub path: String, + pub revision_id: cynic::Id, + pub content: String, +} + +#[derive(Clone, cynic::QueryFragment)] +#[cynic(schema_path = "collab-schema.graphql")] +pub struct Issue { + pub number: i32, + pub title: String, +} + +#[derive(Clone, cynic::QueryFragment)] +#[cynic(schema_path = "collab-schema.graphql")] +pub struct Version { + pub name: String, + pub slug: String, + pub state: String, +} + +#[derive(cynic::QueryFragment)] +#[cynic(schema_path = "collab-schema.graphql", graphql_type = "Query")] +struct WorkspacesResponse { + workspaces: Vec, +} + +pub async fn workspaces() -> Result, String> { + let response: WorkspacesResponse = typed_collab_graphql(QueryBuilder::build(())).await?; + Ok(response.workspaces) +} + +#[derive(cynic::QueryVariables)] +struct WorkspaceVariables { + id: cynic::Id, + workspace_id: cynic::Id, +} + +#[derive(cynic::QueryFragment)] +#[cynic( + schema_path = "collab-schema.graphql", + graphql_type = "Query", + variables = "WorkspaceVariables" +)] +struct WorkspaceResponse { + #[arguments(id: $id)] + workspace: Workspace, + #[arguments(workspaceId: $workspace_id)] + checkout: Checkout, + #[arguments(workspaceId: $workspace_id)] + environments: Vec, + #[arguments(workspaceId: $workspace_id)] + projects: Vec, + #[arguments(workspaceId: $workspace_id)] + documents: Vec, +} + +pub async fn workspace( + id: cynic::Id, +) -> Result< + ( + Workspace, + Vec, + Vec, + Vec, + Vec, + ), + String, +> { + let response: WorkspaceResponse = + typed_collab_graphql(QueryBuilder::build(WorkspaceVariables { + id: id.clone(), + workspace_id: id, + })) + .await?; + Ok(( + response.workspace, + response.checkout.repositories, + response.environments, + response.projects, + response.documents, + )) +} + +#[derive(cynic::QueryVariables)] +struct DocumentsVariables { + workspace_id: cynic::Id, + project_id: Option, +} + +#[derive(cynic::QueryFragment)] +#[cynic( + schema_path = "collab-schema.graphql", + graphql_type = "Query", + variables = "DocumentsVariables" +)] +struct DocumentsResponse { + #[arguments(workspaceId: $workspace_id, projectId: $project_id)] + documents: Vec, +} + +pub async fn project_documents( + workspace_id: cynic::Id, + project_id: cynic::Id, +) -> Result, String> { + let response: DocumentsResponse = + typed_collab_graphql(QueryBuilder::build(DocumentsVariables { + workspace_id, + project_id: Some(project_id), + })) + .await?; + Ok(response.documents) +} + +#[derive(cynic::QueryVariables)] +struct IssuesVariables { + project_id: cynic::Id, +} + +#[derive(cynic::QueryFragment)] +#[cynic( + schema_path = "collab-schema.graphql", + graphql_type = "Query", + variables = "IssuesVariables" +)] +struct IssuesResponse { + #[arguments(projectId: $project_id)] + issues: Vec, +} + +pub async fn issues(project_id: cynic::Id) -> Result, String> { + let response: IssuesResponse = + typed_collab_graphql(QueryBuilder::build(IssuesVariables { project_id })).await?; + Ok(response.issues) +} + +#[derive(cynic::QueryVariables)] +struct VersionsVariables { + project_id: cynic::Id, +} + +#[derive(cynic::QueryFragment)] +#[cynic( + schema_path = "collab-schema.graphql", + graphql_type = "Query", + variables = "VersionsVariables" +)] +struct VersionsResponse { + #[arguments(projectId: $project_id)] + versions: Vec, +} + +pub async fn versions(project_id: cynic::Id) -> Result, String> { + let response: VersionsResponse = + typed_collab_graphql(QueryBuilder::build(VersionsVariables { project_id })).await?; + Ok(response.versions) +} + +#[derive(cynic::InputObject)] +#[cynic(schema_path = "collab-schema.graphql")] +struct CreateWorkspaceInput { + owner_kind: OwnerKind, + owner_id: cynic::Id, + name: String, + slug: String, + default_environment: String, + default_source_branch: String, +} + +#[derive(cynic::QueryVariables)] +struct CreateWorkspaceVariables { + input: CreateWorkspaceInput, +} + +#[derive(cynic::QueryFragment)] +#[cynic( + schema_path = "collab-schema.graphql", + graphql_type = "Mutation", + variables = "CreateWorkspaceVariables" +)] +struct CreateWorkspaceResponse { + #[arguments(input: $input)] + create_workspace: Workspace, +} + +pub async fn create_workspace(owner_id: &str, name: &str, slug: &str) -> Result { + let response: CreateWorkspaceResponse = + typed_collab_graphql(MutationBuilder::build(CreateWorkspaceVariables { + input: CreateWorkspaceInput { + owner_kind: OwnerKind::User, + owner_id: cynic::Id::new(owner_id), + name: name.to_owned(), + slug: slug.to_owned(), + default_environment: "development".to_owned(), + default_source_branch: "main".to_owned(), + }, + })) + .await?; + Ok(response.create_workspace) +} + +#[derive(cynic::InputObject)] +#[cynic(schema_path = "collab-schema.graphql")] +struct CreateProjectInput { + workspace_id: cynic::Id, + key: String, + name: String, + slug: String, + description: String, + kind: ProjectKind, +} + +#[derive(cynic::QueryVariables)] +struct CreateProjectVariables { + input: CreateProjectInput, +} + +#[derive(cynic::QueryFragment)] +#[cynic( + schema_path = "collab-schema.graphql", + graphql_type = "Mutation", + variables = "CreateProjectVariables" +)] +struct CreateProjectResponse { + #[arguments(input: $input)] + create_project: Project, +} + +pub async fn create_project( + workspace_id: &str, + key: &str, + name: &str, + slug: &str, +) -> Result { + let response: CreateProjectResponse = + typed_collab_graphql(MutationBuilder::build(CreateProjectVariables { + input: CreateProjectInput { + workspace_id: cynic::Id::new(workspace_id), + key: key.to_owned(), + name: name.to_owned(), + slug: slug.to_owned(), + description: String::new(), + kind: ProjectKind::Continuous, + }, + })) + .await?; + Ok(response.create_project) +} diff --git a/src/pages/workspace.rs b/src/pages/workspace.rs --- a/src/pages/workspace.rs +++ b/src/pages/workspace.rs @@ -1,105 +1,127 @@ -use leptos::prelude::*; -use leptos_router::components::A; -use leptos_router::hooks::use_params_map; - -use crate::collab::{issues, versions, workspace, Issue, Project, Version}; -use crate::components::nav::Nav; - -use super::workspace_forms::{ - AttachRepositoryForm, DocumentForm, EnvironmentForm, IssueForm, ProjectForm, VersionForm, -}; - -#[derive(Clone)] -struct WorkspaceData { - workspace: crate::collab::Workspace, - repositories: Vec, - environments: Vec, - projects: Vec<(Project, Vec, Vec)>, - documents: Vec, -} - -async fn load_workspace(id: &str) -> Result { - let (workspace, repositories, environments, projects, documents) = - workspace(cynic::Id::new(id)).await?; - let mut projects_with_issues = Vec::with_capacity(projects.len()); - for project in projects { - let project_issues = issues(project.id.clone()).await?; - let project_versions = versions(project.id.clone()).await?; - projects_with_issues.push((project, project_issues, project_versions)); - } - Ok(WorkspaceData { - workspace, - repositories, - environments, - projects: projects_with_issues, - documents, - }) -} - -#[component] -pub fn WorkspacePage() -> impl IntoView { - let params = use_params_map(); - let refresh = RwSignal::new(0_u64); - let data = LocalResource::new(move || { - refresh.get(); - let id = params.read().get("id").unwrap_or_default(); - async move { load_workspace(&id).await } - }); - - view! { -
- -
- "Loading workspace…"

}> - {move || data.get().map(|result| match &*result { - Ok(data) => { - let workspace = data.workspace.clone(); - let workspace_id = workspace.id.inner().to_owned(); - let projects = data.projects.clone(); - let documents = data.documents.clone(); - let repositories = data.repositories.clone(); - let environments = data.environments.clone(); - view! { -
-

{workspace.slug}

{workspace.name}

- {workspace.default_environment} -
-
-
-
-

"Projects"

{projects.len()}
-
    {projects.clone().into_iter().map(|(project, issues, versions)| view! { -
  • -

    {format!("{} · {}", project.key, project.name)}

    {project.description}

    {project.slug}

    {project.lifecycle}
    -
      {issues.into_iter().map(|issue| view! {
    • {format!("#{}", issue.number)}{issue.title}
    • }).collect_view()}
    - -

    "Versions"

      {versions.into_iter().map(|version| view! {
    • {format!("{} ({}) · {}", version.name, version.slug, version.state)}
    • }).collect_view()}
    -
  • - }).collect_view()}
-
-
-

"Docs"

{documents.len()}
-
    {documents.into_iter().map(|document| view! {
  • {document.path}

    {document.revision_id.inner().to_owned()}
    {document.content}
  • }).collect_view()}
-
-
- -
- }.into_any() - } - Err(message) => view! {

{message.clone()}

}.into_any(), - })} -
-
-
- } -} +use leptos::prelude::*; +use leptos_router::components::A; +use leptos_router::hooks::use_params_map; + +use crate::collab::{ + issues, project_documents, versions, workspace, Document, Issue, Project, Version, +}; +use crate::components::nav::Nav; + +use super::workspace_forms::{ + AttachRepositoryForm, DocumentForm, EnvironmentForm, IssueForm, ProjectForm, VersionForm, +}; + +#[derive(Clone)] +struct WorkspaceData { + workspace: crate::collab::Workspace, + repositories: Vec, + environments: Vec, + projects: Vec, + documents: Vec, +} + +#[derive(Clone)] +struct ProjectData { + project: Project, + issues: Vec, + versions: Vec, + documents: Vec, +} + +async fn load_workspace(id: &str) -> Result { + let (workspace, repositories, environments, projects, documents) = + workspace(cynic::Id::new(id)).await?; + let mut projects_with_issues = Vec::with_capacity(projects.len()); + for project in projects { + let project_issues = issues(project.id.clone()).await?; + let project_versions = versions(project.id.clone()).await?; + let documents = project_documents(workspace.id.clone(), project.id.clone()).await?; + projects_with_issues.push(ProjectData { + project, + issues: project_issues, + versions: project_versions, + documents, + }); + } + Ok(WorkspaceData { + workspace, + repositories, + environments, + projects: projects_with_issues, + documents, + }) +} + +#[component] +pub fn WorkspacePage() -> impl IntoView { + let params = use_params_map(); + let refresh = RwSignal::new(0_u64); + let data = LocalResource::new(move || { + refresh.get(); + let id = params.read().get("id").unwrap_or_default(); + async move { load_workspace(&id).await } + }); + + view! { +
+ +
+ "Loading workspace…"

}> + {move || data.get().map(|result| match &*result { + Ok(data) => { + let workspace = data.workspace.clone(); + let workspace_id = workspace.id.inner().to_owned(); + let projects = data.projects.clone(); + let documents = data.documents.clone(); + let repositories = data.repositories.clone(); + let environments = data.environments.clone(); + view! { +
+

{workspace.slug}

{workspace.name}

+ {workspace.default_environment} +
+
+
+
+

"Projects"

{projects.len()}
+
    {projects.clone().into_iter().map(|data| { + let project = data.project; + let issues = data.issues; + let versions = data.versions; + let documents = data.documents; + view! { +
  • +

    {format!("{} · {}", project.key, project.name)}

    {project.description}

    {project.slug}

    {project.lifecycle}
    +
      {issues.into_iter().map(|issue| view! {
    • {format!("#{}", issue.number)}{issue.title}
    • }).collect_view()}
    + +

    "Versions"

      {versions.into_iter().map(|version| view! {
    • {format!("{} ({}) · {}", version.name, version.slug, version.state)}
    • }).collect_view()}
    +

    "Project docs"

    {documents.len()}
      {documents.into_iter().map(|document| view! {
    • {document.path}

      {document.revision_id.inner().to_owned()}
      {document.content}
    • }).collect_view()}
    +
  • + }}).collect_view()}
+
+
+

"Docs"

{documents.len()}
+
    {documents.into_iter().map(|document| view! {
  • {document.path}

    {document.revision_id.inner().to_owned()}
    {document.content}
  • }).collect_view()}
+
+
+ +
+ }.into_any() + } + Err(message) => view! {

{message.clone()}

}.into_any(), + })} +
+
+
+ } +} -- SynCode