Skip to content

Commit

Permalink
added fetching insights by API
Browse files Browse the repository at this point in the history
  • Loading branch information
kortirso committed Jul 4, 2024
1 parent c03d184 commit c960def
Show file tree
Hide file tree
Showing 11 changed files with 494 additions and 9 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- expiration time for access tokens
- creating/removing API access tokens
- API documentation
- fetching insights by API

### Modified
- skip reseting invites email after accepting invite
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ $ bearer scan .

API documentation is available at [api-docs](https://pullkeeper.dev/api-docs).

### Refresh API documentation

```bash
$ rails rswag:specs:swaggerize
```

## Application layers

contracts - model schemas for validators
Expand Down
5 changes: 3 additions & 2 deletions app/controllers/api/frontend/insights_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ def index
ratio_type: ratio_type
}
}
).serializable_hash
}, status: :ok
).serializable_hash,
ratio_type: ratio_enabled? ? ratio_type : nil
}.compact, status: :ok
end

private
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ def index
no_entity: true
}
}
).serializable_hash
}, status: :ok
).serializable_hash,
ratio_type: ratio_enabled? ? ratio_type : nil
}.compact, status: :ok
end

private
Expand Down
75 changes: 75 additions & 0 deletions app/controllers/api/v1/insights_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# frozen_string_literal: true

module Api
module V1
class InsightsController < Api::V1Controller
before_action :find_insightable

def index
render json: {
insights: InsightSerializer.new(
actual_insights,
{
params: {
previous_insights: previous_insights,
insight_fields: insight_fields,
ratio_enabled: ratio_enabled?,
ratio_type: ratio_type
}
}
).serializable_hash,
ratio_type: ratio_enabled? ? ratio_type : nil
}.compact, status: :ok
end

private

def find_insightable
find_company if params[:company_id]
find_repository if params[:repository_id]

page_not_found unless @insightable
end

def find_company
@insightable = authorized_scope(Company.order(id: :desc)).find_by(uuid: params[:company_id])
end

def find_repository
@insightable = authorized_scope(Repository.order(id: :desc)).find_by(uuid: params[:repository_id])
end

def visible_insights
@visible_insights ||=
Insights::VisibleQuery
.new(relation: @insightable.insights)
.resolve(insightable: @insightable)
.load
end

def actual_insights
visible_insights.select(&:actual?)
end

def previous_insights
visible_insights.reject(&:actual?)
end

def insight_fields
if @insightable.premium? && @insightable.configuration.insight_fields.present?
@insightable.selected_insight_fields
else
Insight::DEFAULT_ATTRIBUTES
end
end

def ratio_enabled?
@insightable.premium? && @insightable.configuration.insight_ratio
end

def ratio_type
@insightable.configuration.insight_ratio_type
end
end
end
end
6 changes: 1 addition & 5 deletions app/serializers/insight_serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,10 @@ class InsightSerializer < ApplicationSerializer
{
value: Insight::DECIMAL_ATTRIBUTES.include?(insight_field.to_sym) ? value.to_f : value,
ratio_value: params[:ratio_enabled] ? compare_with_previous_period(object, insight_field, params) : nil
}
}.compact
end
end

attribute :ratio_type do |_, params|
params[:ratio_enabled] ? params[:ratio_type] : nil
end

attribute :entity do |object, params|
params[:no_entity] ? nil : (Rails.cache.read("entity_payload_#{object.entity_id}_v1") || Entity::EMPTY_PAYLOAD)
end
Expand Down
6 changes: 6 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@

namespace :v1 do
resources :companies, only: %i[index]
resources :companies, only: %i[] do
resources :insights, only: %i[index]
end
resources :repositories, only: %i[] do
resources :insights, only: %i[index]
end
end
end

Expand Down
95 changes: 95 additions & 0 deletions spec/controllers/api/v1/insights_controller_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# frozen_string_literal: true

describe Api::V1::InsightsController do
describe 'GET#index' do
it_behaves_like 'required api auth'
it_behaves_like 'required valid api auth'

context 'for logged users' do
let!(:company) { create :company, configuration: { insight_ratio: true } }
let!(:user) { create :user }
let(:api_access_token) { create :api_access_token, user: user }

before { create :subscription, user: user }

