Merge pull request #5571 from cyrillefr/Cannot_Access_Budget_Investments_Using_The_GraphQ_LAPI

Add new GraphQL types for budget investments
This commit is contained in:
Javi Martín
2024-09-30 11:33:39 +02:00
committed by GitHub
13 changed files with 171 additions and 6 deletions

View File

@@ -0,0 +1,14 @@
module Types
class BudgetInvestmentType < Types::BaseObject
field :id, ID, null: false
field :public_author, Types::UserType, null: true
field :price, GraphQL::Types::BigInt, null: true
field :feasibility, String, null: true
field :title, String, null: true
field :description, String, null: true
field :location, String, null: true
field :comments, Types::CommentType.connection_type, null: true
field :comments_count, Integer, null: true
field :milestones, Types::MilestoneType.connection_type, null: true
end
end

View File

@@ -0,0 +1,19 @@
module Types
class BudgetType < Types::BaseObject
field :id, ID, null: false
field :name, String, null: true
field :phase, String, null: true
field :investments, Types::BudgetInvestmentType.connection_type, "Returns all investments", null: false
field :investment, Types::BudgetInvestmentType, null: false do
argument :id, ID, required: true, default_value: false
end
def investments
Budget::Investment.public_for_api
end
def investment(id:)
Budget::Investment.find(id)
end
end
end

View File

@@ -0,0 +1,8 @@
module Types
class MilestoneType < Types::BaseObject
field :title, String, null: true
field :description, String, null: true
field :id, ID, null: false
field :publication_date, GraphQL::Types::ISO8601Date, null: true
end
end

View File

@@ -20,6 +20,7 @@ module Types
field :title, String, null: true field :title, String, null: true
field :video_url, String, null: true field :video_url, String, null: true
field :votes_for, Types::VoteType.connection_type, null: true field :votes_for, Types::VoteType.connection_type, null: true
field :milestones, Types::MilestoneType.connection_type, null: true
def tags def tags
object.tags.public_for_api object.tags.public_for_api

View File

@@ -1,5 +1,10 @@
module Types module Types
class QueryType < Types::BaseObject class QueryType < Types::BaseObject
field :budgets, Types::BudgetType.connection_type, "Returns all budgets", null: false
field :budget, Types::BudgetType, "Returns budget for ID", null: false do
argument :id, ID, required: true, default_value: false
end
field :comments, Types::CommentType.connection_type, "Returns all comments", null: false field :comments, Types::CommentType.connection_type, "Returns all comments", null: false
field :comment, Types::CommentType, "Returns comment for ID", null: false do field :comment, Types::CommentType, "Returns comment for ID", null: false do
argument :id, ID, required: true, default_value: false argument :id, ID, required: true, default_value: false
@@ -15,6 +20,11 @@ module Types
argument :id, ID, required: true, default_value: false argument :id, ID, required: true, default_value: false
end end
field :milestones, Types::MilestoneType.connection_type, "Returns all milestones", null: false
field :milestone, Types::MilestoneType, "Returns milestone for ID", null: false do
argument :id, ID, required: true, default_value: false
end
field :proposals, Types::ProposalType.connection_type, "Returns all proposals", null: false field :proposals, Types::ProposalType.connection_type, "Returns all proposals", null: false
field :proposal, Types::ProposalType, "Returns proposal for ID", null: false do field :proposal, Types::ProposalType, "Returns proposal for ID", null: false do
argument :id, ID, required: true, default_value: false argument :id, ID, required: true, default_value: false
@@ -47,6 +57,14 @@ module Types
argument :id, ID, required: true, default_value: false argument :id, ID, required: true, default_value: false
end end
def budgets
Budget.public_for_api
end
def budget(id:)
Budget.find(id)
end
def comments def comments
Comment.public_for_api Comment.public_for_api
end end
@@ -71,6 +89,14 @@ module Types
Geozone.find(id) Geozone.find(id)
end end
def milestones
Milestone.public_for_api
end
def milestone(id:)
Milestone.find(id)
end
def proposals def proposals
Proposal.public_for_api Proposal.public_for_api
end end

View File

@@ -58,6 +58,7 @@ class Budget < ApplicationRecord
scope :balloting, -> { where(phase: "balloting") } scope :balloting, -> { where(phase: "balloting") }
scope :reviewing_ballots, -> { where(phase: "reviewing_ballots") } scope :reviewing_ballots, -> { where(phase: "reviewing_ballots") }
scope :finished, -> { where(phase: "finished") } scope :finished, -> { where(phase: "finished") }
scope :public_for_api, -> { published }
class << self; undef :open; end class << self; undef :open; end
scope :open, -> { where.not(phase: "finished") } scope :open, -> { where.not(phase: "finished") }

