diff --git a/Rakefile b/Rakefile index 10dc114..c77f96e 100644 --- a/Rakefile +++ b/Rakefile @@ -15,7 +15,7 @@ RuboCop::RakeTask.new desc 'Run Cucumber features' task :cucumber do - sh 'cucumber --format pretty' + sh 'bundle exec cucumber --format pretty' end task default: %i[rubocop test cucumber] diff --git a/features/project_list.feature b/features/project_list.feature new file mode 100644 index 0000000..a1031c0 --- /dev/null +++ b/features/project_list.feature @@ -0,0 +1,12 @@ +Feature: Project List + As a user + I want to list projects + So that I can see what projects are available + + Scenario: Listing my projects + When I run `lc project list --mine` + Then the output should match /Crying Game/ + + Scenario: Listing all projects + When I run `lc project list` + Then the output should match /Crying Game/ diff --git a/lib/linear/commands/project.rb b/lib/linear/commands/project.rb new file mode 100644 index 0000000..33ab4cd --- /dev/null +++ b/lib/linear/commands/project.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require_relative '../cli/sub_commands' + +module Rubyists + module Linear + module CLI + # The Project module is the namespace for all project-related commands + module Project + include CLI::SubCommands + + # Aliases for Project commands. + ALIASES = { + list: %w[ls l], # aliases for the list command + project: %w[p projects] # aliases for the main project command itself + }.freeze + + DESCRIPTION = 'Manage projects' + end + end + end +end diff --git a/lib/linear/commands/project/list.rb b/lib/linear/commands/project/list.rb new file mode 100644 index 0000000..1ec9632 --- /dev/null +++ b/lib/linear/commands/project/list.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require 'semantic_logger' + +module Rubyists + # Main module for Linear CLI commands + module Linear + M :project + O 'project/list' + module CLI + module Project + List = Class.new Dry::CLI::Command + # The List class is a Dry::CLI::Command that lists projects + class List + include SemanticLogger::Loggable + include Rubyists::Linear::CLI::CommonOptions + + desc 'List projects' + example [ + ' # List all projects', + '--mine # List only my projects' + ] + option :mine, type: :boolean, default: false, desc: 'Only show my projects' + + def call(**options) + logger.debug 'Listing projects' + result = Rubyists::Linear::Operations::Project::List.call(params: options) + if result.success? + display result[:projects], options + else + logger.error 'Failed to list projects' + end + end + end + end + end + end +end diff --git a/lib/linear/models/project.rb b/lib/linear/models/project.rb index ee8c5fd..6145aa9 100644 --- a/lib/linear/models/project.rb +++ b/lib/linear/models/project.rb @@ -22,6 +22,10 @@ class Project updatedAt end + def self.mine + User.me.teams.flat_map(&:projects) + end + def slug File.basename(url).sub("-#{slugId}", '') end @@ -49,6 +53,10 @@ def to_s def inspection format('name: "%s" type: "%s"', name:, url:) end + + def display(_options) + printf "%s\n", to_s + end end end end diff --git a/lib/linear/models/team.rb b/lib/linear/models/team.rb index f68b6a7..3036b3f 100644 --- a/lib/linear/models/team.rb +++ b/lib/linear/models/team.rb @@ -12,6 +12,23 @@ class Team include SemanticLogger::Loggable one_to_many :projects + alias loaded_projects projects + + def projects # rubocop:disable Metrics/MethodLength + return @projects if @projects + return @projects = loaded_projects if loaded_projects + + team_id = data[:id] + q = query do + team(id: team_id) do + projects(first: 100) do + nodes { ___ Project.base_fragment } + end + end + end + data = Api.query(q) + @projects = data.dig(:team, :projects, :nodes)&.map { |project| Project.new project } || [] + end # TODO: Make this configurable BaseFilter = { # rubocop:disable Naming/ConstantName diff --git a/lib/linear/operations/project/list.rb b/lib/linear/operations/project/list.rb new file mode 100644 index 0000000..fd38a2b --- /dev/null +++ b/lib/linear/operations/project/list.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module Rubyists + module Linear + module Operations + module Project + # The List operation lists projects + class List < Trailblazer::Operation + step :fetch_projects + + def fetch_projects(ctx, params:, **) + ctx[:projects] = if params[:mine] + Rubyists::Linear::Project.mine + else + Rubyists::Linear::Project.all + end + end + end + end + end + end +end diff --git a/linear-cli.gemspec b/linear-cli.gemspec index 52c5143..cab0546 100644 --- a/linear-cli.gemspec +++ b/linear-cli.gemspec @@ -40,6 +40,7 @@ Gem::Specification.new do |spec| spec.add_dependency 'git' spec.add_dependency 'gqli' spec.add_dependency 'httpx' + spec.add_dependency 'ostruct' spec.add_dependency 'pry-byebug' spec.add_dependency 'reline' spec.add_dependency 'semantic_logger' diff --git a/test/linear/operations/project/list_test.rb b/test/linear/operations/project/list_test.rb new file mode 100644 index 0000000..2d4eed4 --- /dev/null +++ b/test/linear/operations/project/list_test.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require_relative '../../../test_helper' +require 'linear/operations/project/list' + +class ProjectListOperationTest < Minitest::Spec + describe 'Rubyists::Linear::Operations::Project::List' do + let(:projects) { [Object.new, Object.new] } + + describe 'when mine is true' do + let(:params) { { mine: true } } + + it 'fetches my projects' do + Rubyists::Linear::Project.stub :mine, projects do + result = Rubyists::Linear::Operations::Project::List.call(params: params) + + _(result.success?).must_equal true + _(result[:projects]).must_equal projects + end + end + end + + describe 'when mine is false' do + let(:params) { { mine: false } } + + it 'fetches all projects' do + Rubyists::Linear::Project.stub :all, projects do + result = Rubyists::Linear::Operations::Project::List.call(params: params) + + _(result.success?).must_equal true + _(result[:projects]).must_equal projects + end + end + end + end +end