Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Filter issue list by project #7

Merged
merged 15 commits into from
Sep 18, 2024
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions lib/linear/cli/projects.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# frozen_string_literal: true

module Rubyists
module Linear
module CLI
# The Project module contains support for finding and selecting projects
# as part of a filter or query
module Projects
def ask_for_projects(projects, search: true)
prompt.warn("No project found matching #{search}.") if search
return projects.first if projects.size == 1

prompt.select('Project:', projects.to_h { |p| [p.name, p] })
end

def project_scores(projects, search_term)
projects.select { |p| p.match_score?(search_term).positive? }.sort_by { |p| p.match_score?(search_term) }
end

def project_for(project = nil, projects: Project.all)
return nil if projects.empty?

possibles = project ? project_scores(projects, project) : []
return ask_for_projects(projects, search: project) if possibles.empty?

first = possibles.first
return first if first.match_score?(project) == 100

selections = possibles + (projects - possibles)
prompt.select('Project:', selections.to_h { |p| [p.name, p] }) if possibles.size.positive?
end
end
end
end
end
29 changes: 3 additions & 26 deletions lib/linear/cli/what_for.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ module Rubyists
module Linear
module CLI
# Module for the _for methods
module WhatFor # rubocop:disable Metrics/ModuleLength
module WhatFor
include CLI::Projects # for methods called within #project_for

# TODO: Make this configurable
PR_TYPES = {
fix: 'Bug fixes',
Expand Down Expand Up @@ -87,31 +89,6 @@ def title_for(title = nil)
prompt.ask('Title:')
end

def ask_for_projects(projects, search: true)
prompt.warn("No project found matching #{search}.") if search
return projects.first if projects.size == 1

prompt.select('Project:', projects.to_h { |p| [p.name, p] })
end

def project_scores(projects, search_term)
projects.select { |p| p.match_score?(search_term).positive? }.sort_by { |p| p.match_score?(search_term) }
end

def project_for(team, project = nil) # rubocop:disable Metrics/AbcSize
projects = team.projects
return nil if projects.empty?

possibles = project ? project_scores(projects, project) : []
return ask_for_projects(projects, search: project) if possibles.empty?

first = possibles.first
return first if first.match_score?(project) == 100

selections = possibles + (projects - possibles)
prompt.select('Project:', selections.to_h { |p| [p.name, p] }) if possibles.size.positive?
end

def pr_title_for(issue)
proposed = [pr_type_for(issue)]
proposed_scope = pr_scope_for(issue.title)
Expand Down
4 changes: 2 additions & 2 deletions lib/linear/commands/issue.rb
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def issue_pr(issue, **options)
end

def attach_project(issue, project_search)
project = project_for(issue.team, project_search)
project = project_for(project_search, projects: issue.team.projects)
issue.attach_to_project project
prompt.ok "#{issue.identifier} was attached to #{project.name}"
end
Expand All @@ -88,7 +88,7 @@ def make_da_issue!(**options)
description = description_for(options[:description])
team = team_for(options[:team])
labels = labels_for(team, options[:labels])
project = project_for(team, options[:project])
project = project_for(options[:project], projects: team.projects)
Rubyists::Linear::Issue.create(title:, description:, team:, labels:, project:)
end

Expand Down
22 changes: 17 additions & 5 deletions lib/linear/commands/issue/list.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,25 @@ module Issue
class List
include SemanticLogger::Loggable
include Rubyists::Linear::CLI::CommonOptions
include CLI::SubCommands # for #prompt within CLI::Projects
include CLI::Projects # for methods called within #project_for

desc 'List issues'
example [
' # List your issues',
'--no-mine # List The most recent 100 issues',
'-u # List unassigned issues',
'-fu # List unassigned issues with full details',
'CRY-123 # Show issue CRY-123'
' # List your issues',
'--no-mine # List The most recent 100 issues',
'-u # List unassigned issues',
'-fu # List unassigned issues with full details',
'--project "Manhattan" # List only issues in project "Manhattan"',
'CRY-123 # Show issue CRY-123'
]
argument :ids, type: :array, default: [], desc: 'Issue IDs to list'
option :mine, type: :boolean, default: true, desc: 'Only show my issues'
option :unassigned, aliases: ['-u'], type: :boolean, default: false, desc: 'Show unassigned issues only'
option :team, aliases: ['-t'], type: :string, desc: 'Show issues for only this team'
option :full, type: :boolean, aliases: ['-f'], default: false, desc: 'Show full issue details'
option :project, type: :string, aliases: ['-p'],
desc: 'Show issues for only this project. Can be name, URL, ID, or - to select from a list'

def call(ids:, **options)
logger.debug 'Listing issues'
Expand All @@ -42,6 +47,13 @@ def filters_for(options)
filter[:assignee] = { isMe: { eq: true } } if options[:mine]
filter[:assignee] = { null: true } if options[:unassigned]
filter[:team] = { key: { eq: options[:team] } } if options[:team]

if options[:project]
project = project_for(options[:project])
logger.debug('Found project', project:)
filter[:project] = { id: { eq: project.id } } if project
end

filter
end

Expand Down
7 changes: 7 additions & 0 deletions lib/linear/models/project.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ def slug
end

def match_score?(string)
return 100 if matches_attributes?(string, :id, :url)

downed = string.downcase
return 100 if downed.split.join('-') == slug || downed == name.downcase
return 75 if name.include?(string) || slug.include?(downed)
Expand All @@ -35,6 +37,11 @@ def match_score?(string)
0
end

# Does :string _exactly_ match any of the attribute values in *attrs?
def matches_attributes?(string, *attrs)
Array(attrs).any? { |attr| string.casecmp?(data.fetch(attr.to_sym)) }
end

def to_s
format('%<name>-12s %<url>s', name:, url:)
end
Expand Down
54 changes: 54 additions & 0 deletions spec/linear/models/project_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# frozen_string_literal: true

require 'helper'

RSpec.describe 'Rubyists::Linear::Project' do
let(:instance) { Rubyists::Linear::Project.new(attributes) }
let(:attributes) do
{
id: SecureRandom.alphanumeric(32),
name: "Project #{Time.now.to_i}",
content: "Content at #{Time.now}",
slugId: SecureRandom.alphanumeric(8),
description: "Gadzooks! It's #{Time.now}!",
url: 'https://linear.app/myorg/project/my-project-a2d0ddbbfbeb',
createdAt: Time.now - 86_400,
updatedAt: Time.now - 3600
}
end

describe '#match_score?' do
context 'when arg equals matching url' do
subject { instance.match_score?(attributes.fetch(:url)) }

it { is_expected.to eq(100) }
end

context 'when arg equals project ID' do
subject { instance.match_score?(attributes.fetch(:id)) }

it { is_expected.to eq(100) }
end

context 'when arg equals project name' do
subject { instance.match_score?(attributes.fetch(:name)) }

it { is_expected.to eq(100) }
end

context 'when arg is part of project name' do
# Argument is project name with last 5 characters removed
subject { instance.match_score?(attributes.fetch(:name)[0..-5]) }

it { is_expected.to eq(75) }
end

context 'when description includes arg' do
subject { instance.match_score?(project_name) }

let(:project_name) { 'gadzooks' }

it { is_expected.to eq(50) }
end
end
end
Loading