View File

@@ -13,6 +13,7 @@ class Budget
include Mappable include Mappable
include Documentable include Documentable
include SDG::Relatable include SDG::Relatable
include HasPublicAuthor
acts_as_taggable_on :valuation_tags acts_as_taggable_on :valuation_tags
acts_as_votable acts_as_votable
@@ -111,6 +112,7 @@ class Budget
end end
scope :for_render, -> { includes(:heading) } scope :for_render, -> { includes(:heading) }
scope :public_for_api, -> { where(budget: Budget.public_for_api) }
def self.by_valuator(valuator_id) def self.by_valuator(valuator_id)
where(budget_valuator_assignments: { valuator_id: valuator_id }).joins(:valuator_assignments) where(budget_valuator_assignments: { valuator_id: valuator_id }).joins(:valuator_assignments)

View File

@@ -38,7 +38,10 @@ class Comment < ApplicationRecord
scope :sort_by_flags, -> { order(flags_count: :desc, updated_at: :desc) } scope :sort_by_flags, -> { order(flags_count: :desc, updated_at: :desc) }
scope :public_for_api, -> do scope :public_for_api, -> do
not_valuations not_valuations
.where(commentable: [Debate.public_for_api, Proposal.public_for_api, Poll.public_for_api]) .where(commentable: [Debate.public_for_api,
Proposal.public_for_api,
Poll.public_for_api,
Budget::Investment.public_for_api])
end end
scope :sort_by_most_voted, -> { order(confidence_score: :desc, created_at: :desc) } scope :sort_by_most_voted, -> { order(confidence_score: :desc, created_at: :desc) }

View File

@@ -16,6 +16,9 @@ class Milestone < ApplicationRecord
scope :order_by_publication_date, -> { order(publication_date: :asc, created_at: :asc) } scope :order_by_publication_date, -> { order(publication_date: :asc, created_at: :asc) }
scope :published, -> { where(publication_date: ..Date.current.end_of_day) } scope :published, -> { where(publication_date: ..Date.current.end_of_day) }
scope :with_status, -> { where.not(status_id: nil) } scope :with_status, -> { where.not(status_id: nil) }
scope :public_for_api, -> do
where(milestoneable: [Proposal.public_for_api, Budget::Investment.public_for_api])
end
def self.title_max_length def self.title_max_length
80 80

View File

@@ -130,7 +130,10 @@ The models are the following:
| `User` | Users | | `User` | Users |
| `Debate` | Debates | | `Debate` | Debates |
| `Proposal` | Proposals | | `Proposal` | Proposals |
| `Budget` | Participatory budgets |
| `Budget::Investment` | Budget investments |
| `Comment` | Comments on debates, proposals and other comments | | `Comment` | Comments on debates, proposals and other comments |
| `Milestone` | Proposals, investments and processes milestones |
| `Geozone` | Geozones (districts) | | `Geozone` | Geozones (districts) |
| `ProposalNotification` | Notifications related to proposals | | `ProposalNotification` | Notifications related to proposals |
| `Tag` | Tags on debates and proposals | | `Tag` | Tags on debates and proposals |

View File

@@ -130,7 +130,10 @@ La lista de modelos es la siguiente:
| `User` | Usuarios | | `User` | Usuarios |
| `Debate` | Debates | | `Debate` | Debates |
| `Proposal` | Propuestas | | `Proposal` | Propuestas |
| `Budget` | Presupuestos participativos |
| `Budget::Investment` | Proyectos de gasto |
| `Comment` | Comentarios en debates, propuestas y otros comentarios | | `Comment` | Comentarios en debates, propuestas y otros comentarios |
| `Milestone` | Hitos en propuestas, proyectos de gasto y procesos |
| `Geozone` | Geozonas (distritos) | | `Geozone` | Geozonas (distritos) |
| `ProposalNotification` | Notificaciones asociadas a propuestas | | `ProposalNotification` | Notificaciones asociadas a propuestas |
| `Tag` | Tags en debates y propuestas | | `Tag` | Tags en debates y propuestas |

View File

