@@ -16,6 +16,7 @@ defmodule LinearCli.Linear.Project do
1616
1717 read :by_team do
1818 argument :team_id , :string , allow_nil?: false
19+ argument :search , :string
1920 manual LinearCli.Linear.Project.Read.ByTeam
2021 end
2122
@@ -111,12 +112,24 @@ defmodule LinearCli.Linear.Project do
111112 def matches_attributes? ( % __MODULE__ { } = project , string , attrs ) do
112113 Enum . any? ( attrs , fn attr ->
113114 case Map . get ( project , attr ) do
114- value when is_binary ( value ) -> String . downcase ( value ) == String . downcase ( string )
115- _ -> false
115+ value when is_binary ( value ) ->
116+ normalize_match_value ( attr , value ) == normalize_match_value ( attr , string )
117+
118+ _ ->
119+ false
116120 end
117121 end )
118122 end
119123
124+ defp normalize_match_value ( :url , value ) do
125+ value
126+ |> String . trim_trailing ( "/" )
127+ |> String . trim_trailing ( "/issues" )
128+ |> String . downcase ( )
129+ end
130+
131+ defp normalize_match_value ( _attr , value ) , do: String . downcase ( value )
132+
120133 defp exact_name_or_slug_match? ( project , string ) do
121134 downed = String . downcase ( string )
122135 slugified = downed |> String . split ( ) |> Enum . join ( "-" )
@@ -166,11 +179,24 @@ defmodule LinearCli.Linear.Project.Read.ByTeam do
166179 alias LinearCli.Api
167180 alias LinearCli.Linear.Project
168181
169- # Ruby's Team#projects fetches a single page of 100, no cursor loop.
182+ @ uuid ~r/ ^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/ i
183+ @ slugged_reference ~r/ ^(.*)-([[:alnum:]]{12})$/
184+
170185 @ document """
171- query($teamId: String!) {
186+ query($teamId: String!, $after: String ) {
172187 team(id: $teamId) {
173- projects(first: 100) {
188+ projects(first: 100, after: $after) {
189+ nodes { #{ Project . base_fields ( ) } }
190+ pageInfo { hasNextPage endCursor }
191+ }
192+ }
193+ }
194+ """
195+
196+ @ search_document """
197+ query($teamId: String!, $filter: ProjectFilter!) {
198+ team(id: $teamId) {
199+ projects(first: 100, filter: $filter) {
174200 nodes { #{ Project . base_fields ( ) } }
175201 }
176202 }
@@ -180,11 +206,80 @@ defmodule LinearCli.Linear.Project.Read.ByTeam do
180206 def read ( query , _ecto_query , _opts , _context ) do
181207 team_id = query . arguments . team_id
182208
209+ case Map . get ( query . arguments , :search ) do
210+ search when is_binary ( search ) and search not in [ "" , "-" ] ->
211+ search ( team_id , search )
212+
213+ _ ->
214+ all ( team_id )
215+ end
216+ end
217+
218+ defp search ( team_id , search ) do
219+ variables = % { "teamId" => team_id , "filter" => project_filter ( search ) }
220+
221+ with { :ok , projects } <- fetch ( @ search_document , variables ) do
222+ if projects == [ ] , do: all ( team_id ) , else: { :ok , projects }
223+ end
224+ end
225+
226+ defp all ( team_id ) , do: page ( team_id , nil , [ ] )
227+
228+ defp page ( team_id , after_cursor , acc ) do
229+ variables = % { "teamId" => team_id , "after" => after_cursor }
230+
231+ with { :ok , % { "team" => % { "projects" => projects } } } <- Api . call ( @ document , variables ) do
232+ acc = acc ++ Enum . map ( projects [ "nodes" ] || [ ] , & Project . from_map / 1 )
233+
234+ case projects [ "pageInfo" ] do
235+ % { "hasNextPage" => true , "endCursor" => cursor } when is_binary ( cursor ) ->
236+ page ( team_id , cursor , acc )
237+
238+ _ ->
239+ { :ok , acc }
240+ end
241+ end
242+ end
243+
244+ defp fetch ( document , variables ) do
183245 with { :ok , % { "team" => % { "projects" => % { "nodes" => nodes } } } } <-
184- Api . call ( @ document , % { "teamId" => team_id } ) do
246+ Api . call ( document , variables ) do
185247 { :ok , Enum . map ( nodes , & Project . from_map / 1 ) }
186248 end
187249 end
250+
251+ defp project_filter ( search ) do
252+ reference = project_reference ( search )
253+ { name , slug_id } = reference_terms ( reference )
254+
255+ [
256+ project_id_filter ( search ) ,
257+ % { "name" => % { "containsIgnoreCase" => search } } ,
258+ % { "name" => % { "containsIgnoreCase" => name } } ,
259+ % { "slugId" => % { "eqIgnoreCase" => slug_id } }
260+ ]
261+ |> Enum . reject ( & is_nil / 1 )
262+ |> Enum . uniq ( )
263+ |> then ( & % { "or" => & 1 } )
264+ end
265+
266+ defp project_reference ( search ) do
267+ case Regex . run ( ~r{ /project/([^/]+)} , search , capture: :all_but_first ) do
268+ [ reference ] -> reference
269+ _ -> search
270+ end
271+ end
272+
273+ defp reference_terms ( reference ) do
274+ case Regex . run ( @ slugged_reference , reference , capture: :all_but_first ) do
275+ [ slug , slug_id ] -> { String . replace ( slug , "-" , " " ) , slug_id }
276+ _ -> { String . replace ( reference , "-" , " " ) , reference }
277+ end
278+ end
279+
280+ defp project_id_filter ( search ) do
281+ if Regex . match? ( @ uuid , search ) , do: % { "id" => % { "eq" => search } }
282+ end
188283end
189284
190285defmodule LinearCli.Linear.Project.Read.ByName do
0 commit comments