{"id":13880325,"url":"https://github.com/ankane/searchjoy","last_synced_at":"2025-11-17T14:07:48.146Z","repository":{"id":11384605,"uuid":"13825563","full_name":"ankane/searchjoy","owner":"ankane","description":"Search analytics made easy","archived":false,"fork":false,"pushed_at":"2025-10-22T03:51:40.000Z","size":249,"stargazers_count":661,"open_issues_count":1,"forks_count":53,"subscribers_count":10,"default_branch":"master","last_synced_at":"2025-11-11T05:18:06.511Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ankane.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2013-10-24T07:37:31.000Z","updated_at":"2025-11-04T09:51:05.000Z","dependencies_parsed_at":"2023-12-26T17:27:18.203Z","dependency_job_id":"6b8e4eef-4c22-4f37-b980-8aebd7813f83","html_url":"https://github.com/ankane/searchjoy","commit_stats":{"total_commits":309,"total_committers":12,"mean_commits":25.75,"dds":0.04530744336569581,"last_synced_commit":"6d65964a2064a7c9e7e6f860b5194b66ff9af1c4"},"previous_names":["ankane/intel"],"tags_count":29,"template":false,"template_full_name":null,"purl":"pkg:github/ankane/searchjoy","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ankane%2Fsearchjoy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ankane%2Fsearchjoy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ankane%2Fsearchjoy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ankane%2Fsearchjoy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ankane","download_url":"https://codeload.github.com/ankane/searchjoy/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ankane%2Fsearchjoy/sbom","scorecard":{"id":197163,"data":{"date":"2025-08-11","repo":{"name":"github.com/ankane/searchjoy","commit":"1fc12d6128fb8a8b602cd12c799507cf83275eb2"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.4,"checks":[{"name":"Code-Review","score":0,"reason":"Found 2/30 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Maintained","score":0,"reason":"1 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/build.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/build.yml:22: update your workflow using https://app.stepsecurity.io/secureworkflow/ankane/searchjoy/build.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/build.yml:23: update your workflow using https://app.stepsecurity.io/secureworkflow/ankane/searchjoy/build.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/build.yml:27: update your workflow using https://app.stepsecurity.io/secureworkflow/ankane/searchjoy/build.yml/master?enable=pin","Info:   0 out of   1 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   2 third-party GitHubAction dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE.txt:0","Info: FSF or OSI recognized license: MIT License: LICENSE.txt:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 2 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-16T22:01:22.122Z","repository_id":11384605,"created_at":"2025-08-16T22:01:22.122Z","updated_at":"2025-08-16T22:01:22.122Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":283987234,"owners_count":26928239,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-11-12T02:00:06.336Z","response_time":59,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-06T08:02:56.669Z","updated_at":"2025-11-17T14:07:48.127Z","avatar_url":"https://github.com/ankane.png","language":"Ruby","funding_links":[],"categories":["Ruby"],"sub_categories":[],"readme":"# Searchjoy\n\nSearch analytics made easy\n\n[See it in action](https://searchjoy.dokkuapp.com/)\n\n[![Screenshot](https://searchjoy.dokkuapp.com/assets/searchjoy-d0162561.png)](https://searchjoy.dokkuapp.com/)\n\n- view searches in real-time\n- track conversion rate week over week\n- monitor the performance of top searches\n\nWorks with any search platform, including Elasticsearch, OpenSearch, Sphinx, and Solr\n\n:cupid: An amazing companion to [Searchkick](https://github.com/ankane/searchkick)\n\n[![Build Status](https://github.com/ankane/searchjoy/actions/workflows/build.yml/badge.svg)](https://github.com/ankane/searchjoy/actions)\n\n## Installation\n\nAdd this line to your application’s Gemfile:\n\n```ruby\ngem \"searchjoy\"\n```\n\nAnd run the generator. This creates a migration to store searches.\n\n```sh\nrails generate searchjoy:install\nrails db:migrate\n```\n\nNext, add the dashboard to your `config/routes.rb`.\n\n```ruby\nmount Searchjoy::Engine, at: \"searchjoy\"\n```\n\nBe sure to protect the endpoint in production - see the [Authentication](#authentication) section for ways to do this.\n\n## Track Searches\n\nTrack searches by creating a record in the database.\n\n```ruby\nSearchjoy::Search.create(\n  search_type: \"Item\", # typically the model name\n  query: \"apple\",\n  results_count: 12,\n  user_id: 1\n)\n```\n\nWith [Searchkick](https://github.com/ankane/searchkick), you can use the `track` option to do this automatically.\n\n```ruby\nItem.search(\"apple\", track: {user_id: 1})\n```\n\nIf you want to track more attributes, add them to the `searchjoy_searches` table.\n\n```ruby\nadd_column :searchjoy_searches, :source, :string\n```\n\nThen, pass the values to the `track` option.\n\n```ruby\nItem.search(\"apple\", track: {user_id: 1, source: \"web\"})\n```\n\n## Track Conversions\n\nFirst, choose a conversion metric. At Instacart, an item added to the cart from the search results page counts as a conversion.\n\nNext, when a user searches, keep track of the search id. With Searchkick, you can get the id with:\n\n```ruby\nresults = Item.search(\"apple\", track: true)\nresults.search.id\n```\n\nWhen a user converts, find the record and call `convert`.\n\n```ruby\nsearch = Searchjoy::Search.find(params[:id])\nsearch.convert\n```\n\nBetter yet, record the model that converted.\n\n```ruby\nsearch.convert(item)\n```\n\n## Authentication\n\nDon’t forget to protect the dashboard in production.\n\n### Devise\n\nIn your `config/routes.rb`:\n\n```ruby\nauthenticate :user, -\u003e(user) { user.admin? } do\n  mount Searchjoy::Engine, at: \"searchjoy\"\nend\n```\n\n### Basic Authentication\n\nSet the following variables in your environment or an initializer.\n\n```ruby\nENV[\"SEARCHJOY_USERNAME\"] = \"andrew\"\nENV[\"SEARCHJOY_PASSWORD\"] = \"secret\"\n```\n\n## Data Retention\n\nData should only be retained for as long as it’s needed. Delete older data with:\n\n```ruby\nSearchjoy::Search.where(\"created_at \u003c ?\", 1.year.ago).find_in_batches do |searches|\n  search_ids = searches.map(\u0026:id)\n  Searchjoy::Conversion.where(search_id: search_ids).delete_all\n  Searchjoy::Search.where(id: search_ids).delete_all\nend\n```\n\nYou can use [Rollup](https://github.com/ankane/rollup) to aggregate important data before you do.\n\n```ruby\nSearchjoy::Search.rollup(\"Searches\")\n```\n\nDelete data for a specific user with:\n\n```ruby\nuser_id = 123\nsearch_ids = Searchjoy::Search.where(user_id: user_id).pluck(:id)\nSearchjoy::Conversion.where(search_id: search_ids).delete_all\nSearchjoy::Search.where(id: search_ids).delete_all\n```\n\n## Customize\n\nTo customize, create an initializer `config/initializers/searchjoy.rb`.\n\nChange the time zone\n\n```ruby\nSearchjoy.time_zone = \"Pacific Time (US \u0026 Canada)\" # defaults to Time.zone\n```\n\nChange the number of top searches shown\n\n```ruby\nSearchjoy.top_searches = 500 # defaults to 100\n```\n\nLink to the search results\n\n```ruby\nSearchjoy.query_url = -\u003e(search) { Rails.application.routes.url_helpers.items_path(q: search.query) }\n```\n\nAdd additional info to the query in the live stream\n\n```ruby\nSearchjoy.query_name = -\u003e(search) { \"#{search.query} #{search.city}\" }\n```\n\nShow the conversion name in the live stream\n\n```ruby\nSearchjoy.conversion_name = -\u003e(model) { model.name }\n```\n\n## History\n\nView the [changelog](https://github.com/ankane/searchjoy/blob/master/CHANGELOG.md)\n\n## Contributing\n\nEveryone is encouraged to help improve this project. Here are a few ways you can help:\n\n- [Report bugs](https://github.com/ankane/searchjoy/issues)\n- Fix bugs and [submit pull requests](https://github.com/ankane/searchjoy/pulls)\n- Write, clarify, or fix documentation\n- Suggest or add new feature\n\nTo get started with development and testing:\n\n```sh\ngit clone https://github.com/ankane/searchjoy.git\ncd searchjoy\nbundle install\nbundle exec rake test\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fankane%2Fsearchjoy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fankane%2Fsearchjoy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fankane%2Fsearchjoy/lists"}