@@ -193,6 +193,17 @@ describe "Consul Schema" do
end end
end end
describe "Budgets" do
it "does not include unpublished budgets" do
create(:budget, :drafting, name: "Draft")
response = execute("{ budgets { edges { node { name } } } }")
received_names = extract_fields(response, "budgets", "name")
expect(received_names).to eq []
end
end
describe "Debates" do describe "Debates" do
it "does not include hidden debates" do it "does not include hidden debates" do
create(:debate, title: "Visible") create(:debate, title: "Visible")
@@ -252,16 +263,17 @@ describe "Consul Schema" do
end end
describe "Comments" do describe "Comments" do
it "only returns comments from proposals, debates and polls" do it "only returns comments from proposals, debates, polls and Budget::Investment" do
create(:comment, commentable: create(:proposal)) create(:comment, commentable: create(:proposal))
create(:comment, commentable: create(:debate)) create(:comment, commentable: create(:debate))
create(:comment, commentable: create(:poll)) create(:comment, commentable: create(:poll))
build(:comment, commentable: create(:budget_investment)).save!(skip_validation: true) create(:comment, commentable: create(:topic))
create(:comment, commentable: create(:budget_investment))
response = execute("{ comments { edges { node { commentable_type } } } }") response = execute("{ comments { edges { node { commentable_type } } } }")
received_commentables = extract_fields(response, "comments", "commentable_type") received_commentables = extract_fields(response, "comments", "commentable_type")
expect(received_commentables).to match_array ["Proposal", "Debate", "Poll"] expect(received_commentables).to match_array ["Proposal", "Debate", "Poll", "Budget::Investment"]
end end
it "displays comments of authors even if public activity is set to false" do it "displays comments of authors even if public activity is set to false" do
@@ -336,6 +348,19 @@ describe "Consul Schema" do
expect(received_comments).to match_array ["I can see the poll"] expect(received_comments).to match_array ["I can see the poll"]
end end
it "does not include comments from hidden investments" do
visible_investment = create(:budget_investment)
hidden_investment = create(:budget_investment, :hidden)
create(:comment, commentable: visible_investment, body: "I can see the investment")
create(:comment, commentable: hidden_investment, body: "This investment is hidden!")
response = execute("{ comments { edges { node { body } } } }")
received_comments = extract_fields(response, "comments", "body")
expect(received_comments).to match_array ["I can see the investment"]
end
it "does not include comments of debates that are not public" do it "does not include comments of debates that are not public" do
not_public_debate = create(:debate, :hidden) not_public_debate = create(:debate, :hidden)
not_public_debate_comment = create(:comment, commentable: not_public_debate) not_public_debate_comment = create(:comment, commentable: not_public_debate)
@@ -369,6 +394,16 @@ describe "Consul Schema" do
expect(received_comments).not_to include(not_public_poll_comment.body) expect(received_comments).not_to include(not_public_poll_comment.body)
end end
it "does not include comments of investments that are not public" do
investment = create(:budget_investment, budget: create(:budget, :drafting))
not_public_investment_comment = create(:comment, commentable: investment)
response = execute("{ comments { edges { node { body } } } }")
received_comments = extract_fields(response, "comments", "body")
expect(received_comments).not_to include(not_public_investment_comment.body)
end
it "only links public comments" do it "only links public comments" do
user = create(:administrator).user user = create(:administrator).user
create(:comment, author: user, body: "Public") create(:comment, author: user, body: "Public")
@@ -642,4 +677,45 @@ describe "Consul Schema" do
expect(Time.zone.parse(received_timestamps.first)).to eq Time.zone.parse("2017-12-31 9:00:00") expect(Time.zone.parse(received_timestamps.first)).to eq Time.zone.parse("2017-12-31 9:00:00")
end end
end end
describe "Milestone" do
it "formats publication date like in view" do
milestone = create(:milestone, publication_date: Time.zone.parse("2024-07-02 11:45:17"))
response = execute("{ milestone(id: #{milestone.id}) { id publication_date } }")
received_publication_date = dig(response, "data.milestone.publication_date")
expect(received_publication_date).to eq "2024-07-02"
end
end
describe "Budget investment" do
it "does not include hidden comments" do
budget = create(:budget)
investment = create(:budget_investment, budget: budget)
create(:comment, commentable: investment, body: "Visible")
create(:comment, :hidden, commentable: investment, body: "Hidden")
query = <<~GRAPHQL
{
budget(id: #{budget.id}) {
investment(id: #{investment.id}) {
comments {
edges {
node {
body
}
}
}
}
}
}
GRAPHQL
response = execute(query)
received_bodies = extract_fields(response, "budget.investment.comments", "body")
expect(received_bodies).to eq ["Visible"]
end
end
end end

View File

@@ -175,8 +175,14 @@ describe Comment do
expect(Comment.public_for_api).to be_empty expect(Comment.public_for_api).to be_empty
end end
it "does not return comments on elements which are not debates or proposals" do it "returns comments on budget investments" do
create(:comment, commentable: create(:budget_investment)) comment = create(:comment, commentable: create(:budget_investment))
expect(Comment.public_for_api).to eq [comment]
end
it "does not return comments on elements which are not debates, proposals or budget investments" do
create(:comment, commentable: create(:topic))
expect(Comment.public_for_api).to be_empty expect(Comment.public_for_api).to be_empty
end end