Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
9 changes: 7 additions & 2 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ ruby "3.2.2"

# Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main"
gem "rails", "~> 7.0.8"
gem 'active_model_serializers', '~> 0.10.0'

# The original asset pipeline for Rails [https://github.com/rails/sprockets-rails]
gem "sprockets-rails"
Expand Down Expand Up @@ -51,10 +52,15 @@ gem "bootsnap", require: false
# Use Active Storage variants [https://guides.rubyonrails.org/active_storage_overview.html#transforming-images]
# gem "image_processing", "~> 1.2"

gem 'rack-attack', '~> 6.7.0'

group :development, :test do
# See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem
gem "debug", platforms: %i[ mri mingw x64_mingw ]
gem "faker"
gem "faker", '~> 3.3.0'
gem 'factory_bot_rails', '~> 6.4.0'
gem 'rspec-rails', '~> 6.1.0'
gem 'rubocop-rails', '~> 2.24.0'
end

group :development do
Expand All @@ -67,4 +73,3 @@ group :development do
# Speed up commands on slow machines / big apps [https://github.com/rails/spring]
# gem "spring"
end

77 changes: 75 additions & 2 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ GEM
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.1, >= 1.2.0)
active_model_serializers (0.10.14)
actionpack (>= 4.1)
activemodel (>= 4.1)
case_transform (>= 0.2)
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
activejob (7.0.8)
activesupport (= 7.0.8)
globalid (>= 0.3.6)
Expand All @@ -66,10 +71,13 @@ GEM
i18n (>= 1.6, < 2)
minitest (>= 5.1)
tzinfo (~> 2.0)
ast (2.4.2)
bindex (0.8.1)
bootsnap (1.17.1)
msgpack (~> 1.2)
builder (3.2.4)
case_transform (0.2)
activesupport
concurrent-ruby (1.2.3)
crass (1.0.6)
cssbundling-rails (1.4.0)
Expand All @@ -78,8 +86,14 @@ GEM
debug (1.9.1)
irb (~> 1.10)
reline (>= 0.3.8)
diff-lcs (1.5.1)
erubi (1.12.0)
faker (3.2.3)
factory_bot (6.4.6)
activesupport (>= 5.0.0)
factory_bot_rails (6.4.3)
factory_bot (~> 6.4)
railties (>= 5.0.0)
faker (3.3.1)
i18n (>= 1.8.11, < 2)
globalid (1.2.1)
activesupport (>= 6.1)
Expand All @@ -94,6 +108,9 @@ GEM
activesupport (>= 5.0.0)
jsbundling-rails (1.3.0)
railties (>= 6.0.0)
json (2.7.2)
jsonapi-renderer (0.2.2)
language_server-protocol (3.17.0.3)
loofah (2.22.0)
crass (~> 1.0.2)
nokogiri (>= 1.12.0)
Expand All @@ -105,6 +122,7 @@ GEM
marcel (1.0.2)
method_source (1.0.0)
mini_mime (1.1.5)
mini_portile2 (2.8.6)
minitest (5.21.2)
msgpack (1.7.2)
mysql2 (0.5.5)
Expand All @@ -118,14 +136,23 @@ GEM
net-smtp (0.4.0.1)
net-protocol
nio4r (2.7.0)
nokogiri (1.16.0)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
nokogiri (1.16.0-x86_64-darwin)
racc (~> 1.4)
parallel (1.24.0)
parser (3.3.0.5)
ast (~> 2.4.1)
racc
psych (5.1.2)
stringio
puma (6.4.2)
nio4r (~> 2.0)
racc (1.7.3)
rack (2.2.8)
rack-attack (6.7.0)
rack (>= 1.0, < 4)
rack-test (2.1.0)
rack (>= 1.3)
rails (7.0.8)
Expand Down Expand Up @@ -156,12 +183,51 @@ GEM
rake (>= 12.2)
thor (~> 1.0)
zeitwerk (~> 2.5)
rainbow (3.1.1)
rake (13.1.0)
rdoc (6.6.2)
psych (>= 4.0.0)
redis (4.8.1)
regexp_parser (2.9.0)
reline (0.4.2)
io-console (~> 0.5)
rexml (3.2.6)
rspec-core (3.13.0)
rspec-support (~> 3.13.0)
rspec-expectations (3.13.0)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0)
rspec-mocks (3.13.0)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0)
rspec-rails (6.1.2)
actionpack (>= 6.1)
activesupport (>= 6.1)
railties (>= 6.1)
rspec-core (~> 3.13)
rspec-expectations (~> 3.13)
rspec-mocks (~> 3.13)
rspec-support (~> 3.13)
rspec-support (3.13.1)
rubocop (1.63.3)
json (~> 2.3)
language_server-protocol (>= 3.17.0)
parallel (~> 1.10)
parser (>= 3.3.0.2)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0)
rexml (>= 3.2.5, < 4.0)
rubocop-ast (>= 1.31.1, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 3.0)
rubocop-ast (1.31.2)
parser (>= 3.3.0.4)
rubocop-rails (2.24.1)
activesupport (>= 4.2.0)
rack (>= 1.1)
rubocop (>= 1.33.0, < 2.0)
rubocop-ast (>= 1.31.1, < 2.0)
ruby-progressbar (1.13.0)
sprockets (4.2.1)
concurrent-ruby (~> 1.0)
rack (>= 2.2.4, < 4)
Expand All @@ -180,6 +246,7 @@ GEM
railties (>= 6.0.0)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
unicode-display_width (2.5.0)
web-console (4.2.1)
actionview (>= 6.0.0)
activemodel (>= 6.0.0)
Expand All @@ -191,19 +258,25 @@ GEM
zeitwerk (2.6.12)