context 'for companies' do
before do
insight = create :insight, insightable: company, comments_count: 1
create :insight, insightable: company, comments_count: 2, previous_date: 1.week.ago, entity: insight.entity
end

context 'for user company' do
before { company.update!(user: user) }

it 'returns data', :aggregate_failures do
get :index, params: { company_id: company.uuid, api_access_token: api_access_token.value }

response_values = response.parsed_body.dig('insights', 'data', 0, 'attributes', 'values')

expect(response).to have_http_status :ok
expect(response_values.keys).to eq Insight::DEFAULT_ATTRIBUTES.map(&:to_s)
expect(response_values.dig('comments_count', 'value')).to eq 1
end

context 'for selected insight fields' do
before do
company.configuration.insight_fields = { comments_count: true, reviews_count: true }
company.save!
end

it 'returns data', :aggregate_failures do
get :index, params: { company_id: company.uuid, api_access_token: api_access_token.value }

response_values = response.parsed_body.dig('insights', 'data', 0, 'attributes', 'values')

expect(response).to have_http_status :ok
expect(response_values.keys).to contain_exactly('comments_count', 'reviews_count')
expect(response_values.dig('comments_count', 'value')).to eq 1
end
end

context 'for change ratio' do
before do
company.configuration.insight_ratio_type = 'change'
company.save!
end

it 'returns data', :aggregate_failures do
get :index, params: { company_id: company.uuid, api_access_token: api_access_token.value }

response_values = response.parsed_body.dig('insights', 'data', 0, 'attributes', 'values')

expect(response).to have_http_status :ok
expect(response_values.keys).to eq Insight::DEFAULT_ATTRIBUTES.map(&:to_s)
expect(response_values.dig('comments_count', 'value')).to eq 1
end
end
end
end

context 'for repositories' do
let!(:repository) { create :repository, company: company }

before { create :insight, insightable: repository, comments_count: 2 }

context 'for user company' do
before { company.update!(user: user) }

it 'returns data', :aggregate_failures do
get :index, params: { repository_id: repository.uuid, api_access_token: api_access_token.value }

response_values = response.parsed_body.dig('insights', 'data', 0, 'attributes', 'values')

expect(response).to have_http_status :ok
expect(response_values.keys).to eq Insight::DEFAULT_ATTRIBUTES.map(&:to_s)
expect(response_values.dig('comments_count', 'value')).to eq 2
end
end
end
end

def do_request(api_access_token=nil)
get :index, params: { company_id: 'unexisting', api_access_token: api_access_token }.compact
end
end
end
84 changes: 84 additions & 0 deletions spec/requests/api/v1/companies_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -83,5 +83,89 @@
end
end
end

path '/api/v1/companies/{id}/insights' do
get('Insights of developers by company') do
tags 'Companies'

description 'List of developers insights by company'

produces 'application/json'
consumes 'application/json'

parameter name: :id, in: :path, type: :string, description: 'Company UUID', required: true
parameter name: :api_access_token, in: :query, type: :string, description: 'API access token', required: true

response(200, 'successful') do
let(:user) { create :user }
let(:api_access_token) { create(:api_access_token, user: user).value }
let(:company) { create(:company, user: user) }
let(:id) { company.uuid }
let(:insight) { create :insight, insightable: company }

after do |example|
example.metadata[:response][:content] = {
'application/json' => {
example: JSON.parse(response.body, symbolize_names: true)
}
}
end

schema type: :object, properties: {
data: {
type: :array,
items: {
type: :object,
properties: {
id: { type: :string },
type: { type: :string },
attributes: {
type: :object,
properties: {
values: {
type: :object
},
entity: {
type: :object,
properties: {
login: { type: :string },
html_url: { type: :string },
avatar_url: { type: :string }
}
}
}
}
}
}
},
ratio_type: { type: :string, nullable: true }
}

run_test!
end

response(401, 'Unauthorized') do
let(:api_access_token) { 'unexisting' }
let(:id) { 'unexisting' }

after do |example|
example.metadata[:response][:content] = {
'application/json' => {
example: JSON.parse(response.body, symbolize_names: true)
}
}
end

schema type: :object, properties: {
error: {
type: :array,
items: { type: :string }
}
}

run_test!
end
end
end
end
# rubocop: enable RSpec/EmptyExampleGroup, RSpec/ScatteredSetup
Loading

0 comments on commit c960def

Please sign in to comment.