├── .browserslistrc
├── .env
├── .env.test
├── .eslintrc
├── .gitignore
├── .hound.yml
├── .rspec
├── .rubocop.yml
├── .ruby-version
├── .stylelintrc.json
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── Gemfile
├── Gemfile.lock
├── LICENSE
├── Procfile
├── README.md
├── Rakefile
├── app
├── assets
│ ├── config
│ │ └── manifest.js
│ ├── images
│ │ ├── companies
│ │ │ ├── 18f.png
│ │ │ ├── airbrake.png
│ │ │ ├── bbc-news.png
│ │ │ ├── blendtec.png
│ │ │ ├── codeship.png
│ │ │ ├── dropbox.png
│ │ │ ├── elastic.png
│ │ │ ├── envoy.png
│ │ │ ├── envy-labs.png
│ │ │ ├── gitlab.png
│ │ │ ├── heroku.png
│ │ │ ├── hp.png
│ │ │ ├── intuit.png
│ │ │ ├── joomla.png
│ │ │ ├── lumosity.png
│ │ │ ├── meundies.png
│ │ │ ├── middleman.jpg
│ │ │ ├── mixpanel.png
│ │ │ ├── mozilla.png
│ │ │ ├── namely.png
│ │ │ ├── netguru.png
│ │ │ ├── percona.png
│ │ │ ├── rackspace.png
│ │ │ ├── rails-girls.png
│ │ │ ├── salesforce.png
│ │ │ ├── shipio.png
│ │ │ ├── shubox.jpg
│ │ │ ├── suse.png
│ │ │ ├── thoughtbot.png
│ │ │ ├── treehouse.png
│ │ │ ├── turing-school.png
│ │ │ ├── untuit.png
│ │ │ ├── wealthsimple.png
│ │ │ ├── wildbit.png
│ │ │ ├── yahoo.png
│ │ │ └── zendesk.png
│ │ ├── github-logomark.svg
│ │ ├── hatch-gray.svg
│ │ ├── hatch-purple.svg
│ │ ├── home
│ │ │ ├── hound_flow.png
│ │ │ ├── hound_flow.webp
│ │ │ ├── review_example.png
│ │ │ └── review_example.webp
│ │ ├── hound-logo.svg
│ │ ├── icon.svg
│ │ ├── languages
│ │ │ ├── coffeescript.svg
│ │ │ ├── elixir.svg
│ │ │ ├── go.svg
│ │ │ ├── haml.svg
│ │ │ ├── javascript.svg
│ │ │ ├── php.svg
│ │ │ ├── python.svg
│ │ │ ├── ruby.svg
│ │ │ ├── sass.svg
│ │ │ ├── shell.svg
│ │ │ ├── swift.svg
│ │ │ └── typescript.svg
│ │ ├── madi.jpg
│ │ ├── pricing_pattern.svg
│ │ ├── pricing_pattern_os.svg
│ │ └── select-arrow.svg
│ ├── javascripts
│ │ └── application.js
│ └── stylesheets
│ │ ├── application.scss
│ │ ├── base
│ │ ├── _animations.scss
│ │ ├── _base.scss
│ │ ├── _buttons.scss
│ │ ├── _extends.scss
│ │ ├── _fonts.scss
│ │ ├── _forms.scss
│ │ ├── _helpers.scss
│ │ ├── _media.scss
│ │ ├── _mixins.scss
│ │ ├── _tables.scss
│ │ ├── _typography.scss
│ │ └── _variables.scss
│ │ ├── components
│ │ ├── _allowance.scss
│ │ ├── _app-footer.scss
│ │ ├── _app-nav.scss
│ │ ├── _avatar.scss
│ │ ├── _badge.scss
│ │ ├── _button.scss
│ │ ├── _code.scss
│ │ ├── _components.scss
│ │ ├── _icon.scss
│ │ ├── _inline-flash.scss
│ │ ├── _logo.scss
│ │ ├── _plan-markers.scss
│ │ ├── _pricing.scss
│ │ └── _toggle-switch.scss
│ │ ├── marketing.scss
│ │ ├── marketing
│ │ ├── components
│ │ │ ├── _app-footer.scss
│ │ │ ├── _app-nav.scss
│ │ │ ├── _button.scss
│ │ │ ├── _page-hero.scss
│ │ │ ├── _page-section.scss
│ │ │ ├── _plan.scss
│ │ │ └── _text-partition.scss
│ │ ├── elements
│ │ │ ├── _layout.scss
│ │ │ └── _typography.scss
│ │ ├── generic
│ │ │ ├── _fonts.scss
│ │ │ └── _reset.scss
│ │ ├── objects
│ │ │ ├── _container.scss
│ │ │ ├── _grid.scss
│ │ │ └── _list-bare.scss
│ │ ├── settings
│ │ │ ├── _breakpoints.scss
│ │ │ ├── _color.scss
│ │ │ ├── _controls.scss
│ │ │ ├── _transitions.scss
│ │ │ └── _typography.scss
│ │ └── utilities
│ │ │ ├── _font-weight.scss
│ │ │ ├── _line-height.scss
│ │ │ ├── _margin.scss
│ │ │ └── _text-align.scss
│ │ ├── pages
│ │ ├── _builds.scss
│ │ ├── _pages.scss
│ │ ├── _tier_change.scss
│ │ ├── accounts
│ │ │ ├── _accounts.scss
│ │ │ └── _show.scss
│ │ └── repos
│ │ │ ├── _index.scss
│ │ │ ├── _onboarding.scss
│ │ │ ├── _organization.scss
│ │ │ ├── _repos.scss
│ │ │ ├── _syncing.scss
│ │ │ └── _tools.scss
│ │ └── utilities
│ │ ├── _clearfix.scss
│ │ ├── _line-height.scss
│ │ ├── _margin.scss
│ │ └── _utilities.scss
├── controllers
│ ├── accounts_controller.rb
│ ├── activations_controller.rb
│ ├── admin
│ │ ├── application_controller.rb
│ │ ├── blacklisted_pull_requests_controller.rb
│ │ ├── job_failures_controller.rb
│ │ ├── masquerades_controller.rb
│ │ └── owners_controller.rb
│ ├── application_controller.rb
│ ├── builds_controller.rb
│ ├── credit_cards_controller.rb
│ ├── deactivations_controller.rb
│ ├── deleted_subscriptions_controller.rb
│ ├── errors_controller.rb
│ ├── github_events_controller.rb
│ ├── home_controller.rb
│ ├── owners_controller.rb
│ ├── pages_controller.rb
│ ├── plans_controller.rb
│ ├── rebuilds_controller.rb
│ ├── repo_syncs_controller.rb
│ ├── repos_controller.rb
│ ├── sessions_controller.rb
│ ├── setups_controller.rb
│ ├── subscriptions_controller.rb
│ └── users_controller.rb
├── dashboards
│ ├── blacklisted_pull_request_dashboard.rb
│ ├── dashboard_manifest.rb
│ ├── job_failure_dashboard.rb
│ └── owner_dashboard.rb
├── helpers
│ ├── admin
│ │ └── job_failures_helper.rb
│ ├── analytics_helper.rb
│ ├── application_helper.rb
│ ├── flashes_helper.rb
│ └── repos_helper.rb
├── javascript
│ ├── __tests__
│ │ ├── RepoToolsAdd.test.jsx
│ │ ├── __snapshots__
│ │ │ ├── RepoToolsAdd.test.jsx.snap
│ │ │ ├── empty_repo_list-test.jsx.snap
│ │ │ ├── notify_tier_change-test.jsx.snap
│ │ │ ├── organization-test.jsx.snap
│ │ │ ├── organization_list-test.jsx.snap
│ │ │ ├── populated_repo_list-test.jsx.snap
│ │ │ ├── repo-test.jsx.snap
│ │ │ ├── repo_activation_button-test.jsx.snap
│ │ │ ├── repo_allowance-test.jsx.snap
│ │ │ ├── repo_deactivation_button-test.jsx.snap
│ │ │ ├── repo_list-test.jsx.snap
│ │ │ ├── repo_tools-test.jsx.snap
│ │ │ ├── repo_tools_refresh-test.jsx.snap
│ │ │ ├── repo_tools_search-test.jsx.snap
│ │ │ ├── repos_container-test.jsx.snap
│ │ │ ├── repos_sync_spinner-test.jsx.snap
│ │ │ ├── repos_view-test.jsx.snap
│ │ │ ├── tier_plan-test.jsx.snap
│ │ │ ├── update_account_credit_card-test.jsx.snap
│ │ │ ├── update_account_email-test.jsx.snap
│ │ │ ├── update_account_email_message-test.jsx.snap
│ │ │ └── upgrade_subscription_link-test.jsx.snap
│ │ ├── empty_repo_list-test.jsx
│ │ ├── notify_tier_change-test.jsx
│ │ ├── organization-test.jsx
│ │ ├── organization_list-test.jsx
│ │ ├── populated_repo_list-test.jsx
│ │ ├── repo-test.jsx
│ │ ├── repo_activation_button-test.jsx
│ │ ├── repo_allowance-test.jsx
│ │ ├── repo_deactivation_button-test.jsx
│ │ ├── repo_list-test.jsx
│ │ ├── repo_tools-test.jsx
│ │ ├── repo_tools_refresh-test.jsx
│ │ ├── repo_tools_search-test.jsx
│ │ ├── repos_container-test.jsx
│ │ ├── repos_sync_spinner-test.jsx
│ │ ├── repos_view-test.jsx
│ │ ├── setup.js
│ │ ├── tier_plan-test.jsx
│ │ ├── update_account_credit_card-test.jsx
│ │ ├── update_account_email-test.jsx
│ │ ├── update_account_email_message-test.jsx
│ │ └── upgrade_subscription_link-test.jsx
│ ├── components
│ │ ├── NotifyTierChange.jsx
│ │ ├── NotifyTierChange
│ │ │ └── components
│ │ │ │ ├── App.jsx
│ │ │ │ ├── TierPlan.jsx
│ │ │ │ └── UpgradeSubscriptionLink.jsx
│ │ ├── ReposContainer.jsx
│ │ ├── ReposContainer
│ │ │ └── components
│ │ │ │ ├── App.jsx
│ │ │ │ ├── Organization.jsx
│ │ │ │ ├── Organization
│ │ │ │ ├── OrganizationConfiguration.jsx
│ │ │ │ ├── RepoList.jsx
│ │ │ │ └── RepoList
│ │ │ │ │ ├── EmptyRepoList.jsx
│ │ │ │ │ └── PopulatedRepoList.jsx
│ │ │ │ ├── Repo.jsx
│ │ │ │ ├── Repo
│ │ │ │ ├── RepoActivationButton.jsx
│ │ │ │ └── RepoDeactivationButton.jsx
│ │ │ │ ├── RepoAllowance.jsx
│ │ │ │ ├── RepoTools.jsx
│ │ │ │ ├── RepoTools
│ │ │ │ ├── RepoToolsAdd.jsx
│ │ │ │ ├── RepoToolsRefresh.jsx
│ │ │ │ └── RepoToolsSearch.jsx
│ │ │ │ ├── ReposView.jsx
│ │ │ │ └── ReposView
│ │ │ │ ├── NoReposMessage.jsx
│ │ │ │ ├── OrganizationsList.jsx
│ │ │ │ └── ReposSyncSpinner.jsx
│ │ ├── UpdateAccountCreditCard.jsx
│ │ ├── UpdateAccountCreditCard
│ │ │ └── components
│ │ │ │ └── App.jsx
│ │ ├── UpdateAccountEmail.jsx
│ │ └── UpdateAccountEmail
│ │ │ └── components
│ │ │ ├── App.jsx
│ │ │ └── UpdateAccountEmailMessage.jsx
│ ├── modules
│ │ ├── Ajax.js
│ │ └── Utils.js
│ └── packs
│ │ ├── notify_tier_change.js
│ │ ├── repos_container.js
│ │ ├── update_account_credit_card.js
│ │ └── update_account_email.js
├── jobs
│ ├── application_job.rb
│ ├── buildable.rb
│ ├── completed_file_review_job.rb
│ ├── large_build_job.rb
│ ├── linters_job.rb
│ ├── repo_synchronization_job.rb
│ └── small_build_job.rb
├── mailers
│ └── .gitkeep
├── models
│ ├── .gitkeep
│ ├── analytics.rb
│ ├── app_token.rb
│ ├── application_record.rb
│ ├── blacklisted_pull_request.rb
│ ├── build.rb
│ ├── commit.rb
│ ├── commit_file.rb
│ ├── commit_status.rb
│ ├── config
│ │ ├── base.rb
│ │ ├── coffeelint.rb
│ │ ├── credo.rb
│ │ ├── erb_lint.rb
│ │ ├── eslint.rb
│ │ ├── flake8.rb
│ │ ├── flog.rb
│ │ ├── golint.rb
│ │ ├── haml_lint.rb
│ │ ├── jshint.rb
│ │ ├── json_with_comments.rb
│ │ ├── parser.rb
│ │ ├── parser_error.rb
│ │ ├── phpcs.rb
│ │ ├── reek.rb
│ │ ├── remark.rb
│ │ ├── rubocop.rb
│ │ ├── sass_lint.rb
│ │ ├── scss_lint.rb
│ │ ├── serializer.rb
│ │ ├── shellcheck.rb
│ │ ├── slim_lint.rb
│ │ ├── stylelint.rb
│ │ ├── swiftlint.rb
│ │ ├── tslint.rb
│ │ └── unsupported.rb
│ ├── config_content.rb
│ ├── config_content
│ │ └── remote.rb
│ ├── empty_commit.rb
│ ├── file_review.rb
│ ├── github_auth.rb
│ ├── github_event.rb
│ ├── github_plan.rb
│ ├── home.rb
│ ├── hound_config.rb
│ ├── ignore_file.rb
│ ├── job_failure.rb
│ ├── line.rb
│ ├── linter
│ │ ├── base.rb
│ │ ├── coffeelint.rb
│ │ ├── credo.rb
│ │ ├── erb_lint.rb
│ │ ├── eslint.rb
│ │ ├── flake8.rb
│ │ ├── flog.rb
│ │ ├── golint.rb
│ │ ├── haml_lint.rb
│ │ ├── jshint.rb
│ │ ├── phpcs.rb
│ │ ├── reek.rb
│ │ ├── remark.rb
│ │ ├── rubocop.rb
│ │ ├── sass_lint.rb
│ │ ├── scss_lint.rb
│ │ ├── shellcheck.rb
│ │ ├── slim_lint.rb
│ │ ├── stylelint.rb
│ │ ├── swiftlint.rb
│ │ └── tslint.rb
│ ├── membership.rb
│ ├── metered_stripe_plan.rb
│ ├── missing_owner.rb
│ ├── owner.rb
│ ├── patch.rb
│ ├── payload.rb
│ ├── payment_gateway_customer.rb
│ ├── payment_gateway_subscription.rb
│ ├── plan.rb
│ ├── plan_selector.rb
│ ├── pull_request.rb
│ ├── repo.rb
│ ├── review_body.rb
│ ├── stripe_plan.rb
│ ├── subscription.rb
│ ├── unchanged_line.rb
│ ├── user.rb
│ └── violation.rb
├── policies
│ └── commenting_policy.rb
├── presenters
│ ├── account_page.rb
│ ├── monthly_line_item.rb
│ └── plan_presenter.rb
├── queries
│ ├── recent_builds_by_repo_query.rb
│ └── repos_with_membership_or_subscription_query.rb
├── serializers
│ ├── plan_serializer.rb
│ ├── repo_serializer.rb
│ └── user_serializer.rb
├── services
│ ├── build_config.rb
│ ├── build_owner_hound_config.rb
│ ├── complete_build.rb
│ ├── complete_file_review.rb
│ ├── create_repo.rb
│ ├── deactivate_repo.rb
│ ├── delete_subscriptions.rb
│ ├── normalize_config.rb
│ ├── rebuild_pull_request.rb
│ ├── repo_subscriber.rb
│ ├── repo_synchronization.rb
│ ├── report_invalid_config.rb
│ ├── resolve_config_aliases.rb
│ ├── resolve_config_conflicts.rb
│ ├── review_files.rb
│ ├── sanitize_ini_file.rb
│ ├── start_build.rb
│ ├── submit_review.rb
│ ├── update_github_plans.rb
│ └── update_repo_status.rb
└── views
│ ├── accounts
│ └── show.haml
│ ├── admin
│ ├── application
│ │ └── _navigation.html.haml
│ └── job_failures
│ │ └── index.haml
│ ├── application
│ ├── _analytics.haml
│ ├── _app_footer_marketing.html.erb
│ ├── _app_nav.haml
│ ├── _app_nav_marketing.html.erb
│ ├── _footer.haml
│ ├── _settings.haml
│ └── update_billing.html.erb
│ ├── builds
│ └── index.html.haml
│ ├── errors
│ ├── 404.haml
│ ├── 422.haml
│ └── 500.haml
│ ├── home
│ ├── _code_quality.html.erb
│ ├── _companies.html.erb
│ ├── _company.html.erb
│ ├── _guides.html.erb
│ ├── _hero.html.erb
│ ├── _how_it_works.html.erb
│ ├── _language.html.erb
│ ├── _languages.html.erb
│ ├── _pricing.html.erb
│ ├── _review.html.erb
│ ├── _security.html.erb
│ └── index.html.erb
│ ├── layouts
│ ├── application.html.erb
│ └── marketing.html.erb
│ ├── plans
│ ├── _open_source.html.haml
│ ├── _private.html.haml
│ └── index.html.haml
│ ├── repos
│ ├── _github_app.haml
│ ├── _onboarding.haml
│ └── index.haml
│ └── shared
│ └── _plan_vertical.html.haml
├── babel.config.js
├── bin
├── bundle
├── bundler
├── deploy
├── production-status
├── rails
├── rake
├── rspec
├── setup
├── update
├── webpack
└── webpack-dev-server
├── circle.yml
├── config.ru
├── config
├── application.rb
├── autoprefixer.yml
├── boot.rb
├── cable.yml
├── companies.yml
├── database.yml
├── environment.rb
├── environments
│ ├── development.rb
│ ├── production.rb
│ ├── staging.rb
│ └── test.rb
├── initializers
│ ├── analytics_ruby.rb
│ ├── application_controller_renderer.rb
│ ├── assets.rb
│ ├── backtrace_silencers.rb
│ ├── constants.rb
│ ├── content_security_policy.rb
│ ├── cookies_serializer.rb
│ ├── filter_parameter_logging.rb
│ ├── high_voltage.rb
│ ├── inflections.rb
│ ├── inline_svg.rb
│ ├── mime_types.rb
│ ├── new_framework_defaults.rb
│ ├── new_framework_defaults_6_0.rb
│ ├── omni_auth.rb
│ ├── sentry.rb
│ ├── session_store.rb
│ ├── sidekiq.rb
│ ├── stripe.rb
│ └── wrap_parameters.rb
├── languages.yml
├── locales
│ └── en.yml
├── puma.rb
├── routes.rb
├── secrets.yml
├── sidekiq.yml
├── spring.rb
├── storage.yml
├── webpack
│ ├── development.js
│ ├── environment.js
│ ├── production.js
│ └── test.js
└── webpacker.yml
├── db
├── migrate
│ ├── 20121229003013_create_diesel_clearance_users.rb
│ ├── 20121229180151_add_github_username_to_users.rb
│ ├── 20130104231409_remove_clearance_columns_from_user.rb
│ ├── 20130105203331_add_remember_token_to_users.rb
│ ├── 20130105203459_change_github_username_null_constraint.rb
│ ├── 20130121230036_add_repos.rb
│ ├── 20130123030036_add_user_id_to_repos.rb
│ ├── 20130123030453_add_github_token_to_users.rb
│ ├── 20130228235811_add_hook_id_to_repos.rb
│ ├── 20130322200146_add_name_to_repos.rb
│ ├── 20130322220351_add_full_github_name_to_repos.rb
│ ├── 20130524154558_create_delayed_jobs.rb
│ ├── 20130614194012_add_uniqueness_constraint_to_repos.rb
│ ├── 20130621203400_create_builds.rb
│ ├── 20131212190854_create_memberships.rb
│ ├── 20131212192202_remove_user_id_from_repos.rb
│ ├── 20140120165453_add_uuid_to_builds.rb
│ ├── 20140120234919_add_unique_index_for_uuid.rb
│ ├── 20140327142505_remove_github_token_from_users.rb
│ ├── 20140417231853_add_timestamps_to_repo.rb
│ ├── 20140417232711_add_timestamps_to_memberships.rb
│ ├── 20140418142740_add_index_for_remember_token_to_users.rb
│ ├── 20140418144357_add_index_for_github_id_to_repos.rb
│ ├── 20140419160756_add_refreshing_repos_to_users.rb
│ ├── 20140419235609_add_index_for_active_to_repos.rb
│ ├── 20140421183905_add_email_address_to_users.rb
│ ├── 20140421191318_remove_name_from_repos.rb
│ ├── 20140425212732_add_private_to_repos.rb
│ ├── 20140425235458_add_in_organization_to_repos.rb
│ ├── 20140711223828_create_subscriptions.rb
│ ├── 20140711223913_add_stripe_customer_id_to_users.rb
│ ├── 20140711223957_add_stripe_subscription_id_to_subscriptions.rb
│ ├── 20140808163844_add_null_constraint_to_subscriptions_for_timestamps.rb
│ ├── 20140808164104_add_index_for_user_id.rb
│ ├── 20140808195409_add_deleted_at_and_price_to_subscriptions.rb
│ ├── 20140808202140_remove_unique_index_from_subscriptions_for_repo_id.rb
│ ├── 20140926163029_add_pull_request_info_to_builds.rb
│ ├── 20141107174021_create_violations.rb
│ ├── 20141210070831_drop_delayed_jobs_table.rb
│ ├── 20141211224528_create_owners.rb
│ ├── 20141219224840_inactivate_repos_without_privacy_or_org_info.rb
│ ├── 20150108003045_add_uniqueness_on_full_github_name_to_repos.rb
│ ├── 20150123224104_add_owner_to_repos.rb
│ ├── 20150130051749_add_index_to_memberships_for_user_id.rb
│ ├── 20150213132248_backfill_timestamps_add_null_constraints.rb
│ ├── 20150217090319_add_foreign_key_memberships.rb
│ ├── 20150220174603_create_style_config.rb
│ ├── 20150225001118_add_unique_index_on_subscription_repo_id.rb
│ ├── 20150303212157_add_unique_constraint_to_memberships.rb
│ ├── 20150306184045_add_token_to_users.rb
│ ├── 20150425204143_create_file_reviews.rb
│ ├── 20150526214750_associate_violations_with_file_reviews.rb
│ ├── 20150612231322_add_utm_source_to_users.rb
│ ├── 20150619222741_add_payload_to_builds.rb
│ ├── 20150710220040_associate_user_to_build.rb
│ ├── 20150725013530_create_bulk_customers.rb
│ ├── 20150728154011_add_token_scopes_to_users.rb
│ ├── 20150731212842_remove_repo_limit_constraint_on_bulk_customers.rb
│ ├── 20151019123908_remove_style_config.rb
│ ├── 20151204174817_add_admin_to_memberships.rb
│ ├── 20151216235118_add_violations_count_to_builds.rb
│ ├── 20160314122828_add_linter_name_to_file_reviews.rb
│ ├── 20160430171015_backfill_linter_names_add_null_constraint.rb
│ ├── 20160513002940_add_github_id_uniqueness_on_repos.rb
│ ├── 20160513205551_add_index_for_builds_pull_request_number_and_commit_sha.rb
│ ├── 20160608183021_add_config_to_owner.rb
│ ├── 20160624165658_update_thoughtbot_owner_configs.rb
│ ├── 20160712203443_create_blacklisted_pull_requests.rb
│ ├── 20161021212135_add_default_to_violations_count_on_builds.rb
│ ├── 20161021231021_rename_columns_on_repos_and_users.rb
│ ├── 20170505220736_add_error_to_file_reviews.rb
│ ├── 20170911205516_add_whitelisted_to_owners.rb
│ ├── 20180510051449_add_marketplace_plan_id_to_owners.rb
│ ├── 20180613230247_add_stripe_subscription_id_to_owners.rb
│ ├── 20180620003423_add_installation_id_to_users_and_repos.rb
│ └── 20191212043041_add_index_for_created_at_to_builds.rb
├── schema.rb
└── seeds.rb
├── doc
├── PRIVACY.md
├── SECURITY.md
└── TERMS.md
├── jest.config.js
├── lib
├── assets
│ └── .gitkeep
├── github_api.rb
├── migrate_stripe_subscription.rb
├── redirect_to_configuration.rb
└── tasks
│ ├── billing.rake
│ ├── bundler_audit.rake
│ ├── js.rake
│ ├── plan.rake
│ ├── repo.rake
│ ├── user.rake
│ └── webpack.rake
├── log
└── .gitkeep
├── package.json
├── postcss.config.js
├── public
├── apple-touch-icon-precomposed.png
├── favicon.ico
└── robots.txt
├── spec
├── controllers
│ ├── accounts_controller_spec.rb
│ ├── activations_controller_spec.rb
│ ├── builds_controller_spec.rb
│ ├── credit_cards_controller_spec.rb
│ ├── deactivations_controller_spec.rb
│ ├── home_controller_spec.rb
│ ├── repo_syncs_controller_spec.rb
│ ├── repos_controller_spec.rb
│ ├── sessions_controller_spec.rb
│ ├── subscriptions_controller_spec.rb
│ └── users_controller_spec.rb
├── factories.rb
├── features
│ ├── account_spec.rb
│ ├── admin_authorization_spec.rb
│ ├── admin_masquerades_spec.rb
│ ├── error_pages_spec.rb
│ ├── job_failures_spec.rb
│ ├── plans_spec.rb
│ ├── repo_list_spec.rb
│ ├── user_activates_a_repo_spec.rb
│ ├── user_authentication_spec.rb
│ ├── user_deactivates_a_repo_spec.rb
│ └── user_rebuilds_a_pull_request_spec.rb
├── helpers
│ └── analytics_helper_spec.rb
├── jobs
│ ├── buildable_spec.rb
│ ├── completed_file_review_job_spec.rb
│ ├── large_build_job_spec.rb
│ ├── repo_synchronization_job_spec.rb
│ └── small_build_job_spec.rb
├── lib
│ ├── github_api_spec.rb
│ └── tasks
│ │ └── repo_spec.rb
├── models
│ ├── build_spec.rb
│ ├── commit_file_spec.rb
│ ├── commit_spec.rb
│ ├── commit_status_spec.rb
│ ├── config
│ │ ├── base_spec.rb
│ │ ├── coffeelint_spec.rb
│ │ ├── credo_spec.rb
│ │ ├── erb_lint_spec.rb
│ │ ├── eslint_spec.rb
│ │ ├── flake8_spec.rb
│ │ ├── golint_spec.rb
│ │ ├── haml_lint_spec.rb
│ │ ├── jshint_spec.rb
│ │ ├── json_with_comments_spec.rb
│ │ ├── phpcs_spec.rb
│ │ ├── remark_spec.rb
│ │ ├── rubocop_spec.rb
│ │ ├── sass_lint_spec.rb
│ │ ├── scss_lint_spec.rb
│ │ ├── swiftlint_spec.rb
│ │ └── tslint_spec.rb
│ ├── config_content
│ │ └── remote_spec.rb
│ ├── config_content_spec.rb
│ ├── file_review_spec.rb
│ ├── github_auth_spec.rb
│ ├── github_event_spec.rb
│ ├── home_spec.rb
│ ├── hound_config_spec.rb
│ ├── ignore_file_spec.rb
│ ├── job_failure_spec.rb
│ ├── linter
│ │ ├── base_spec.rb
│ │ ├── coffeelint_spec.rb
│ │ ├── credo_spec.rb
│ │ ├── erb_lint_spec.rb
│ │ ├── eslint_spec.rb
│ │ ├── flake8_spec.rb
│ │ ├── flog_spec.rb
│ │ ├── go_spec.rb
│ │ ├── haml_lint_spec.rb
│ │ ├── jshint_spec.rb
│ │ ├── phpcs_spec.rb
│ │ ├── reek_spec.rb
│ │ ├── remark_spec.rb
│ │ ├── rubocop_spec.rb
│ │ ├── sass_lint_spec.rb
│ │ ├── scss_lint_spec.rb
│ │ ├── shellcheck_spec.rb
│ │ ├── slim_lint_spec.rb
│ │ ├── stylelint_spec.rb
│ │ ├── swiftlint_spec.rb
│ │ └── tslint_spec.rb
│ ├── membership_spec.rb
│ ├── missing_owner_spec.rb
│ ├── owner_spec.rb
│ ├── patch_spec.rb
│ ├── payload_spec.rb
│ ├── payment_gateway_customer_spec.rb
│ ├── payment_gateway_subscription_spec.rb
│ ├── plan_selector_spec.rb
│ ├── pull_request_spec.rb
│ ├── repo_spec.rb
│ ├── review_body_spec.rb
│ ├── stripe_plan_spec.rb
│ ├── subscription_spec.rb
│ ├── user_spec.rb
│ └── violation_spec.rb
├── policies
│ └── commenting_policy_spec.rb
├── presenters
│ ├── account_page_spec.rb
│ ├── monthly_line_item_spec.rb
│ └── plan_presenter_spec.rb
├── queries
│ ├── recent_builds_by_repo_query_spec.rb
│ └── repos_with_membership_or_subscription_query_spec.rb
├── rails_helper.rb
├── requests
│ ├── admin
│ │ └── masquerades_spec.rb
│ ├── builds_spec.rb
│ ├── github_events_spec.rb
│ └── subscriptions_spec.rb
├── serializers
│ ├── plan_serializer_spec.rb
│ ├── repo_serializer_spec.rb
│ └── user_serializer_spec.rb
├── services
│ ├── build_config_spec.rb
│ ├── build_owner_hound_config_spec.rb
│ ├── complete_build_spec.rb
│ ├── complete_file_review_spec.rb
│ ├── delete_subscriptions_spec.rb
│ ├── normalize_config_spec.rb
│ ├── rebuild_pull_request_spec.rb
│ ├── repo_deactivator_spec.rb
│ ├── repo_subscriber_spec.rb
│ ├── repo_synchronization_spec.rb
│ ├── report_invalid_config_spec.rb
│ ├── resolve_config_aliases_spec.rb
│ ├── resolve_config_conflicts_spec.rb
│ ├── review_files_spec.rb
│ ├── sanitize_ini_file_spec.rb
│ ├── start_build_spec.rb
│ ├── submit_review_spec.rb
│ ├── update_github_plans_spec.rb
│ └── update_repo_status_spec.rb
├── spec_helper.rb
├── support
│ ├── background_jobs.rb
│ ├── fake_github.rb
│ ├── features.rb
│ ├── fixtures
│ │ ├── config_contents.json
│ │ ├── contents_with_violations.json
│ │ ├── failed_hook.json
│ │ ├── github_app_uninstall.json
│ │ ├── github_hook_creation_response.json
│ │ ├── github_installation_repositories_added.json
│ │ ├── github_installation_repositories_removed.json
│ │ ├── github_marketplace_purchase_cancelled.json
│ │ ├── github_patch.diff
│ │ ├── github_repos_response_for_jimtom.json
│ │ ├── github_repos_response_for_jimtom_page2.json
│ │ ├── github_status_response.json
│ │ ├── legacy_coffeescript.json
│ │ ├── legacy_rubocop_config.yml
│ │ ├── patch.diff
│ │ ├── pull_request_comments.json
│ │ ├── pull_request_event_with_many_files.json
│ │ ├── pull_request_files.json
│ │ ├── pull_request_opened_event.json
│ │ ├── pull_request_synchronize_event.json
│ │ ├── push_event.json
│ │ ├── stripe_customer_create.json
│ │ ├── stripe_customer_find.json
│ │ ├── stripe_customer_find_with_subscriptions.json
│ │ ├── stripe_customer_find_with_tier2_subscription.json
│ │ ├── stripe_customer_find_with_tier3_subscription.json
│ │ ├── stripe_customer_update.json
│ │ ├── stripe_discounted_amount_subscription.json
│ │ ├── stripe_discounted_percent_subscription.json
│ │ ├── stripe_individual_subscription.json
│ │ ├── stripe_org_subscription.json
│ │ ├── stripe_private_subscription.json
│ │ ├── stripe_subscription_create.json
│ │ ├── stripe_subscription_delete.json
│ │ ├── stripe_subscription_find.json
│ │ ├── stripe_subscription_update.json
│ │ ├── thoughtbot_coffeescript.json
│ │ ├── thoughtbot_rubocop_config.yml
│ │ └── zen_payload.json
│ ├── helpers
│ │ ├── analytics_helper.rb
│ │ ├── authentication_helper.rb
│ │ ├── commit_file_helper.rb
│ │ ├── config_helper.rb
│ │ ├── fake_analytics_ruby.rb
│ │ ├── github_api_helper.rb
│ │ ├── https_helper.rb
│ │ ├── linter_helper.rb
│ │ ├── oauth_helper.rb
│ │ └── stripe_api_helper.rb
│ ├── linters.rb
│ ├── matchers
│ │ ├── have_tracked_matcher.rb
│ │ └── violate_matcher.rb
│ ├── shared_examples.rb
│ ├── user_on_page.rb
│ └── wait_for_ajax.rb
└── views
│ └── application
│ └── _analytics.haml_spec.rb
├── vendor
├── assets
│ ├── javascripts
│ │ ├── .gitkeep
│ │ └── namespaced.js
│ └── stylesheets
│ │ ├── .gitkeep
│ │ └── _reset.scss
└── plugins
│ └── .gitkeep
└── yarn.lock
/.browserslistrc:
--------------------------------------------------------------------------------
1 | defaults
2 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {},
3 | "env": {
4 | "es6": true,
5 | "node": true,
6 | "browser": true
7 | },
8 | "extends": "eslint:recommended"
9 | }
10 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .env.local
2 | .foreman
3 | /.bundle
4 | /db/*.sqlite3
5 | /log/*.log
6 | /tmp
7 |
8 | node_modules/
9 | yarn-debug.log*
10 | yarn-error.log*
11 | public/packs
12 | public/packs-test
13 | /public/packs
14 | /public/packs-test
15 | /node_modules
16 |
--------------------------------------------------------------------------------
/.hound.yml:
--------------------------------------------------------------------------------
1 | fail_on_violations: true
2 |
3 | rubocop:
4 | config_file: .rubocop.yml
5 | version: 0.75.0
6 |
7 | scss:
8 | enabled: false
9 |
10 | stylelint:
11 | config_file: .stylelintrc.json
12 | enabled: true
13 |
14 | eslint:
15 | enabled: true
16 | version: 5.7.0
17 | config_file: .eslintrc
18 |
--------------------------------------------------------------------------------
/.rspec:
--------------------------------------------------------------------------------
1 | --color
2 | --require spec_helper
3 |
--------------------------------------------------------------------------------
/.ruby-version:
--------------------------------------------------------------------------------
1 | 2.7.1
2 |
--------------------------------------------------------------------------------
/.stylelintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@thoughtbot/stylelint-config"
3 | }
4 |
--------------------------------------------------------------------------------
/Procfile:
--------------------------------------------------------------------------------
1 | web: bundle exec puma -C config/puma.rb
2 | webpack_dev_server: ./bin/webpack-dev-server
3 | worker: bundle exec sidekiq
4 | release: bundle exec rake db:migrate
5 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env rake
2 | # Add your own tasks in files placed in lib/tasks ending in .rake,
3 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
4 |
5 | require File.expand_path('../config/application', __FILE__)
6 |
7 | Houndapp::Application.load_tasks
8 |
9 | task(:default).clear
10 |
11 | if defined? RSpec
12 | task(:spec).clear
13 | RSpec::Core::RakeTask.new(:spec) do |t|
14 | t.verbose = false
15 | end
16 | end
17 |
18 | task default: ["assets:clobber", "spec", "js:spec", "bundler:audit"]
19 |
--------------------------------------------------------------------------------
/app/assets/config/manifest.js:
--------------------------------------------------------------------------------
1 | //= link_tree ../images
2 | //= link_directory ../javascripts .js
3 | //= link_directory ../stylesheets .css
4 |
5 | //= link administrate/application.css
6 | //= link administrate/application.js
7 |
--------------------------------------------------------------------------------
/app/assets/images/companies/18f.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/houndci/hound/a50bf519bcdd42817ebb35bb19c268893e7a9909/app/assets/images/companies/18f.png
--------------------------------------------------------------------------------
/app/assets/images/companies/airbrake.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/houndci/hound/a50bf519bcdd42817ebb35bb19c268893e7a9909/app/assets/images/companies/airbrake.png
--------------------------------------------------------------------------------
/app/assets/images/companies/bbc-news.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/houndci/hound/a50bf519bcdd42817ebb35bb19c268893e7a9909/app/assets/images/companies/bbc-news.png
--------------------------------------------------------------------------------
/app/assets/images/companies/blendtec.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/houndci/hound/a50bf519bcdd42817ebb35bb19c268893e7a9909/app/assets/images/companies/blendtec.png
--------------------------------------------------------------------------------
/app/assets/images/companies/codeship.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/houndci/hound/a50bf519bcdd42817ebb35bb19c268893e7a9909/app/assets/images/companies/codeship.png
--------------------------------------------------------------------------------
/app/assets/images/companies/dropbox.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/houndci/hound/a50bf519bcdd42817ebb35bb19c268893e7a9909/app/assets/images/companies/dropbox.png
--------------------------------------------------------------------------------
/app/assets/images/companies/elastic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/houndci/hound/a50bf519bcdd42817ebb35bb19c268893e7a9909/app/assets/images/companies/elastic.png
--------------------------------------------------------------------------------
/app/assets/images/companies/envoy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/houndci/hound/a50bf519bcdd42817ebb35bb19c268893e7a9909/app/assets/images/companies/envoy.png
--------------------------------------------------------------------------------
/app/assets/images/companies/envy-labs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/houndci/hound/a50bf519bcdd42817ebb35bb19c268893e7a9909/app/assets/images/companies/envy-labs.png
--------------------------------------------------------------------------------
/app/assets/images/companies/gitlab.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/houndci/hound/a50bf519bcdd42817ebb35bb19c268893e7a9909/app/assets/images/companies/gitlab.png
--------------------------------------------------------------------------------
/app/assets/images/companies/heroku.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/houndci/hound/a50bf519bcdd42817ebb35bb19c268893e7a9909/app/assets/images/companies/heroku.png
--------------------------------------------------------------------------------
/app/assets/images/companies/hp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/houndci/hound/a50bf519bcdd42817ebb35bb19c268893e7a9909/app/assets/images/companies/hp.png
--------------------------------------------------------------------------------
/app/assets/images/companies/intuit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/houndci/hound/a50bf519bcdd42817ebb35bb19c268893e7a9909/app/assets/images/companies/intuit.png
--------------------------------------------------------------------------------
/app/assets/images/companies/joomla.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/houndci/hound/a50bf519bcdd42817ebb35bb19c268893e7a9909/app/assets/images/companies/joomla.png
--------------------------------------------------------------------------------
/app/assets/images/companies/lumosity.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/houndci/hound/a50bf519bcdd42817ebb35bb19c268893e7a9909/app/assets/images/companies/lumosity.png
--------------------------------------------------------------------------------
/app/assets/images/companies/meundies.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/houndci/hound/a50bf519bcdd42817ebb35bb19c268893e7a9909/app/assets/images/companies/meundies.png
--------------------------------------------------------------------------------
/app/assets/images/companies/middleman.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/houndci/hound/a50bf519bcdd42817ebb35bb19c268893e7a9909/app/assets/images/companies/middleman.jpg
--------------------------------------------------------------------------------
/app/assets/images/companies/mixpanel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/houndci/hound/a50bf519bcdd42817ebb35bb19c268893e7a9909/app/assets/images/companies/mixpanel.png
--------------------------------------------------------------------------------
/app/assets/images/companies/mozilla.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/houndci/hound/a50bf519bcdd42817ebb35bb19c268893e7a9909/app/assets/images/companies/mozilla.png
--------------------------------------------------------------------------------
/app/assets/images/companies/namely.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/houndci/hound/a50bf519bcdd42817ebb35bb19c268893e7a9909/app/assets/images/companies/namely.png
--------------------------------------------------------------------------------
/app/assets/images/companies/netguru.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/houndci/hound/a50bf519bcdd42817ebb35bb19c268893e7a9909/app/assets/images/companies/netguru.png
--------------------------------------------------------------------------------
/app/assets/images/companies/percona.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/houndci/hound/a50bf519bcdd42817ebb35bb19c268893e7a9909/app/assets/images/companies/percona.png
--------------------------------------------------------------------------------
/app/assets/images/companies/rackspace.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/houndci/hound/a50bf519bcdd42817ebb35bb19c268893e7a9909/app/assets/images/companies/rackspace.png
--------------------------------------------------------------------------------
/app/assets/images/companies/rails-girls.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/houndci/hound/a50bf519bcdd42817ebb35bb19c268893e7a9909/app/assets/images/companies/rails-girls.png
--------------------------------------------------------------------------------
/app/assets/images/companies/salesforce.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/houndci/hound/a50bf519bcdd42817ebb35bb19c268893e7a9909/app/assets/images/companies/salesforce.png
--------------------------------------------------------------------------------
/app/assets/images/companies/shipio.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/houndci/hound/a50bf519bcdd42817ebb35bb19c268893e7a9909/app/assets/images/companies/shipio.png
--------------------------------------------------------------------------------
/app/assets/images/companies/shubox.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/houndci/hound/a50bf519bcdd42817ebb35bb19c268893e7a9909/app/assets/images/companies/shubox.jpg
--------------------------------------------------------------------------------
/app/assets/images/companies/suse.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/houndci/hound/a50bf519bcdd42817ebb35bb19c268893e7a9909/app/assets/images/companies/suse.png
--------------------------------------------------------------------------------
/app/assets/images/companies/thoughtbot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/houndci/hound/a50bf519bcdd42817ebb35bb19c268893e7a9909/app/assets/images/companies/thoughtbot.png
--------------------------------------------------------------------------------
/app/assets/images/companies/treehouse.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/houndci/hound/a50bf519bcdd42817ebb35bb19c268893e7a9909/app/assets/images/companies/treehouse.png
--------------------------------------------------------------------------------
/app/assets/images/companies/turing-school.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/houndci/hound/a50bf519bcdd42817ebb35bb19c268893e7a9909/app/assets/images/companies/turing-school.png
--------------------------------------------------------------------------------
/app/assets/images/companies/untuit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/houndci/hound/a50bf519bcdd42817ebb35bb19c268893e7a9909/app/assets/images/companies/untuit.png
--------------------------------------------------------------------------------
/app/assets/images/companies/wealthsimple.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/houndci/hound/a50bf519bcdd42817ebb35bb19c268893e7a9909/app/assets/images/companies/wealthsimple.png
--------------------------------------------------------------------------------
/app/assets/images/companies/wildbit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/houndci/hound/a50bf519bcdd42817ebb35bb19c268893e7a9909/app/assets/images/companies/wildbit.png
--------------------------------------------------------------------------------
/app/assets/images/companies/yahoo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/houndci/hound/a50bf519bcdd42817ebb35bb19c268893e7a9909/app/assets/images/companies/yahoo.png
--------------------------------------------------------------------------------
/app/assets/images/companies/zendesk.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/houndci/hound/a50bf519bcdd42817ebb35bb19c268893e7a9909/app/assets/images/companies/zendesk.png
--------------------------------------------------------------------------------
/app/assets/images/hatch-gray.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/app/assets/images/hatch-purple.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/app/assets/images/home/hound_flow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/houndci/hound/a50bf519bcdd42817ebb35bb19c268893e7a9909/app/assets/images/home/hound_flow.png
--------------------------------------------------------------------------------
/app/assets/images/home/hound_flow.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/houndci/hound/a50bf519bcdd42817ebb35bb19c268893e7a9909/app/assets/images/home/hound_flow.webp
--------------------------------------------------------------------------------
/app/assets/images/home/review_example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/houndci/hound/a50bf519bcdd42817ebb35bb19c268893e7a9909/app/assets/images/home/review_example.png
--------------------------------------------------------------------------------
/app/assets/images/home/review_example.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/houndci/hound/a50bf519bcdd42817ebb35bb19c268893e7a9909/app/assets/images/home/review_example.webp
--------------------------------------------------------------------------------
/app/assets/images/icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/assets/images/languages/elixir.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/assets/images/languages/php.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/assets/images/languages/ruby.svg:
--------------------------------------------------------------------------------
1 |
19 |
--------------------------------------------------------------------------------
/app/assets/images/languages/shell.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/app/assets/images/languages/typescript.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/assets/images/madi.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/houndci/hound/a50bf519bcdd42817ebb35bb19c268893e7a9909/app/assets/images/madi.jpg
--------------------------------------------------------------------------------
/app/assets/images/select-arrow.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/app/assets/javascripts/application.js:
--------------------------------------------------------------------------------
1 | //= require jquery3
2 | //= require jquery_ujs
3 | //= require namespaced
4 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/application.scss:
--------------------------------------------------------------------------------
1 | @import "bourbon";
2 | @import "neat";
3 |
4 | @import "normalize.css/normalize";
5 |
6 | @import "base/base";
7 | @import "components/components";
8 | @import "pages/pages";
9 | @import "utilities/utilities";
10 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/base/_animations.scss:
--------------------------------------------------------------------------------
1 | @keyframes loading {
2 | 0% {
3 | opacity: 0.15;
4 | transform: scale(0.95);
5 | }
6 |
7 | 50% {
8 | opacity: 1;
9 | transform: scale(1);
10 | }
11 |
12 | 100% {
13 | opacity: 0.15;
14 | transform: scale(0.95);
15 | }
16 | }
17 |
18 | @keyframes pulse-in {
19 | 0% {
20 | opacity: 0;
21 | transform: scale(0.95);
22 | }
23 |
24 | 50% {
25 | transform: scale(1.02);
26 | }
27 |
28 | 100% {
29 | opacity: 1;
30 | transform: scale(1);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/base/_base.scss:
--------------------------------------------------------------------------------
1 | @import "variables";
2 |
3 | @import "animations";
4 | @import "buttons";
5 | @import "extends";
6 | @import "mixins";
7 | @import "fonts";
8 | @import "forms";
9 | @import "helpers";
10 | @import "media";
11 | @import "tables";
12 | @import "typography";
13 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/base/_buttons.scss:
--------------------------------------------------------------------------------
1 | #{$all-buttons} {
2 | font-family: $font-family-default;
3 | }
4 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/base/_extends.scss:
--------------------------------------------------------------------------------
1 | %content-aside {
2 | @include span-columns(3 of 12, table);
3 | @include omega;
4 | padding: 0;
5 | padding-right: 2em;
6 | vertical-align: top;
7 | }
8 |
9 | %content-main {
10 | @include span-columns(9 of 12, table);
11 | border-left: 1px solid $base-border-color;
12 | padding: 0;
13 | padding-left: 2em;
14 | }
15 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/base/_forms.scss:
--------------------------------------------------------------------------------
1 | #{$all-text-inputs},
2 | select {
3 | font-family: $font-family-default;
4 | }
5 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/base/_helpers.scss:
--------------------------------------------------------------------------------
1 | .content {
2 | @include outer-container;
3 | }
4 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/base/_media.scss:
--------------------------------------------------------------------------------
1 | figure {
2 | margin: 0;
3 | }
4 |
5 | img,
6 | picture {
7 | margin: 0;
8 | max-width: 100%;
9 | }
10 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/base/_mixins.scss:
--------------------------------------------------------------------------------
1 | @mixin content-frame {
2 | @include clearfix;
3 | border: 1px solid $base-border-color;
4 | border-radius: 6px;
5 | padding: 2em;
6 | }
7 |
8 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/base/_tables.scss:
--------------------------------------------------------------------------------
1 | $_table-heading-background-color: rgb(245, 245, 245);
2 |
3 | table {
4 | border-collapse: collapse;
5 | width: 100%;
6 |
7 | tr {
8 | border: 1px solid $base-border-color;
9 | }
10 |
11 | th,
12 | td {
13 | text-align: left;
14 | }
15 |
16 | td {
17 | padding: 1em 1.1em;
18 | }
19 |
20 | th {
21 | background-color: $_table-heading-background-color;
22 | font-size: 0.8em;
23 | font-weight: $font-weight-medium;
24 | padding: 1em 1.4em;
25 | text-transform: uppercase;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/components/_allowance.scss:
--------------------------------------------------------------------------------
1 | .allowance {
2 | border: 2px solid $base-accent-color;
3 | border-radius: 100px;
4 | color: $base-accent-color;
5 | line-height: 40px;
6 | margin-right: $base-spacing;
7 | padding: 0 $small-spacing;
8 |
9 | strong {
10 | border-left: 1px dotted $base-accent-color;
11 | margin-left: $small-spacing;
12 | padding-left: $small-spacing;
13 | }
14 |
15 | &.stressed {
16 | color: $red;
17 | }
18 |
19 | &.large {
20 | display: inline-block;
21 | font-size: 1.3em;
22 | line-height: 60px;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/components/_app-footer.scss:
--------------------------------------------------------------------------------
1 | .app-footer {
2 | padding: $large-spacing $base-spacing;
3 | text-align: center;
4 | }
5 |
6 | .app-footer__nav {
7 | margin-bottom: $base-spacing;
8 | }
9 |
10 | .app-footer__nav-link:not(:first-of-type) {
11 | margin-left: $base-spacing;
12 | }
13 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/components/_avatar.scss:
--------------------------------------------------------------------------------
1 | $_avatar-size: 2rem;
2 |
3 | .avatar {
4 | @include size($_avatar-size);
5 | background-color: $light-gray;
6 | border-radius: 50%;
7 | display: inline-block;
8 | overflow: hidden;
9 | position: relative;
10 | vertical-align: middle;
11 |
12 | &::after {
13 | @include position(absolute, 0);
14 | border-radius: 50%;
15 | box-shadow: inset 0 0 0 1px rgba($black, 0.1);
16 | content: "";
17 | pointer-events: none;
18 | }
19 |
20 | img {
21 | @include size($_avatar-size);
22 | object-fit: cover;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/components/_badge.scss:
--------------------------------------------------------------------------------
1 | .badge {
2 | border: 1px solid $light-gray;
3 | border-radius: 3px;
4 | color: $base-font-color;
5 | display: inline-block;
6 | font-size: $font-size-small;
7 | line-height: 1;
8 | margin: 0;
9 | padding: 0.125em 0.25em;
10 | }
11 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/components/_code.scss:
--------------------------------------------------------------------------------
1 | .code,
2 | .code-block {
3 | background-color: $code-background-color;
4 | display: inline-block;
5 | font-family: $font-family-code;
6 | font-style: normal;
7 | }
8 |
9 | .code-block {
10 | border: 1px solid $code-background-color;
11 | border-radius: 3px;
12 | display: block;
13 | margin: 1em 0 2em;
14 | padding: 1em;
15 | white-space: pre-wrap;
16 | }
17 |
18 | .code-inline {
19 | border-radius: 2px;
20 | padding: 0.1em;
21 | }
22 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/components/_components.scss:
--------------------------------------------------------------------------------
1 | @import "allowance";
2 | @import "app-footer";
3 | @import "app-nav";
4 | @import "avatar";
5 | @import "badge";
6 | @import "button";
7 | @import "code";
8 | @import "icon";
9 | @import "inline-flash";
10 | @import "logo";
11 | @import "plan-markers";
12 | @import "pricing";
13 | @import "toggle-switch";
14 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/components/_icon.scss:
--------------------------------------------------------------------------------
1 | .icon {
2 | @include size(1em);
3 | fill: currentcolor;
4 | }
5 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/components/_inline-flash.scss:
--------------------------------------------------------------------------------
1 | .inline-flash {
2 | margin: 0;
3 | }
4 |
5 | .inline-flash--visible {
6 | display: block;
7 | }
8 |
9 | .inline-flash--success {
10 | color: darken($green, 20%);
11 | }
12 |
13 | .inline-flash--error {
14 | color: $red;
15 | }
16 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/components/_logo.scss:
--------------------------------------------------------------------------------
1 | $_logo-aspect-ratio: 217 / 50;
2 | $_logo-height: 2.5rem;
3 | $_logo-color: $purple;
4 |
5 | .logo {
6 | color: $_logo-color;
7 | height: $_logo-height;
8 | transition: color $base-transition-timing $base-easing;
9 | width: $_logo-height * $_logo-aspect-ratio;
10 |
11 | &:hover {
12 | color: shade($_logo-color, 15%);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/components/_plan-markers.scss:
--------------------------------------------------------------------------------
1 | .plan-marker {
2 | border-radius: 100px;
3 | display: inline-block;
4 | font-size: 0.8em;
5 | margin-bottom: $small-spacing;
6 | padding: 0 1em;
7 | text-transform: uppercase;
8 | }
9 |
10 | .plan-marker--current {
11 | background-color: lighten($gold, 50%);
12 | border: 1px solid lighten($gold, 3%);
13 | color: darken($gold, 10%);
14 | }
15 |
16 | .plan-marker--new {
17 | background-color: lighten($green, 10%);
18 | border: 1px solid darken($green, 20%);
19 | color: darken($green, 40%);
20 | }
21 |
22 | .plan-marker__wrapper {
23 | display: block;
24 | }
25 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/marketing/components/_app-footer.scss:
--------------------------------------------------------------------------------
1 | .c-app-footer {
2 | padding: 5rem 2rem;
3 | text-align: center;
4 | }
5 |
6 | .c-app-footer__list {
7 | --link-text-decoration: none;
8 | --link-text-decoration-hover: underline;
9 |
10 | align-items: center;
11 | display: flex;
12 | gap: 2rem;
13 | justify-content: center;
14 | margin-bottom: 1rem;
15 | }
16 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/marketing/components/_page-hero.scss:
--------------------------------------------------------------------------------
1 | .c-page-hero {
2 | padding-bottom: 6rem;
3 | padding-top: 6rem;
4 | text-align: center;
5 | }
6 |
7 | .c-page-hero__heading {
8 | font-size: 3.5rem;
9 | }
10 |
11 | .c-page-hero__lede {
12 | font-size: 1.25rem;
13 | margin-top: 2rem;
14 | }
15 |
16 | .c-page-hero__cta {
17 | margin-top: 2rem;
18 | }
19 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/marketing/components/_plan.scss:
--------------------------------------------------------------------------------
1 | .c-plan {
2 | background-image: image-url("hatch-gray.svg");
3 | background-repeat: repeat-y;
4 | border: 2px solid currentColor;
5 | border-radius: 5px;
6 | display: flex;
7 | flex-direction: column;
8 | padding: 1.25rem 1.25rem 1.25rem calc(1.25rem + 10px);
9 | }
10 |
11 | .c-plan--private {
12 | background-image: image-url("hatch-purple.svg");
13 | color: $purple;
14 | }
15 |
16 | .c-plan__title {
17 | font-size: 1.5rem;
18 | font-weight: $font-weight-medium;
19 | line-height: var(--line-height-heading);
20 | margin-bottom: 0.25rem;
21 | }
22 |
23 | .c-plan__allowance {
24 | font-size: 0.875rem;
25 | margin-bottom: 1rem;
26 | }
27 |
28 | .c-plan__price {
29 | margin-top: auto;
30 | }
31 |
32 | .c-plan__dollar-amount {
33 | font-size: 1.5rem;
34 | }
35 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/marketing/components/_text-partition.scss:
--------------------------------------------------------------------------------
1 | $_text-partition-stroke-height: 0.125em;
2 |
3 | .c-text-partition {
4 | align-items: center;
5 | column-gap: 1em;
6 | display: grid;
7 | grid-template-columns: 23% auto 23%;
8 | justify-content: center;
9 | letter-spacing: 0.125em;
10 | text-transform: uppercase;
11 | width: 100%;
12 |
13 | &::before,
14 | &::after {
15 | background-color: var(--color-border);
16 | content: "";
17 | height: $_text-partition-stroke-height;
18 | position: relative;
19 | top: -($_text-partition-stroke-height / 2);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/marketing/elements/_layout.scss:
--------------------------------------------------------------------------------
1 | html {
2 | background-color: var(--color-background);
3 |
4 | @media (prefers-reduced-motion: no-preference) {
5 | scroll-behavior: smooth;
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/marketing/generic/_reset.scss:
--------------------------------------------------------------------------------
1 | html {
2 | box-sizing: border-box;
3 | }
4 |
5 | *,
6 | *::before,
7 | *::after {
8 | box-sizing: inherit;
9 | }
10 |
11 | body {
12 | line-height: inherit;
13 | }
14 |
15 | img {
16 | height: auto;
17 | max-width: 100%;
18 | }
19 |
20 | [hidden] {
21 | // stylelint-disable-next-line declaration-no-important
22 | display: none !important;
23 | }
24 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/marketing/objects/_container.scss:
--------------------------------------------------------------------------------
1 | .o-container {
2 | margin-left: auto;
3 | margin-right: auto;
4 | max-width: 70rem;
5 | padding-left: 1.25rem;
6 | padding-right: 1.25rem;
7 | }
8 |
9 | .o-container--small {
10 | max-width: 48rem;
11 | }
12 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/marketing/objects/_list-bare.scss:
--------------------------------------------------------------------------------
1 | .o-list-bare {
2 | list-style-type: none;
3 | margin: 0;
4 | padding: 0;
5 | }
6 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/marketing/settings/_breakpoints.scss:
--------------------------------------------------------------------------------
1 | $breakpoints: (
2 | "small": 30em,
3 | "medium": 48em,
4 | "large": 60em,
5 | );
6 |
7 | $breakpoint-small: map-get($breakpoints, "small");
8 | $breakpoint-medium: map-get($breakpoints, "medium");
9 | $breakpoint-large: map-get($breakpoints, "large");
10 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/marketing/settings/_color.scss:
--------------------------------------------------------------------------------
1 | $black: #000;
2 | $white: #fff;
3 |
4 | $gray: #6a737d;
5 | $gray-dark: #586069;
6 |
7 | $purple: #a873d1;
8 | $purple-light: #fbf8fd;
9 | $purple-dark: #8f62b2;
10 |
11 | html {
12 | --color-background: #{$white};
13 | --color-border: #dcdcdc;
14 |
15 | --color-text: #{$gray};
16 | --color-text-heading: #{$gray-dark};
17 | --color-text-link: var(--color-text);
18 | --color-text-link-hover: #{$purple};
19 | }
20 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/marketing/settings/_controls.scss:
--------------------------------------------------------------------------------
1 | $control-block-size: 3rem;
2 | $control-block-size-large: 4rem;
3 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/marketing/settings/_transitions.scss:
--------------------------------------------------------------------------------
1 | html {
2 | @media (prefers-reduced-motion: no-preference) {
3 | --transition-duration: 200ms;
4 | --transition-timing: cubic-bezier(0.39, 0.575, 0.565, 1);
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/marketing/settings/_typography.scss:
--------------------------------------------------------------------------------
1 | $font-family-body: "Tofino", ui-sans-serif, system-ui, -apple-system, sans-serif;
2 | $font-family-heading: $font-family-body;
3 | $font-family-code: "Menlo", "Monaco", ui-monospace, monospace;
4 |
5 | $font-weights: (
6 | "light": 300,
7 | "normal": 400,
8 | "medium": 500,
9 | );
10 |
11 | $font-weight-light: map-get($font-weights, "light");
12 | $font-weight-normal: map-get($font-weights, "normal");
13 | $font-weight-medium: map-get($font-weights, "medium");
14 |
15 | html {
16 | --line-height-body: 1.5;
17 | --line-height-heading: 1.2;
18 |
19 | --link-text-decoration: underline;
20 | --link-text-decoration-hover: none;
21 | }
22 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/marketing/utilities/_font-weight.scss:
--------------------------------------------------------------------------------
1 | @each $name, $weight in $font-weights {
2 | .u-font-weight-#{$name} {
3 | font-weight: $weight;
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/marketing/utilities/_line-height.scss:
--------------------------------------------------------------------------------
1 | .u-line-height-0 {
2 | line-height: 0;
3 | }
4 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/marketing/utilities/_margin.scss:
--------------------------------------------------------------------------------
1 | .u-margin-bottom-2 {
2 | margin-bottom: 0.5rem;
3 | }
4 |
5 | .u-margin-top-2 {
6 | margin-top: 1.5rem;
7 | }
8 |
9 | .u-margin-top-4 {
10 | margin-top: 3rem;
11 | }
12 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/marketing/utilities/_text-align.scss:
--------------------------------------------------------------------------------
1 | .u-text-align-start {
2 | text-align: left;
3 |
4 | @supports (text-align: start) {
5 | text-align: start;
6 | }
7 | }
8 |
9 | .u-text-align-center {
10 | text-align: center;
11 | }
12 |
13 | .u-text-align-end {
14 | text-align: left;
15 |
16 | @supports (text-align: end) {
17 | text-align: end;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/pages/_pages.scss:
--------------------------------------------------------------------------------
1 | @import "accounts/accounts";
2 | @import "repos/repos";
3 | @import "builds";
4 | @import "tier_change";
5 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/pages/accounts/_accounts.scss:
--------------------------------------------------------------------------------
1 | @import "show";
2 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/pages/repos/_repos.scss:
--------------------------------------------------------------------------------
1 | @import "index";
2 | @import "onboarding";
3 | @import "organization";
4 | @import "syncing";
5 | @import "tools";
6 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/pages/repos/_syncing.scss:
--------------------------------------------------------------------------------
1 | .repos-syncing {
2 | background-color: $white;
3 | padding: 4em 1em;
4 | text-align: center;
5 |
6 | .dot {
7 | @include size(24px);
8 | animation: loading 1.3s ease-out;
9 | animation-iteration-count: infinite;
10 | background-color: rgba($green, 0.1);
11 | border: 1px solid $green;
12 | border-radius: 100%;
13 | display: inline-block;
14 | margin: 0 1.2em;
15 | opacity: 0.1;
16 |
17 | &:nth-child(2) {
18 | animation-delay: 0.2s;
19 | }
20 |
21 | &:nth-child(3) {
22 | animation-delay: 0.4s;
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/utilities/_clearfix.scss:
--------------------------------------------------------------------------------
1 | .clearfix {
2 | @include clearfix;
3 | }
4 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/utilities/_line-height.scss:
--------------------------------------------------------------------------------
1 | .line-height-zero {
2 | line-height: 0;
3 | }
4 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/utilities/_margin.scss:
--------------------------------------------------------------------------------
1 | .margin-right-x-small {
2 | margin-right: $xsmall-spacing !important;
3 | }
4 |
5 | .margin-left-small {
6 | margin-left: $small-spacing !important;
7 | }
8 |
9 | .margin-top-large {
10 | margin-top: $large-spacing;
11 | }
12 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/utilities/_utilities.scss:
--------------------------------------------------------------------------------
1 | @import "clearfix";
2 | @import "line-height";
3 | @import "margin";
4 |
--------------------------------------------------------------------------------
/app/controllers/activations_controller.rb:
--------------------------------------------------------------------------------
1 | class ActivationsController < ApplicationController
2 | class CannotActivatePaidRepo < StandardError; end
3 |
4 | before_action :ensure_repo_allowed
5 |
6 | def create
7 | repo.activate
8 |
9 | render json: repo, status: :created
10 | end
11 |
12 | private
13 |
14 | def ensure_repo_allowed
15 | if repo.private?
16 | raise CannotActivatePaidRepo
17 | end
18 | end
19 |
20 | def repo
21 | @repo ||= current_user.repos.find(params[:repo_id])
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/app/controllers/admin/application_controller.rb:
--------------------------------------------------------------------------------
1 | module Admin
2 | class ApplicationController < Administrate::ApplicationController
3 | before_action :authenticate_admin
4 |
5 | helper_method :navigation_resources
6 |
7 | private
8 |
9 | def navigation_resources
10 | Administrate::Namespace.new(namespace).resources.select do |resource|
11 | DashboardManifest::DASHBOARDS.include?(resource.name)
12 | end
13 | end
14 |
15 | def authenticate_admin
16 | unless github_admin?
17 | redirect_to :root
18 | end
19 | end
20 |
21 | def github_admin?
22 | current_user &&
23 | Hound::ADMIN_GITHUB_USERNAMES.include?(current_user.username)
24 | end
25 |
26 | def current_user
27 | @current_user ||= User.find_by(remember_token: session[:remember_token])
28 | end
29 | end
30 | end
31 |
--------------------------------------------------------------------------------
/app/controllers/admin/blacklisted_pull_requests_controller.rb:
--------------------------------------------------------------------------------
1 | module Admin
2 | class BlacklistedPullRequestsController < Admin::ApplicationController
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/app/controllers/admin/job_failures_controller.rb:
--------------------------------------------------------------------------------
1 | module Admin
2 | class JobFailuresController < Admin::ApplicationController
3 | def index
4 | render locals: { resources: JobFailure.grouped }
5 | end
6 |
7 | def destroy
8 | JobFailure.remove(params[:ids])
9 |
10 | redirect_to action: :index
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/app/controllers/admin/masquerades_controller.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 | module Admin
3 | class MasqueradesController < Admin::ApplicationController
4 | def show
5 | requested_user = User.find_by(username: params[:username])
6 | session[:masqueraded_user_id] = requested_user.id
7 |
8 | redirect_to repos_path
9 | end
10 |
11 | def destroy
12 | session.delete(:masqueraded_user_id)
13 |
14 | redirect_to repos_path
15 | end
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/app/controllers/admin/owners_controller.rb:
--------------------------------------------------------------------------------
1 | module Admin
2 | class OwnersController < Admin::ApplicationController
3 | before_action :default_params
4 |
5 | private
6 |
7 | def default_params
8 | params[:order] ||= :whitelisted
9 | params[:direction] ||= :desc
10 | end
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/app/controllers/credit_cards_controller.rb:
--------------------------------------------------------------------------------
1 | class CreditCardsController < ApplicationController
2 | class CreditCardUpdateFailed < StandardError; end
3 |
4 | def update
5 | customer = PaymentGatewayCustomer.new(current_user)
6 |
7 | if customer.update_card(params[:card_token])
8 | head 200
9 | else
10 | report_error
11 | head 422
12 | end
13 | end
14 |
15 | private
16 |
17 | def report_error
18 | exception = CreditCardUpdateFailed.new(
19 | "Credit card failed to update for user #{current_user.id}"
20 | )
21 | Raven.capture_exception(exception)
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/app/controllers/deleted_subscriptions_controller.rb:
--------------------------------------------------------------------------------
1 | class DeletedSubscriptionsController < ApplicationController
2 | skip_before_action :authenticate, only: :create
3 |
4 | def create
5 | DeleteSubscriptions.call(params)
6 |
7 | head :ok
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/app/controllers/errors_controller.rb:
--------------------------------------------------------------------------------
1 | class ErrorsController < ApplicationController
2 | skip_before_action :authenticate
3 |
4 | def show
5 | render view, status: status_code
6 | end
7 |
8 | private
9 |
10 | def view
11 | status_code.to_s
12 | end
13 |
14 | def status_code
15 | params[:code] || 500
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/app/controllers/home_controller.rb:
--------------------------------------------------------------------------------
1 | class HomeController < ApplicationController
2 | skip_before_action :authenticate, only: [:index]
3 |
4 | before_action :redirect_to_repos, if: :signed_in?
5 |
6 | layout "marketing"
7 |
8 | def index
9 | @home = Home.new(current_user || guest)
10 | @companies = YAML.safe_load(File.read("config/companies.yml"))
11 | @languages = YAML.safe_load(File.read("config/languages.yml"))
12 | end
13 |
14 | private
15 |
16 | def redirect_to_repos
17 | redirect_to repos_path
18 | end
19 |
20 | def guest
21 | User.new
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/app/controllers/owners_controller.rb:
--------------------------------------------------------------------------------
1 | class OwnersController < ApplicationController
2 | def update
3 | if owner.update(owner_params)
4 | render json: owner
5 | end
6 | end
7 |
8 | private
9 |
10 | def owner
11 | @_owner ||= Owner.find(params[:id])
12 | end
13 |
14 | def owner_params
15 | params.require(:owner).permit(:config_enabled, :config_repo)
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/app/controllers/pages_controller.rb:
--------------------------------------------------------------------------------
1 | class PagesController < ApplicationController
2 | include HighVoltage::StaticPage
3 |
4 | skip_before_action :authenticate
5 | end
6 |
--------------------------------------------------------------------------------
/app/controllers/rebuilds_controller.rb:
--------------------------------------------------------------------------------
1 | class RebuildsController < ApplicationController
2 | def create
3 | RebuildPullRequest.call(
4 | repo: repo,
5 | pull_request_number: pull_request_number,
6 | )
7 | flash[:notice] = t(".success")
8 |
9 | redirect_to builds_path
10 | end
11 |
12 | private
13 |
14 | def repo
15 | current_user.repos.find(params[:repo_id])
16 | end
17 |
18 | def pull_request_number
19 | params.require(:rebuild)[:pull_request_number]
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/app/controllers/repo_syncs_controller.rb:
--------------------------------------------------------------------------------
1 | class RepoSyncsController < ApplicationController
2 | def create
3 | unless current_user.refreshing_repos?
4 | current_user.update(refreshing_repos: true)
5 |
6 | RepoSynchronizationJob.perform_async(current_user.id)
7 | end
8 |
9 | head 201
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/app/controllers/repos_controller.rb:
--------------------------------------------------------------------------------
1 | class ReposController < ApplicationController
2 | def index
3 | respond_to do |format|
4 | format.html
5 | format.json do
6 | repos = ReposWithMembershipOrSubscriptionQuery.call(current_user)
7 |
8 | render json: repos
9 | end
10 | end
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/app/controllers/setups_controller.rb:
--------------------------------------------------------------------------------
1 | class SetupsController < ApplicationController
2 | prepend_before_action :save_installation_id
3 |
4 | def show
5 | if session[:installation_id]
6 | ids = current_user.installation_ids | [session[:installation_id].to_i]
7 | current_user.update!(installation_ids: ids, repos: [])
8 | end
9 |
10 | redirect_to repos_path
11 | end
12 |
13 | private
14 |
15 | def save_installation_id
16 | if params[:installation_id]
17 | session[:installation_id] = params[:installation_id]
18 | end
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/app/controllers/users_controller.rb:
--------------------------------------------------------------------------------
1 | class UsersController < ApplicationController
2 | def show
3 | render json: current_user
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/app/dashboards/blacklisted_pull_request_dashboard.rb:
--------------------------------------------------------------------------------
1 | require "administrate/base_dashboard"
2 |
3 | class BlacklistedPullRequestDashboard < Administrate::BaseDashboard
4 | ATTRIBUTE_TYPES = {
5 | full_repo_name: Field::String,
6 | pull_request_number: Field::Number,
7 | }.freeze
8 |
9 | COLLECTION_ATTRIBUTES = [
10 | :full_repo_name,
11 | :pull_request_number,
12 | ].freeze
13 |
14 | SHOW_PAGE_ATTRIBUTES = [
15 | :full_repo_name,
16 | :pull_request_number,
17 | ].freeze
18 |
19 | FORM_ATTRIBUTES = [
20 | :full_repo_name,
21 | :pull_request_number,
22 | ].freeze
23 | end
24 |
--------------------------------------------------------------------------------
/app/dashboards/dashboard_manifest.rb:
--------------------------------------------------------------------------------
1 | class DashboardManifest
2 | DASHBOARDS = [
3 | :blacklisted_pull_requests,
4 | :job_failures,
5 | :owners,
6 | ]
7 | ROOT_DASHBOARD = :owners
8 | end
9 |
--------------------------------------------------------------------------------
/app/dashboards/job_failure_dashboard.rb:
--------------------------------------------------------------------------------
1 | require "administrate/custom_dashboard"
2 |
3 | class JobFailureDashboard < Administrate::CustomDashboard
4 | resource "JobFailure"
5 | end
6 |
--------------------------------------------------------------------------------
/app/helpers/admin/job_failures_helper.rb:
--------------------------------------------------------------------------------
1 | module Admin
2 | module JobFailuresHelper
3 | def latest_failed_at(job_failures)
4 | latest_failed = job_failures.max_by(&:failed_at).failed_at
5 |
6 | Time.zone.at(latest_failed).strftime("%l:%M%P, %b %e")
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/app/helpers/analytics_helper.rb:
--------------------------------------------------------------------------------
1 | module AnalyticsHelper
2 | def analytics?
3 | Hound::SEGMENT_KEY.present?
4 | end
5 |
6 | def identify_hash(user = current_user)
7 | {
8 | created: user.created_at,
9 | email: user.email,
10 | username: user.username,
11 | user_id: user.id,
12 | active_repo_ids: user.active_repos.ids,
13 | }
14 | end
15 |
16 | def intercom_hash(user = current_user)
17 | {
18 | "Intercom" => {
19 | userHash: OpenSSL::HMAC.hexdigest(
20 | "sha256",
21 | Hound::INTERCOM_API_SECRET,
22 | user.id.to_s
23 | )
24 | }
25 | }
26 | end
27 |
28 | def campaign_hash
29 | {
30 | context: {
31 | campaign: session[:campaign_params]
32 | }
33 | }
34 | end
35 | end
36 |
--------------------------------------------------------------------------------
/app/helpers/application_helper.rb:
--------------------------------------------------------------------------------
1 | module ApplicationHelper
2 | def avatar_url(user)
3 | "https://github.com/#{user.username}.png?size=64"
4 | end
5 |
6 | def display_onboarding?
7 | !current_user.has_active_repos? || current_user.builds.none?
8 | end
9 |
10 | def new_window_options(options = {})
11 | options.merge(target: "_blank", rel: "noopener noreferrer")
12 | end
13 |
14 | def svg(file_name, options = {})
15 | if options[:title].present?
16 | options[:aria] = true
17 | else
18 | options[:aria_hidden] = true
19 | end
20 |
21 | inline_svg_tag(file_name, options)
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/app/helpers/flashes_helper.rb:
--------------------------------------------------------------------------------
1 | module FlashesHelper
2 | def user_facing_flashes
3 | flash.to_hash.slice("notice", "error")
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/app/helpers/repos_helper.rb:
--------------------------------------------------------------------------------
1 | module ReposHelper
2 | def switch(repo)
3 | current_state = repo.active ? 'off' : 'on'
4 | link_to(current_state, '#', { 'data-github-id' => repo.github_id })
5 | end
6 |
7 | def has_active_repos?(user)
8 | user.repos.any? && user.has_active_repos?
9 | end
10 |
11 | def has_no_builds?(user)
12 | user.has_active_repos? && user.builds.none?
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/app/javascript/__tests__/RepoToolsAdd.test.jsx:
--------------------------------------------------------------------------------
1 | import RepoToolsAdd from '../components/ReposContainer/components/RepoTools/RepoToolsAdd';
2 |
3 | describe('RepoToolsAdd component', () => {
4 | it('renders the button to add repos', () => {
5 | const wrapper = shallow(
7 | There was a problem updating your email. Please try again. 8 |
9 | `; 10 | 11 | exports[`renders appropriately 2`] = ` 12 |16 | Email address updated! 17 |
18 | `; 19 | -------------------------------------------------------------------------------- /app/javascript/__tests__/__snapshots__/upgrade_subscription_link-test.jsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`renders appropriately 1`] = ` 4 | 9 | Upgrade 10 | 11 | `; 12 | -------------------------------------------------------------------------------- /app/javascript/__tests__/empty_repo_list-test.jsx: -------------------------------------------------------------------------------- 1 | import EmptyRepoList from '../components/ReposContainer/components/Organization/RepoList/EmptyRepoList'; 2 | 3 | it('renders an empty unordered list', () => { 4 | const wrapper = shallow( 5 |10 | Email address updated! 11 |
12 | ); 13 | } else { 14 | return ( 15 |16 | There was a problem updating your email. Please try again. 17 |
18 | ); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/javascript/modules/Utils.js: -------------------------------------------------------------------------------- 1 | export function getCSRFfromHead() { 2 | if (process.env.NODE_ENV === 'test') { 3 | return "csrf_token"; 4 | } else { 5 | return document.querySelector("meta[name=csrf-token]").content; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /app/javascript/packs/notify_tier_change.js: -------------------------------------------------------------------------------- 1 | import WebpackerReact from 'webpacker-react'; 2 | import NotifyTierChange from '../components/NotifyTierChange'; 3 | 4 | WebpackerReact.setup({NotifyTierChange}); 5 | -------------------------------------------------------------------------------- /app/javascript/packs/repos_container.js: -------------------------------------------------------------------------------- 1 | import WebpackerReact from 'webpacker-react'; 2 | import ReposContainer from '../components/ReposContainer'; 3 | 4 | WebpackerReact.setup({ReposContainer}); 5 | -------------------------------------------------------------------------------- /app/javascript/packs/update_account_credit_card.js: -------------------------------------------------------------------------------- 1 | import WebpackerReact from 'webpacker-react'; 2 | import UpdateAccountCreditCard from '../components/UpdateAccountCreditCard'; 3 | 4 | WebpackerReact.setup({UpdateAccountCreditCard}); 5 | -------------------------------------------------------------------------------- /app/javascript/packs/update_account_email.js: -------------------------------------------------------------------------------- 1 | import WebpackerReact from 'webpacker-react'; 2 | import UpdateAccountEmail from '../components/UpdateAccountEmail'; 3 | 4 | WebpackerReact.setup({UpdateAccountEmail}); 5 | -------------------------------------------------------------------------------- /app/jobs/application_job.rb: -------------------------------------------------------------------------------- 1 | class ApplicationJob 2 | include Sidekiq::Worker 3 | end 4 | -------------------------------------------------------------------------------- /app/jobs/completed_file_review_job.rb: -------------------------------------------------------------------------------- 1 | class CompletedFileReviewJob < ApplicationJob 2 | sidekiq_options queue: :medium 3 | 4 | # The following parameters are required for this job to run; 5 | # - filename 6 | # - commit_sha 7 | # - pull_request_number 8 | # - patch 9 | # - violations (e.g. [{ line: 123, message: "WAT" }]) 10 | def perform(attributes) 11 | CompleteFileReview.call(attributes) 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /app/jobs/large_build_job.rb: -------------------------------------------------------------------------------- 1 | class LargeBuildJob < ApplicationJob 2 | include Buildable 3 | 4 | sidekiq_options queue: :low 5 | end 6 | -------------------------------------------------------------------------------- /app/jobs/linters_job.rb: -------------------------------------------------------------------------------- 1 | class LintersJob < ApplicationJob 2 | sidekiq_options queue: :linters 3 | end 4 | -------------------------------------------------------------------------------- /app/jobs/repo_synchronization_job.rb: -------------------------------------------------------------------------------- 1 | class RepoSynchronizationJob < ApplicationJob 2 | sidekiq_options queue: :high 3 | 4 | def perform(user_id) 5 | user = User.find(user_id) 6 | synchronization = RepoSynchronization.new(user) 7 | synchronization.start 8 | user.update(refreshing_repos: false) 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /app/jobs/small_build_job.rb: -------------------------------------------------------------------------------- 1 | class SmallBuildJob < ApplicationJob 2 | include Buildable 3 | 4 | sidekiq_options queue: :medium 5 | end 6 | -------------------------------------------------------------------------------- /app/mailers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/houndci/hound/a50bf519bcdd42817ebb35bb19c268893e7a9909/app/mailers/.gitkeep -------------------------------------------------------------------------------- /app/models/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/houndci/hound/a50bf519bcdd42817ebb35bb19c268893e7a9909/app/models/.gitkeep -------------------------------------------------------------------------------- /app/models/app_token.rb: -------------------------------------------------------------------------------- 1 | class AppToken 2 | def initialize 3 | private_pem = Hound::GITHUB_APP_PEM.gsub('\n', "\n") 4 | @private_key = OpenSSL::PKey::RSA.new(private_pem) 5 | end 6 | 7 | def generate 8 | issue_time = Time.now.to_i 9 | expiration_time = issue_time + (10 * 60 - 10) 10 | payload = { 11 | iat: issue_time, 12 | exp: expiration_time, 13 | iss: Hound::GITHUB_APP_ID, 14 | } 15 | 16 | JWT.encode(payload, @private_key, "RS256") 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /app/models/application_record.rb: -------------------------------------------------------------------------------- 1 | class ApplicationRecord < ActiveRecord::Base 2 | self.abstract_class = true 3 | end 4 | -------------------------------------------------------------------------------- /app/models/blacklisted_pull_request.rb: -------------------------------------------------------------------------------- 1 | class BlacklistedPullRequest < ApplicationRecord 2 | end 3 | -------------------------------------------------------------------------------- /app/models/build.rb: -------------------------------------------------------------------------------- 1 | class Build < ApplicationRecord 2 | belongs_to :repo 3 | belongs_to :user 4 | has_many :file_reviews, dependent: :destroy 5 | has_many :violations, through: :file_reviews 6 | 7 | before_create :generate_uuid 8 | 9 | validates :repo, presence: true 10 | 11 | delegate :name, to: :repo, prefix: true 12 | 13 | def completed? 14 | file_reviews.where(completed_at: nil).empty? 15 | end 16 | 17 | def github_auth 18 | @_github_auth ||= GitHubAuth.new(repo) 19 | end 20 | 21 | def review_errors 22 | file_reviews.where.not(error: [nil, ""]). 23 | pluck(Arel.sql("DISTINCT error")). 24 | uniq { |error| error.lines.first } 25 | end 26 | 27 | private 28 | 29 | def generate_uuid 30 | self.uuid = SecureRandom.uuid 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /app/models/commit.rb: -------------------------------------------------------------------------------- 1 | class Commit 2 | pattr_initialize :repo_name, :sha, :github 3 | attr_reader :repo_name, :sha 4 | 5 | def file_content(filename) 6 | contents = github.file_contents(repo_name, filename, sha) 7 | if contents && contents.content 8 | Base64.decode64(contents.content).force_encoding("UTF-8") 9 | else 10 | "" 11 | end 12 | rescue Octokit::NotFound 13 | "" 14 | rescue Octokit::Forbidden => exception 15 | if exception.errors.any? && exception.errors.first[:code] == "too_large" 16 | "" 17 | else 18 | raise exception 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /app/models/commit_file.rb: -------------------------------------------------------------------------------- 1 | class CommitFile 2 | attr_reader :filename, :commit, :patch 3 | 4 | def initialize(filename:, commit:, patch:) 5 | @filename = filename 6 | @commit = commit 7 | @patch = patch 8 | end 9 | 10 | def content 11 | commit.file_content(filename) 12 | end 13 | 14 | def line_at(line_number) 15 | changed_lines[line_number] || UnchangedLine.new 16 | end 17 | 18 | private 19 | 20 | def changed_lines 21 | @changed_lines ||= Patch.new(patch).changed_lines 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /app/models/config/coffeelint.rb: -------------------------------------------------------------------------------- 1 | module Config 2 | class Coffeelint < Base 3 | def serialize 4 | Serializer.json(content) 5 | end 6 | 7 | private 8 | 9 | def parse(file_content) 10 | json_with_comments = JsonWithComments.new(file_content) 11 | content_without_comments = json_with_comments.without_comments 12 | super(content_without_comments) 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /app/models/config/credo.rb: -------------------------------------------------------------------------------- 1 | module Config 2 | class Credo < Base 3 | def content 4 | @content ||= load_content 5 | end 6 | 7 | def serialize 8 | content 9 | end 10 | 11 | private 12 | 13 | def load_content 14 | if file_path 15 | commit.file_content(file_path) 16 | else 17 | "" 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /app/models/config/erb_lint.rb: -------------------------------------------------------------------------------- 1 | module Config 2 | class ErbLint < Base 3 | def serialize 4 | Serializer.yaml(content) 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/models/config/eslint.rb: -------------------------------------------------------------------------------- 1 | module Config 2 | class Eslint < Base 3 | def serialize 4 | Serializer.json(content) 5 | end 6 | 7 | private 8 | 9 | def parse(file_content) 10 | json_with_comments = JsonWithComments.new(file_content) 11 | content_without_comments = json_with_comments.without_comments 12 | super(content_without_comments) 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /app/models/config/flake8.rb: -------------------------------------------------------------------------------- 1 | module Config 2 | class Flake8 < Base 3 | def serialize 4 | Serializer.ini(content) 5 | end 6 | 7 | private 8 | 9 | def parse(file_content) 10 | content = SanitizeIniFile.call(file_content) 11 | Parser.ini(content) 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /app/models/config/flog.rb: -------------------------------------------------------------------------------- 1 | module Config 2 | class Flog < Base 3 | def serialize 4 | "" 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/models/config/golint.rb: -------------------------------------------------------------------------------- 1 | module Config 2 | class Golint < Base 3 | def serialize 4 | "" 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/models/config/haml_lint.rb: -------------------------------------------------------------------------------- 1 | module Config 2 | class HamlLint < Base 3 | def serialize 4 | Serializer.yaml(content) 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/models/config/jshint.rb: -------------------------------------------------------------------------------- 1 | module Config 2 | class Jshint < Base 3 | def serialize 4 | Serializer.json(content) 5 | end 6 | 7 | private 8 | 9 | def parse(content) 10 | Parser.json(content) 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /app/models/config/parser.rb: -------------------------------------------------------------------------------- 1 | module Config 2 | class Parser 3 | def self.yaml(content) 4 | if content.present? 5 | YAML.safe_load(content, [Regexp, Symbol]) 6 | else 7 | {} 8 | end 9 | rescue Psych::SyntaxError 10 | raise Config::ParserError 11 | end 12 | 13 | def self.json(content) 14 | if content.present? 15 | JSON.parse(content) 16 | else 17 | {} 18 | end 19 | rescue JSON::ParserError 20 | raise Config::ParserError 21 | end 22 | 23 | def self.ini(content) 24 | IniFile.new(content: content).to_h 25 | rescue IniFile::Error 26 | raise Config::ParserError 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /app/models/config/parser_error.rb: -------------------------------------------------------------------------------- 1 | module Config 2 | class ParserError < StandardError; end 3 | end 4 | -------------------------------------------------------------------------------- /app/models/config/phpcs.rb: -------------------------------------------------------------------------------- 1 | module Config 2 | class Phpcs < Base 3 | def content 4 | @content ||= load_content 5 | end 6 | 7 | def serialize 8 | content 9 | end 10 | 11 | private 12 | 13 | def load_content 14 | if file_path 15 | commit.file_content(file_path) 16 | else 17 | "" 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /app/models/config/reek.rb: -------------------------------------------------------------------------------- 1 | module Config 2 | class Reek < Base 3 | def serialize 4 | Serializer.yaml(content) 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/models/config/remark.rb: -------------------------------------------------------------------------------- 1 | module Config 2 | class Remark < Base 3 | def serialize 4 | Serializer.json(content) 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/models/config/sass_lint.rb: -------------------------------------------------------------------------------- 1 | module Config 2 | class SassLint < Base 3 | def serialize 4 | Serializer.yaml(content) 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/models/config/scss_lint.rb: -------------------------------------------------------------------------------- 1 | module Config 2 | class ScssLint < Base 3 | def serialize 4 | Serializer.yaml(content) 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/models/config/serializer.rb: -------------------------------------------------------------------------------- 1 | module Config 2 | class Serializer 3 | def self.yaml(data) 4 | data.to_yaml 5 | end 6 | 7 | def self.json(data) 8 | ActiveSupport::JSON.encode(data) 9 | end 10 | 11 | def self.ini(data) 12 | IniFile.new(content: data).to_s 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /app/models/config/shellcheck.rb: -------------------------------------------------------------------------------- 1 | module Config 2 | class Shellcheck < Base 3 | def serialize 4 | Serializer.yaml(content) 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/models/config/slim_lint.rb: -------------------------------------------------------------------------------- 1 | module Config 2 | class SlimLint < Base 3 | def serialize 4 | Serializer.yaml(content) 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/models/config/stylelint.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module Config 3 | class Stylelint < Base 4 | def serialize 5 | Serializer.json(content) 6 | end 7 | 8 | private 9 | 10 | def parse(file_content) 11 | json_with_comments = JsonWithComments.new(file_content) 12 | content_without_comments = json_with_comments.without_comments 13 | super(content_without_comments) 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /app/models/config/swiftlint.rb: -------------------------------------------------------------------------------- 1 | module Config 2 | class Swiftlint < Base 3 | def serialize 4 | Serializer.yaml(content) 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/models/config/tslint.rb: -------------------------------------------------------------------------------- 1 | module Config 2 | class Tslint < Base 3 | def serialize 4 | Serializer.json(content) 5 | end 6 | 7 | private 8 | 9 | def parse(file_content) 10 | json_with_comments = JsonWithComments.new(file_content) 11 | content_without_comments = json_with_comments.without_comments 12 | super(content_without_comments) 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /app/models/config/unsupported.rb: -------------------------------------------------------------------------------- 1 | module Config 2 | class Unsupported < Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /app/models/config_content/remote.rb: -------------------------------------------------------------------------------- 1 | class ConfigContent 2 | class Remote 3 | extend Forwardable 4 | 5 | def_delegators :response, :body, :status, :success? 6 | 7 | def initialize(url) 8 | @url = url 9 | end 10 | 11 | def load 12 | if success? 13 | body 14 | else 15 | raise ContentError, "#{status} #{body}" 16 | end 17 | end 18 | 19 | private 20 | 21 | attr_reader :url 22 | 23 | def connection 24 | Faraday.new 25 | end 26 | 27 | def response 28 | connection.get(url) 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /app/models/empty_commit.rb: -------------------------------------------------------------------------------- 1 | class EmptyCommit < Commit 2 | def initialize 3 | end 4 | 5 | def file_content(_filename) 6 | "" 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /app/models/file_review.rb: -------------------------------------------------------------------------------- 1 | class FileReview < ApplicationRecord 2 | belongs_to :build 3 | has_many :violations, dependent: :destroy 4 | 5 | def build_violation(line, message) 6 | if line.changed? 7 | violation = find_or_build_violation(line) 8 | violation.add_message(message) 9 | violation.patch_position = line.patch_position 10 | end 11 | end 12 | 13 | def complete 14 | self.completed_at = Time.current 15 | end 16 | 17 | def completed? 18 | completed_at? 19 | end 20 | 21 | private 22 | 23 | def find_or_build_violation(line) 24 | violations.detect { |violation| violation.line_number == line.number } || 25 | violations.build(line_number: line.number) 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /app/models/github_plan.rb: -------------------------------------------------------------------------------- 1 | class GitHubPlan < Plan 2 | PLANS = [ 3 | { 4 | id: 1061, 5 | price: 0, 6 | range: 0..0, 7 | title: "Hound", 8 | slug: "MDIyOk1hcmtldHBsYWNlTGlzdGluZ1BsYW4xMDYx", 9 | }, 10 | { 11 | id: 1062, 12 | price: 49, 13 | range: 1..4, 14 | title: "Chihuahua", 15 | slug: "MDIyOk1hcmtldHBsYWNlTGlzdGluZ1BsYW4xMDYy", 16 | }, 17 | { 18 | id: 1063, 19 | price: 149, 20 | range: 5..20, 21 | title: "Octodog", 22 | slug: "MDIyOk1hcmtldHBsYWNlTGlzdGluZ1BsYW4xMDYz", 23 | }, 24 | ].freeze 25 | 26 | attr_reader :slug 27 | 28 | def initialize(id:, range:, price:, title:, slug:) 29 | @id = id 30 | @range = range 31 | @price = price 32 | @title = title 33 | @slug = slug 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /app/models/home.rb: -------------------------------------------------------------------------------- 1 | class Home 2 | attr_reader :user 3 | 4 | def initialize(user) 5 | @user = user 6 | end 7 | 8 | def open_source_plans 9 | open_source_repos.map { |plan| present(plan) } 10 | end 11 | 12 | def private_plans 13 | private_repos.map { |plan| present(plan) } 14 | end 15 | 16 | private 17 | 18 | def present(plan) 19 | PlanPresenter.new(plan: plan, user: user) 20 | end 21 | 22 | def plans 23 | plan_selector.plans 24 | end 25 | 26 | def private_repos 27 | plans.reject(&:open_source?) 28 | end 29 | 30 | def open_source_repos 31 | plans.select(&:open_source?) 32 | end 33 | 34 | def plan_selector 35 | PlanSelector.new(user: user) 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /app/models/job_failure.rb: -------------------------------------------------------------------------------- 1 | class JobFailure < OpenStruct 2 | extend ActiveModel::Naming 3 | 4 | def id 5 | self["jid"] 6 | end 7 | 8 | def job_class 9 | self["class"] 10 | end 11 | 12 | def failed_at 13 | self["failed_at"] 14 | end 15 | 16 | def error_message 17 | self["error_message"] 18 | end 19 | 20 | def self.grouped 21 | Sidekiq::DeadSet.new. 22 | map { |failure| JobFailure.new(failure.item) }. 23 | group_by do |job| 24 | if job.error_message.match?(%r{/statuses/\w+: 404 - Not Found}) 25 | job.error_message[/.+?\/statuses\//] 26 | else 27 | job.error_message 28 | end 29 | end 30 | end 31 | 32 | def self.remove(ids) 33 | dead_set = Sidekiq::DeadSet.new 34 | ids.each do |id| 35 | dead_set.find_job(id).delete 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /app/models/line.rb: -------------------------------------------------------------------------------- 1 | class Line 2 | attr_reader :number, :patch_position 3 | 4 | def initialize(number:, content:, patch_position:) 5 | @number = number 6 | @content = content 7 | @patch_position = patch_position 8 | end 9 | 10 | def changed? 11 | true 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /app/models/linter/coffeelint.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Linter 4 | class Coffeelint < Base 5 | FILE_REGEXP = /.+\.coffee(\.js)?(\.erb)?\z/ 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/models/linter/credo.rb: -------------------------------------------------------------------------------- 1 | module Linter 2 | class Credo < Base 3 | FILE_REGEXP = /.+(\.ex|\.exs)\z/ 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/models/linter/erb_lint.rb: -------------------------------------------------------------------------------- 1 | module Linter 2 | class ErbLint < Base 3 | FILE_REGEXP = /.+\.erb\z/ 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/models/linter/eslint.rb: -------------------------------------------------------------------------------- 1 | module Linter 2 | class Eslint < Base 3 | FILE_REGEXP = /.+(\.js|\.es6|\.jsx|\.vue|\.ts|\.tsx)\z/ 4 | IGNORE_FILENAME = ".eslintignore".freeze 5 | 6 | def file_included?(commit_file) 7 | ignore_file.file_included?(commit_file.filename) 8 | end 9 | 10 | private 11 | 12 | def ignore_file 13 | @_ignore_file ||= IgnoreFile.new(name, hound_config, IGNORE_FILENAME) 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /app/models/linter/flake8.rb: -------------------------------------------------------------------------------- 1 | module Linter 2 | class Flake8 < Base 3 | FILE_REGEXP = /.+\.py\z/ 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/models/linter/flog.rb: -------------------------------------------------------------------------------- 1 | module Linter 2 | class Flog < Base 3 | FILE_REGEXP = /.+\.r(b|ake)\z/ 4 | 5 | def file_included?(commit_file) 6 | commit_file.filename !~ /^(spec|test)\// 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/models/linter/golint.rb: -------------------------------------------------------------------------------- 1 | module Linter 2 | class Golint < Base 3 | FILE_REGEXP = /.+\.go\z/ 4 | 5 | def file_included?(commit_file) 6 | !vendored?(commit_file.filename) 7 | end 8 | 9 | private 10 | 11 | def vendored?(filename) 12 | path_components = Pathname(filename).each_filename 13 | 14 | path_components.include?("vendor") || 15 | path_components.take(2) == ["Godeps", "_workspace"] 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /app/models/linter/haml_lint.rb: -------------------------------------------------------------------------------- 1 | module Linter 2 | class HamlLint < Base 3 | FILE_REGEXP = /.+\.haml\z/ 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/models/linter/jshint.rb: -------------------------------------------------------------------------------- 1 | module Linter 2 | class Jshint < Base 3 | FILE_REGEXP = /.+\.js\z/ 4 | IGNORE_FILENAME = ".jshintignore".freeze 5 | 6 | def file_included?(commit_file) 7 | ignore_file.file_included?(commit_file.filename) 8 | end 9 | 10 | private 11 | 12 | def ignore_file 13 | @_ignore_file ||= IgnoreFile.new(name, hound_config, IGNORE_FILENAME) 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /app/models/linter/phpcs.rb: -------------------------------------------------------------------------------- 1 | module Linter 2 | class Phpcs < Base 3 | FILE_REGEXP = /.+\.php\z/ 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/models/linter/reek.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module Linter 3 | class Reek < Base 4 | FILE_REGEXP = /.+\.r(b|ake)\z/ 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /app/models/linter/remark.rb: -------------------------------------------------------------------------------- 1 | module Linter 2 | class Remark < Base 3 | FILE_REGEXP = /.+\.(?:md|markdown)\z/ 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/models/linter/rubocop.rb: -------------------------------------------------------------------------------- 1 | module Linter 2 | class Rubocop < Base 3 | FILE_REGEXP = /.+(\.rb|\.rake|\.jbuilder)|(Gemfile|Rakefile|Podfile)\z/ 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/models/linter/sass_lint.rb: -------------------------------------------------------------------------------- 1 | module Linter 2 | class SassLint < Base 3 | FILE_REGEXP = /.+\.s(a|c)ss\z/ 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/models/linter/scss_lint.rb: -------------------------------------------------------------------------------- 1 | module Linter 2 | class ScssLint < Base 3 | FILE_REGEXP = /.+\.scss\z/ 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/models/linter/shellcheck.rb: -------------------------------------------------------------------------------- 1 | module Linter 2 | class Shellcheck < Base 3 | FILE_REGEXP = /.+(\.sh|\.zsh|\.bash)\z/ 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/models/linter/slim_lint.rb: -------------------------------------------------------------------------------- 1 | module Linter 2 | class SlimLint < Base 3 | FILE_REGEXP = /.slim\z/ 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/models/linter/stylelint.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module Linter 3 | class Stylelint < Base 4 | FILE_REGEXP = /.+(\.scss|\.css|\.less)\z/ 5 | IGNORE_FILENAME = ".stylelintignore" 6 | 7 | def file_included?(commit_file) 8 | ignore_file.file_included?(commit_file.filename) 9 | end 10 | 11 | private 12 | 13 | def ignore_file 14 | @_ignore_file ||= IgnoreFile.new(name, hound_config, IGNORE_FILENAME) 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /app/models/linter/swiftlint.rb: -------------------------------------------------------------------------------- 1 | module Linter 2 | class Swiftlint < Base 3 | FILE_REGEXP = /.+\.swift\z/ 4 | 5 | def file_included?(commit_file) 6 | !pathspec.match(commit_file.filename) 7 | end 8 | 9 | private 10 | 11 | def pathspec 12 | @_pathspec ||= PathSpec.new(config.content["excluded"]) 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /app/models/linter/tslint.rb: -------------------------------------------------------------------------------- 1 | module Linter 2 | class Tslint < Base 3 | FILE_REGEXP = /.+\.ts[x]?\z/ 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/models/membership.rb: -------------------------------------------------------------------------------- 1 | class Membership < ApplicationRecord 2 | belongs_to :repo 3 | belongs_to :user 4 | end 5 | -------------------------------------------------------------------------------- /app/models/missing_owner.rb: -------------------------------------------------------------------------------- 1 | class MissingOwner 2 | def config_content(*) 3 | {} 4 | end 5 | 6 | def hound_config_content 7 | {} 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/models/plan.rb: -------------------------------------------------------------------------------- 1 | class Plan 2 | include ActiveModel::Serialization 3 | 4 | attr_reader :id, :price, :title, :range 5 | 6 | def initialize(id:, range:, price:, title:) 7 | @id = id 8 | @range = range 9 | @price = price 10 | @title = title 11 | end 12 | 13 | def ==(other) 14 | other && id == other.id 15 | end 16 | 17 | def allowance 18 | range.max 19 | end 20 | 21 | def open_source? 22 | price.zero? 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /app/models/stripe_plan.rb: -------------------------------------------------------------------------------- 1 | class StripePlan < Plan 2 | PLANS = [ 3 | { id: "basic", price: 0, range: 0..0, title: "Hound" }, 4 | { id: "tier1", price: 49, range: 1..4, title: "Chihuahua" }, 5 | { id: "tier2", price: 99, range: 5..10, title: "Labrador" }, 6 | { id: "tier3", price: 249, range: 11..30, title: "Great Dane" }, 7 | ].freeze 8 | end 9 | -------------------------------------------------------------------------------- /app/models/subscription.rb: -------------------------------------------------------------------------------- 1 | class Subscription < ApplicationRecord 2 | acts_as_paranoid 3 | 4 | belongs_to :repo 5 | belongs_to :user 6 | end 7 | -------------------------------------------------------------------------------- /app/models/unchanged_line.rb: -------------------------------------------------------------------------------- 1 | class UnchangedLine 2 | def initialize(*) 3 | end 4 | 5 | def patch_position 6 | -1 7 | end 8 | 9 | def changed? 10 | false 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /app/models/violation.rb: -------------------------------------------------------------------------------- 1 | # Hold file, line number, and violation message values. 2 | # Built by style guides. 3 | # Printed by Commenter. 4 | class Violation < ApplicationRecord 5 | belongs_to :file_review 6 | 7 | delegate :count, to: :messages, prefix: true 8 | delegate :filename, to: :file_review 9 | 10 | def add_message(message) 11 | self[:messages] << message 12 | end 13 | 14 | def messages 15 | self[:messages].uniq 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /app/presenters/plan_presenter.rb: -------------------------------------------------------------------------------- 1 | class PlanPresenter 2 | delegate :allowance, :open_source?, :price, :title, to: :plan 3 | 4 | def initialize(plan:, user:) 5 | @plan = plan 6 | @user = user 7 | end 8 | 9 | def current? 10 | user.current_plan == plan 11 | end 12 | 13 | def next? 14 | user.next_plan == plan 15 | end 16 | 17 | def to_partial_path 18 | if open_source? 19 | "plans/open_source" 20 | else 21 | "plans/private" 22 | end 23 | end 24 | 25 | private 26 | 27 | attr_reader :plan, :user 28 | end 29 | -------------------------------------------------------------------------------- /app/queries/repos_with_membership_or_subscription_query.rb: -------------------------------------------------------------------------------- 1 | class ReposWithMembershipOrSubscriptionQuery 2 | static_facade :call 3 | 4 | def initialize(user) 5 | @user = user 6 | end 7 | 8 | def call 9 | subscribed_repos | activatable_repos 10 | end 11 | 12 | private 13 | 14 | def subscribed_repos 15 | @user.subscribed_repos.includes(*repo_includes) 16 | end 17 | 18 | def activatable_repos 19 | @user.repos_by_activation_ability.includes(*repo_includes) 20 | end 21 | 22 | def repo_includes 23 | [ 24 | :memberships, 25 | :owner, 26 | :subscription, 27 | ] 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /app/serializers/plan_serializer.rb: -------------------------------------------------------------------------------- 1 | class PlanSerializer < ActiveModel::Serializer 2 | attributes :current, :name, :price, :allowance 3 | 4 | def current 5 | scope.current_plan == object 6 | end 7 | 8 | def name 9 | object.title 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /app/serializers/repo_serializer.rb: -------------------------------------------------------------------------------- 1 | class RepoSerializer < ActiveModel::Serializer 2 | attributes( 3 | :admin, 4 | :active, 5 | :name, 6 | :github_id, 7 | :id, 8 | :owner, 9 | :private, 10 | :stripe_subscription_id, 11 | ) 12 | 13 | def admin 14 | has_admin_membership? || has_subscription? 15 | end 16 | 17 | private 18 | 19 | def membership 20 | @membership ||= object.memberships.detect { |m| m.user_id == scope.id } 21 | end 22 | 23 | def has_admin_membership? 24 | membership.present? && membership.admin? 25 | end 26 | 27 | def has_subscription? 28 | object.subscription&.user_id == scope.id 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /app/serializers/user_serializer.rb: -------------------------------------------------------------------------------- 1 | class UserSerializer < ActiveModel::Serializer 2 | attributes( 3 | :id, 4 | :refreshing_repos, 5 | :subscribed_repo_count, 6 | :plan_max, 7 | :username, 8 | ) 9 | 10 | def subscribed_repo_count 11 | object.subscribed_repos.count 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /app/services/build_config.rb: -------------------------------------------------------------------------------- 1 | class BuildConfig 2 | static_facade :call 3 | 4 | def initialize(hound_config:, name:, owner:) 5 | @hound_config = hound_config 6 | @name = name 7 | @owner = owner 8 | end 9 | 10 | def call 11 | config_class.new(hound_config, owner: owner) 12 | end 13 | 14 | private 15 | 16 | attr_reader :hound_config, :name, :owner 17 | 18 | def config_class 19 | "Config::#{name.classify}".constantize 20 | rescue 21 | Config::Unsupported 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /app/services/create_repo.rb: -------------------------------------------------------------------------------- 1 | class CreateRepo 2 | pattr_initialize :repo 3 | static_facade :call, :repo 4 | 5 | def call 6 | Repo.find_or_initialize_by(github_id: repo[:id]).tap do |repo| 7 | repo.update!(attributes) 8 | end 9 | end 10 | 11 | def attributes 12 | { 13 | installation_id: repo[:installation_id], 14 | name: repo[:full_name], 15 | owner: owner, 16 | private: repo[:private], 17 | }.compact 18 | end 19 | 20 | def owner 21 | Owner.upsert( 22 | github_id: repo[:owner][:id], 23 | name: repo[:owner][:login], 24 | organization: repo[:owner][:type] == GitHubApi::ORGANIZATION_TYPE, 25 | ) 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /app/services/normalize_config.rb: -------------------------------------------------------------------------------- 1 | class NormalizeConfig 2 | static_facade :call 3 | 4 | def initialize(config) 5 | @config = config 6 | end 7 | 8 | def call 9 | @config.reduce({}) do |normalized_config, (key, value)| 10 | normalized_key = normalize_key(key) 11 | if value.is_a? Hash 12 | normalized_config[normalized_key] = NormalizeConfig.call(value) 13 | else 14 | normalized_config[normalized_key] = value 15 | end 16 | normalized_config 17 | end 18 | end 19 | 20 | private 21 | 22 | def normalize_key(key) 23 | key.downcase 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /app/services/rebuild_pull_request.rb: -------------------------------------------------------------------------------- 1 | class RebuildPullRequest 2 | static_facade :call 3 | 4 | def initialize(repo:, pull_request_number:) 5 | @repo = repo 6 | @pull_request_number = pull_request_number 7 | end 8 | 9 | def call 10 | if latest_build.present? 11 | SmallBuildJob.perform_async(latest_build.payload) 12 | end 13 | end 14 | 15 | private 16 | 17 | def latest_build 18 | Build. 19 | where(repo: @repo, pull_request_number: @pull_request_number). 20 | order(created_at: :desc). 21 | first 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /app/services/report_invalid_config.rb: -------------------------------------------------------------------------------- 1 | class ReportInvalidConfig 2 | static_facade :call 3 | 4 | def initialize(pull_request_number:, commit_sha:, message:) 5 | @pull_request_number = pull_request_number 6 | @commit_sha = commit_sha 7 | @message = message 8 | end 9 | 10 | def call 11 | commit_status.set_config_error(message) 12 | end 13 | 14 | private 15 | 16 | attr_reader :pull_request_number, :commit_sha, :message 17 | 18 | def commit_status 19 | @commit_status ||= CommitStatus.new( 20 | repo: build.repo, 21 | sha: commit_sha, 22 | github_auth: build.github_auth, 23 | ) 24 | end 25 | 26 | def build 27 | @build ||= Build.find_by!( 28 | pull_request_number: pull_request_number, 29 | commit_sha: commit_sha, 30 | ) 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /app/services/resolve_config_conflicts.rb: -------------------------------------------------------------------------------- 1 | class ResolveConfigConflicts 2 | CONFLICTS = { 3 | "eslint" => "jshint", 4 | "stylelint" => "scss_lint", 5 | "sass_lint" => "scss_lint", 6 | }.freeze 7 | 8 | static_facade :call 9 | 10 | def initialize(config) 11 | @config = config 12 | end 13 | 14 | def call 15 | @config.reduce({}) do |resolved_config, (linter, options)| 16 | if options.nil? 17 | raise( 18 | ConfigContent::ContentError, 19 | "#{linter} options in your .hound.yml are invalid", 20 | ) 21 | else 22 | if CONFLICTS.has_key?(linter) && options["enabled"] == true 23 | resolved_config[CONFLICTS[linter]] = { "enabled" => false } 24 | end 25 | resolved_config[linter] = options 26 | end 27 | resolved_config 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /app/services/sanitize_ini_file.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | class SanitizeIniFile 3 | static_facade :call 4 | 5 | def initialize(config) 6 | @config = config 7 | end 8 | 9 | def call 10 | lines_without_comments. 11 | map { |line| normalize_line(line) }. 12 | join 13 | end 14 | 15 | private 16 | 17 | def normalize_line(line) 18 | case line.rstrip 19 | when /.+=$/ 20 | line.rstrip + " " 21 | when /^\s+.+,$/ 22 | line.strip 23 | when /^\s+.+$/ 24 | line.lstrip 25 | else 26 | line 27 | end 28 | end 29 | 30 | def lines_without_comments 31 | @config.lines. 32 | map { |line| line.gsub(/\s*#.*$/, "") }. 33 | reject { |line| line.strip.blank? } 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /app/services/update_github_plans.rb: -------------------------------------------------------------------------------- 1 | class UpdateGitHubPlans 2 | static_facade :call 3 | 4 | def call 5 | GitHubPlan::PLANS.each do |plan| 6 | accounts_for_plan(plan[:id]).each do |account| 7 | Owner. 8 | where(github_id: account.id). 9 | update(marketplace_plan_id: account.marketplace_purchase.plan.id) 10 | end 11 | end 12 | end 13 | 14 | private 15 | 16 | def accounts_for_plan(plan_id) 17 | GitHubApi.new(AppToken.new.generate).accounts_for_plan(plan_id) 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /app/services/update_repo_status.rb: -------------------------------------------------------------------------------- 1 | class UpdateRepoStatus 2 | static_facade :call 3 | pattr_initialize :payload 4 | 5 | def call 6 | if repo 7 | repo.update(repo_attributes) 8 | end 9 | end 10 | 11 | private 12 | 13 | def repo 14 | @repo ||= Repo.active.find_by(github_id: payload.github_repo_id) 15 | end 16 | 17 | def repo_attributes 18 | { 19 | name: payload.full_repo_name, 20 | private: payload.private_repo?, 21 | } 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /app/views/admin/application/_navigation.html.haml: -------------------------------------------------------------------------------- 1 | %nav.navigation{ role: "navigation" } 2 | - navigation_resources.each do |resource| 3 | = link_to display_resource_name(resource), 4 | [namespace, resource.path.to_sym], 5 | class: "navigation__link navigation__link--#{nav_link_state(resource)}" 6 | -------------------------------------------------------------------------------- /app/views/application/_settings.haml: -------------------------------------------------------------------------------- 1 | :javascript 2 | Namespaced.declare("Hound.settings"); 3 | Hound.settings = { 4 | stripePublishableKey: "#{Hound::STRIPE_PUBLISHABLE_KEY}", 5 | syncingButtonText: "#{I18n.t('syncing_repos')}", 6 | syncNowButtonText: "#{I18n.t('sync_repos')}", 7 | searchPlaceholder: "#{I18n.t('search_placeholder')}", 8 | iconPath: "#{image_path('icon.svg')}", 9 | }; 10 | -------------------------------------------------------------------------------- /app/views/application/update_billing.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |17 | <%= link_to( 18 | t(".supported_linters"), 19 | "http://help.houndci.com/en/articles/2461415-supported-linters", 20 | class: "u-font-weight-medium", 21 | ) %> 22 |
23 |