ListWorkspaceGraphLinks returns every edge for the workspace graph view: all item_links rows (parent / blocks / implements / related) plus resolved same-workspace wiki-links from the item_wiki_links reverse index (PLAN-1593). Both queries exclude edges whose source or target item is soft-deleted, m
(workspaceID string)
| 55 | // structure). Unresolved rows (target_item_id IS NULL) and |
| 56 | // cross-workspace rows are excluded: the graph renders one workspace. |
| 57 | func (s *Store) ListWorkspaceGraphLinks(workspaceID string) ([]GraphLink, error) { |
| 58 | links := []GraphLink{} |
| 59 | |
| 60 | rows, err := s.db.Query(s.q(` |
| 61 | SELECT il.source_id, il.target_id, il.link_type |
| 62 | FROM item_links il |
| 63 | JOIN items src ON src.id = il.source_id AND src.deleted_at IS NULL |
| 64 | JOIN items tgt ON tgt.id = il.target_id AND tgt.deleted_at IS NULL |
| 65 | WHERE il.workspace_id = ? |
| 66 | `), workspaceID) |
| 67 | if err != nil { |
| 68 | return nil, fmt.Errorf("list graph item links: %w", err) |
| 69 | } |
| 70 | defer rows.Close() |
| 71 | for rows.Next() { |
| 72 | var l GraphLink |
| 73 | if err := rows.Scan(&l.SourceID, &l.TargetID, &l.Type); err != nil { |
| 74 | return nil, fmt.Errorf("scan graph item link: %w", err) |
| 75 | } |
| 76 | l.Type = graphEdgeType(l.Type) |
| 77 | links = append(links, l) |
| 78 | } |
| 79 | if err := rows.Err(); err != nil { |
| 80 | return nil, err |
| 81 | } |
| 82 | |
| 83 | // Wiki-link edges. Scoped by the SOURCE item's workspace — |
| 84 | // item_wiki_links has no workspace_id column of its own, and |
| 85 | // target_workspace_id is only set on cross-workspace rows (which |
| 86 | // are excluded here anyway). |
| 87 | wikiRows, err := s.db.Query(s.q(` |
| 88 | SELECT DISTINCT wl.source_item_id, wl.target_item_id |
| 89 | FROM item_wiki_links wl |
| 90 | JOIN items src ON src.id = wl.source_item_id AND src.deleted_at IS NULL |
| 91 | JOIN items tgt ON tgt.id = wl.target_item_id AND tgt.deleted_at IS NULL |
| 92 | WHERE src.workspace_id = ? |
| 93 | AND wl.target_item_id IS NOT NULL |
| 94 | AND wl.target_workspace_id IS NULL |
| 95 | AND wl.source_item_id != wl.target_item_id |
| 96 | `), workspaceID) |
| 97 | if err != nil { |
| 98 | return nil, fmt.Errorf("list graph wiki links: %w", err) |
| 99 | } |
| 100 | defer wikiRows.Close() |
| 101 | for wikiRows.Next() { |
| 102 | l := GraphLink{Type: "wiki-link"} |
| 103 | if err := wikiRows.Scan(&l.SourceID, &l.TargetID); err != nil { |
| 104 | return nil, fmt.Errorf("scan graph wiki link: %w", err) |
| 105 | } |
| 106 | links = append(links, l) |
| 107 | } |
| 108 | if err := wikiRows.Err(); err != nil { |
| 109 | return nil, err |
| 110 | } |
| 111 | |
| 112 | // Dedupe (source, target, type) — a stored wiki_link item_links row |
| 113 | // and a parsed [[...]] mention of the same pair both normalize to |
| 114 | // 'wiki-link' and would otherwise emit twice. |
no test coverage detected