PLATFORMS
ruby
x86_64-darwin-21

DEPENDENCIES
active_model_serializers (~> 0.10.0)
bootsnap
cssbundling-rails
debug
faker
factory_bot_rails (~> 6.4.0)
faker (~> 3.3.0)
jbuilder
jsbundling-rails
mysql2 (~> 0.5)
puma
rack-attack (~> 6.7.0)
rails (~> 7.0.8)
redis (~> 4.0)
rspec-rails (~> 6.1.0)
rubocop-rails (~> 2.24.0)
sprockets-rails
stimulus-rails
turbo-rails
Expand Down
25 changes: 23 additions & 2 deletions app/controllers/api/v1/companies_controller.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,27 @@
class Api::V1::CompaniesController < ApplicationController
PAGE_LIMIT = 50

protect_from_forgery with: :exception

def index
companies = Company.all.order(created_at: :desc)
render json: companies.as_json(include: :deals)
companies = Rails.cache.fetch('companies-' + params_cache_key, expires: 6.hours) {
CompanyFilter.new(filter_params, Company.all).filter.limit(PAGE_LIMIT).to_a
}
render json: companies
end

private

def filter_params
params.permit(:name, :industry, 'min-employee', 'min-deal-amount')
end

def params_cache_key
filter_params
.to_h
.to_a
.select{ |k, v| v.present? || v.to_i > 0 }
.sort_by(&:first)
.join('-')
end
end
16 changes: 13 additions & 3 deletions app/javascript/components/App.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
import React from "react";
import Routes from "../routes";
import React, { Component } from "react";
import CompaniesContainer from "./CompaniesContainer";

export default props => <>{Routes}</>;
class App extends Component {
render() {
return (
<div className="App">
<CompaniesContainer />
</div>
);
}
}

export default App
23 changes: 23 additions & 0 deletions app/javascript/components/AppLayout.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React, { Component } from "react";

class AppLayout extends Component {
constructor(props) {
super(props);
}

render() {
return(
<div className="vw-100 primary-color d-flex align-items-center justify-content-center">
<div className="jumbotron jumbotron-fluid bg-transparent">
<div className="container secondary-color">
<h1 className="display-4">Companies</h1>

{this.props.children}
</div>
</div>
</div>
);
}
}

export default AppLayout
75 changes: 75 additions & 0 deletions app/javascript/components/CompaniesContainer.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import React, { Component } from "react";
import AppLayout from "./AppLayout";
import CompanyFilter from "./CompanyFilter";
import CompanyListView from "./CompanyListView";
import Company from "./Company";
import debounce from "../helpers/debounce";
import getResource from "../helpers/getResource";

class CompaniesContainer extends Component {
constructor(props) {
super(props);

this.state = {
companies: [],
name: "",
industry: "",
'min-employee': 0,
'min-deal-amount': 0
};
}

componentDidMount() {
this._fetchCompanies();
}

_fetchCompanies = debounce(() => {
let params = "?" + ['name', 'industry', 'min-employee', 'min-deal-amount']
.map(key => key + '=' + this.state[key]).join('&');
const url = "/api/v1/companies" + params;

getResource(url)
.then((data) => {
this.setState({ companies: data })
})
.catch((err) => {
console.error('Could not fetch', err);
this.setState({ companies: [] })
});
})

_getCompanies() {
return this.state.companies.map((company) => {
return (
<Company
key={company.id}
name={company.name}
industry={company.industry}
employeeCount={company.employee_count}
dealsMin={company.deals_min}
dealsSum={company.deals_sum} />
)
})
}

_filterAll(name, value) {
this.setState({
[name]: value
}, () => {
this._fetchCompanies();
});
}

render() {
const companies = this._getCompanies();

return (
<AppLayout>
<CompanyFilter filterAll={this._filterAll.bind(this)} />
<CompanyListView companies={companies} />
</AppLayout>
);
}
}

export default CompaniesContainer
21 changes: 21 additions & 0 deletions app/javascript/components/Company.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React, { Component } from "react";

class Company extends Component {
constructor(props) {
super(props);
}

render() {
return(
<tr key={this.props.id}>
<td>{this.props.name}</td>
<td>{this.props.industry}</td>
<td>{this.props.employeeCount}</td>
<td>{this.props.dealsMin}</td>
<td>{this.props.dealsSum}</td>
</tr>
);
}
}

export default Company
Loading