{"action":"created","comment":{"url":"https://api.github.com/repos/edx-solutions/edx-search/pulls/comments/22876759","id":22876759,"diff_hunk":"@@ -0,0 +1,273 @@\n+\"\"\" Elatic Search implementation for courseware search index \"\"\"\n+from django.conf import settings\n+from elasticsearch import Elasticsearch, exceptions\n+\n+from search.manager import SearchEngine\n+\n+\n+def _translate_hits(es_response):\n+    \"\"\" Provide resultset in our desired format from elasticsearch results \"\"\"\n+\n+    def process_result(result):\n+        \"\"\" Any conversion from ES result syntax into our search engine syntax \"\"\"\n+        data = result.pop(\"_source\")\n+\n+        result.update({\n+            \"data\": data,\n+            \"score\": result[\"_score\"]\n+        })\n+\n+        return result\n+\n+    results = [process_result(hit) for hit in es_response[\"hits\"][\"hits\"]]\n+    response = {\n+        \"took\": es_response[\"took\"],\n+        \"total\": es_response[\"hits\"][\"total\"],\n+        \"max_score\": es_response[\"hits\"][\"max_score\"],\n+        \"results\": results,\n+    }\n+\n+    return response\n+\n+\n+def _get_filter_field(field_name, field_value):\n+    \"\"\" Return field to apply into filter, if an array then use a range, otherwise look for a term match \"\"\"\n+    filter_field = None\n+    if isinstance(field_value, list) and len(field_value) == 2:\n+        range_values = {}\n+        if field_value[0]:\n+            range_values.update({\"gte\": field_value[0]})\n+        if field_value[1]:\n+            range_values.update({\"lte\": field_value[1]})\n+        filter_field = {\n+            \"range\": {\n+                field_name: range_values\n+            }\n+        }\n+    else:\n+        filter_field = {\n+            \"term\": {\n+                field_name: field_value\n+            }\n+        }\n+    return filter_field\n+\n+\n+def _process_field_dictionary(filters, queries, field_dictionary, search_using_fields):\n+    \"\"\"\n+    We have a field_dictionary - make sure that we are matching the values\n+    Pass switch search_using_fields as True if you want to use \"match\" query instead of \"term\" filter\n+    This is only potentially useful when trying to tune certain search operations\n+    \"\"\"\n+    for field in field_dictionary:\n+        if search_using_fields:\n+            queries.append({\n+                \"match\": {\n+                    field: field_dictionary[field]\n+                }\n+            })\n+        else:\n+            filters.append(_get_filter_field(field, field_dictionary[field]))\n+\n+\n+def process_filter_dictionary(filters, filter_dictionary):\n+    \"\"\"\n+    We have a filter_dictionary - this means that if the field is included\n+    and matches, then we can include, OR if the field is undefined, then we\n+    assume it is safe to include\n+    \"\"\"\n+    for field in filter_dictionary:\n+        filters.append({\n+            \"or\": [\n+                _get_filter_field(field, filter_dictionary[field]),\n+                {\n+                    \"missing\": {\n+                        \"field\": field\n+                    }\n+                }\n+            ]\n+        })\n+\n+\n+class ElasticSearchEngine(SearchEngine):\n+\n+    \"\"\" ElasticSearch implementation of SearchEngine abstraction \"\"\"\n+\n+    _es = Elasticsearch()\n+    _known_mappings = {}\n+\n+    def _get_mappings(self, doc_type):\n+        \"\"\"\n+        Interfaces with the elasticsearch mappings for the index\n+        prevents multiple loading of the same mappings from ES when called more than once\n+        \"\"\"\n+        known_mappings = ElasticSearchEngine._known_mappings\n+        if self.index_name not in known_mappings:\n+            known_mappings[self.index_name] = {}\n+        if doc_type not in known_mappings[self.index_name]:\n+            try:\n+                known_mappings[self.index_name][doc_type] = self._es.indices.get_mapping(\n+                    index=self.index_name,\n+                    doc_type=doc_type\n+                )[doc_type]\n+            except exceptions.NotFoundError:\n+                return {}\n+\n+        return known_mappings[self.index_name][doc_type]\n+\n+    def _clear_mapping(self, doc_type):\n+        \"\"\" Remove the cached mappings, so that they get loaded from ES next time they are requested \"\"\"\n+        known_mappings = ElasticSearchEngine._known_mappings\n+        if self.index_name in known_mappings and doc_type in known_mappings[self.index_name]:\n+            del known_mappings[self.index_name][doc_type]\n+\n+    def __init__(self, index=None):\n+        super(ElasticSearchEngine, self).__init__(index)\n+        if not self._es.indices.exists(index=self.index_name):\n+            self._es.indices.create(index=self.index_name)\n+\n+    def _check_mappings(self, doc_type, body):\n+        \"\"\"\n+        We desire to index content so that anything we want to be textually searchable (and therefore needing to be\n+        analysed), but the other fields are designed to be filters, and only require an exact match. So, we want to\n+        set up the mappings for these fields as \"not_analyzed\" - this will allow our filters to work faster because\n+        they only have to work off exact matches\n+        \"\"\"\n+\n+        # Make fields other than content be indexed as unanalyzed terms - content\n+        # contains fields that are to be analyzed\n+        exclude_fields = [\"content\"]\n+        current_mappings = self._get_mappings(doc_type)\n+        field_properties = getattr(settings, \"ELASTIC_FIELD_MAPPINGS\", {})\n+        properties = {}\n+\n+        def field_property(field_name, field_value):\n+            \"\"\" Prepares field as property syntax for providing correct mapping desired for field \"\"\"\n+            prop_val = None\n+            if field_name in field_properties:\n+                prop_val = field_properties[field_name]\n+            elif isinstance(field_value, dict):\n+                props = {fn: field_property(fn, field_value[fn]) for fn in field_value}\n+                prop_val = {\"properties\": props}\n+            else:\n+                prop_val = {\n+                    \"type\": \"string\",\n+                    \"index\": \"not_analyzed\",\n+                }\n+\n+            return prop_val\n+\n+        any_change = False\n+        for field in body:\n+            if field not in exclude_fields:\n+                if field not in current_mappings:\n+                    any_change = True\n+                    properties[field] = field_property(field, body[field])\n+\n+        if any_change:\n+            self._es.indices.put_mapping(\n+                index=self.index_name,\n+                doc_type=doc_type,\n+                body={\n+                    doc_type: {\n+                        \"properties\": properties,\n+                    }\n+                }\n+            )\n+            self._clear_mapping(doc_type)\n+\n+    def index(self, doc_type, body, **kwargs):\n+        \"\"\"\n+        Implements call to add document to the ES index\n+        Note the call to _check_mappings which will setup fields with the desired mappings\n+        \"\"\"\n+        if 'id' in body:\n+            kwargs['id'] = body['id']\n+\n+        self._check_mappings(doc_type, body)\n+\n+        self._es.index(\n+            index=self.index_name,\n+            doc_type=doc_type,\n+            body=body,\n+            **kwargs\n+        )\n+\n+    def remove(self, doc_type, doc_id, **kwargs):\n+        \"\"\" Implements call to remove the document from the index \"\"\"\n+\n+        # let notfound not cause error\n+        kwargs.update({\n+            \"ignore\": [404]\n+        })\n+\n+        self._es.delete(\n+            index=self.index_name,\n+            doc_type=doc_type,\n+            id=doc_id,\n+            **kwargs\n+        )\n+\n+    def search(self, query_string=None, field_dictionary=None, filter_dictionary=None, **kwargs):\n+        \"\"\" Implements call to search the index for the desired content \"\"\"\n+\n+        search_using_fields = \"search_fields\" in kwargs and kwargs[\"search_fields\"]\n+        if search_using_fields:\n+            del kwargs[\"search_fields\"]\n+\n+        queries = []\n+        filters = []\n+\n+        # We have a query string, search all fields for matching text within the \"content\" node\n+        if query_string:\n+            queries.append({\n+                \"query_string\": {\n+                    \"fields\": [\"content.*\"],\n+                    \"query\": query_string\n+                }\n+            })\n+\n+        if field_dictionary:\n+            _process_field_dictionary(filters, queries, field_dictionary, search_using_fields)\n+\n+        if filter_dictionary:\n+            process_filter_dictionary(filters, filter_dictionary)\n+\n+        query = {\n+            \"match_all\": {}\n+        }","path":"search/elastic.py","position":238,"original_position":238,"commit_id":"d3dc36b96ac0607136413356257d5099567f2d67","original_commit_id":"d3dc36b96ac0607136413356257d5099567f2d67","user":{"login":"ormsbee","id":41625,"avatar_url":"https://avatars.githubusercontent.com/u/41625?v=3","gravatar_id":"","url":"https://api.github.com/users/ormsbee","html_url":"https://github.com/ormsbee","followers_url":"https://api.github.com/users/ormsbee/followers","following_url":"https://api.github.com/users/ormsbee/following{/other_user}","gists_url":"https://api.github.com/users/ormsbee/gists{/gist_id}","starred_url":"https://api.github.com/users/ormsbee/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ormsbee/subscriptions","organizations_url":"https://api.github.com/users/ormsbee/orgs","repos_url":"https://api.github.com/users/ormsbee/repos","events_url":"https://api.github.com/users/ormsbee/events{/privacy}","received_events_url":"https://api.github.com/users/ormsbee/received_events","type":"User","site_admin":false},"body":"Not sure I understand this bit -- if there's no query, it returns everything?","created_at":"2015-01-13T17:10:21Z","updated_at":"2015-01-13T17:10:21Z","html_url":"https://github.com/edx-solutions/edx-search/pull/1#discussion_r22876759","pull_request_url":"https://api.github.com/repos/edx-solutions/edx-search/pulls/1","_links":{"self":{"href":"https://api.github.com/repos/edx-solutions/edx-search/pulls/comments/22876759"},"html":{"href":"https://github.com/edx-solutions/edx-search/pull/1#discussion_r22876759"},"pull_request":{"href":"https://api.github.com/repos/edx-solutions/edx-search/pulls/1"}}},"pull_request":{"url":"https://api.github.com/repos/edx-solutions/edx-search/pulls/1","id":26988112,"html_url":"https://github.com/edx-solutions/edx-search/pull/1","diff_url":"https://github.com/edx-solutions/edx-search/pull/1.diff","patch_url":"https://github.com/edx-solutions/edx-search/pull/1.patch","issue_url":"https://api.github.com/repos/edx-solutions/edx-search/issues/1","number":1,"state":"open","locked":false,"title":"Individual search app for courseware search","user":{"login":"martynjames","id":675165,"avatar_url":"https://avatars.githubusercontent.com/u/675165?v=3","gravatar_id":"","url":"https://api.github.com/users/martynjames","html_url":"https://github.com/martynjames","followers_url":"https://api.github.com/users/martynjames/followers","following_url":"https://api.github.com/users/martynjames/following{/other_user}","gists_url":"https://api.github.com/users/martynjames/gists{/gist_id}","starred_url":"https://api.github.com/users/martynjames/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/martynjames/subscriptions","organizations_url":"https://api.github.com/users/martynjames/orgs","repos_url":"https://api.github.com/users/martynjames/repos","events_url":"https://api.github.com/users/martynjames/events{/privacy}","received_events_url":"https://api.github.com/users/martynjames/received_events","type":"User","site_admin":false},"body":"@chrisndodge - Please take a look when you can. This is the initial implementation of the search app.\r\n\r\nThings of note:\r\n* Calling application provides an (optional) override for class `SearchResultProcessor` in order to post-process results _it is intended that one would inherit from `SearchResultProcessor` and add in additional properties as well as provide a last-chance to remove a result from the list","created_at":"2015-01-07T21:12:33Z","updated_at":"2015-01-13T17:10:21Z","closed_at":null,"merged_at":null,"merge_commit_sha":"b45d1d7594afd3f75e47603a96392eeed7197fae","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/edx-solutions/edx-search/pulls/1/commits","review_comments_url":"https://api.github.com/repos/edx-solutions/edx-search/pulls/1/comments","review_comment_url":"https://api.github.com/repos/edx-solutions/edx-search/pulls/comments/{number}","comments_url":"https://api.github.com/repos/edx-solutions/edx-search/issues/1/comments","statuses_url":"https://api.github.com/repos/edx-solutions/edx-search/statuses/d3dc36b96ac0607136413356257d5099567f2d67","head":{"label":"edx-solutions:mjames/initial_commit","ref":"mjames/initial_commit","sha":"d3dc36b96ac0607136413356257d5099567f2d67","user":{"login":"edx-solutions","id":6585054,"avatar_url":"https://avatars.githubusercontent.com/u/6585054?v=3","gravatar_id":"","url":"https://api.github.com/users/edx-solutions","html_url":"https://github.com/edx-solutions","followers_url":"https://api.github.com/users/edx-solutions/followers","following_url":"https://api.github.com/users/edx-solutions/following{/other_user}","gists_url":"https://api.github.com/users/edx-solutions/gists{/gist_id}","starred_url":"https://api.github.com/users/edx-solutions/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/edx-solutions/subscriptions","organizations_url":"https://api.github.com/users/edx-solutions/orgs","repos_url":"https://api.github.com/users/edx-solutions/repos","events_url":"https://api.github.com/users/edx-solutions/events{/privacy}","received_events_url":"https://api.github.com/users/edx-solutions/received_events","type":"Organization","site_admin":false},"repo":{"id":27890225,"name":"edx-search","full_name":"edx-solutions/edx-search","owner":{"login":"edx-solutions","id":6585054,"avatar_url":"https://avatars.githubusercontent.com/u/6585054?v=3","gravatar_id":"","url":"https://api.github.com/users/edx-solutions","html_url":"https://github.com/edx-solutions","followers_url":"https://api.github.com/users/edx-solutions/followers","following_url":"https://api.github.com/users/edx-solutions/following{/other_user}","gists_url":"https://api.github.com/users/edx-solutions/gists{/gist_id}","starred_url":"https://api.github.com/users/edx-solutions/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/edx-solutions/subscriptions","organizations_url":"https://api.github.com/users/edx-solutions/orgs","repos_url":"https://api.github.com/users/edx-solutions/repos","events_url":"https://api.github.com/users/edx-solutions/events{/privacy}","received_events_url":"https://api.github.com/users/edx-solutions/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/edx-solutions/edx-search","description":"Search/index programming interface for use within edx platform components","fork":false,"url":"https://api.github.com/repos/edx-solutions/edx-search","forks_url":"https://api.github.com/repos/edx-solutions/edx-search/forks","keys_url":"https://api.github.com/repos/edx-solutions/edx-search/keys{/key_id}","collaborators_url":"https://api.github.com/repos/edx-solutions/edx-search/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/edx-solutions/edx-search/teams","hooks_url":"https://api.github.com/repos/edx-solutions/edx-search/hooks","issue_events_url":"https://api.github.com/repos/edx-solutions/edx-search/issues/events{/number}","events_url":"https://api.github.com/repos/edx-solutions/edx-search/events","assignees_url":"https://api.github.com/repos/edx-solutions/edx-search/assignees{/user}","branches_url":"https://api.github.com/repos/edx-solutions/edx-search/branches{/branch}","tags_url":"https://api.github.com/repos/edx-solutions/edx-search/tags","blobs_url":"https://api.github.com/repos/edx-solutions/edx-search/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/edx-solutions/edx-search/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/edx-solutions/edx-search/git/refs{/sha}","trees_url":"https://api.github.com/repos/edx-solutions/edx-search/git/trees{/sha}","statuses_url":"https://api.github.com/repos/edx-solutions/edx-search/statuses/{sha}","languages_url":"https://api.github.com/repos/edx-solutions/edx-search/languages","stargazers_url":"https://api.github.com/repos/edx-solutions/edx-search/stargazers","contributors_url":"https://api.github.com/repos/edx-solutions/edx-search/contributors","subscribers_url":"https://api.github.com/repos/edx-solutions/edx-search/subscribers","subscription_url":"https://api.github.com/repos/edx-solutions/edx-search/subscription","commits_url":"https://api.github.com/repos/edx-solutions/edx-search/commits{/sha}","git_commits_url":"https://api.github.com/repos/edx-solutions/edx-search/git/commits{/sha}","comments_url":"https://api.github.com/repos/edx-solutions/edx-search/comments{/number}","issue_comment_url":"https://api.github.com/repos/edx-solutions/edx-search/issues/comments/{number}","contents_url":"https://api.github.com/repos/edx-solutions/edx-search/contents/{+path}","compare_url":"https://api.github.com/repos/edx-solutions/edx-search/compare/{base}...{head}","merges_url":"https://api.github.com/repos/edx-solutions/edx-search/merges","archive_url":"https://api.github.com/repos/edx-solutions/edx-search/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/edx-solutions/edx-search/downloads","issues_url":"https://api.github.com/repos/edx-solutions/edx-search/issues{/number}","pulls_url":"https://api.github.com/repos/edx-solutions/edx-search/pulls{/number}","milestones_url":"https://api.github.com/repos/edx-solutions/edx-search/milestones{/number}","notifications_url":"https://api.github.com/repos/edx-solutions/edx-search/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/edx-solutions/edx-search/labels{/name}","releases_url":"https://api.github.com/repos/edx-solutions/edx-search/releases{/id}","created_at":"2014-12-11T20:26:57Z","updated_at":"2014-12-17T18:38:08Z","pushed_at":"2015-01-13T14:34:24Z","git_url":"git://github.com/edx-solutions/edx-search.git","ssh_url":"git@github.com:edx-solutions/edx-search.git","clone_url":"https://github.com/edx-solutions/edx-search.git","svn_url":"https://github.com/edx-solutions/edx-search","homepage":null,"size":360,"stargazers_count":1,"watchers_count":1,"language":null,"has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":1,"forks":0,"open_issues":1,"watchers":1,"default_branch":"master"}},"base":{"label":"edx-solutions:master","ref":"master","sha":"710decd58e1c121b1dcd0b1ddb7fe28720f0e5a9","user":{"login":"edx-solutions","id":6585054,"avatar_url":"https://avatars.githubusercontent.com/u/6585054?v=3","gravatar_id":"","url":"https://api.github.com/users/edx-solutions","html_url":"https://github.com/edx-solutions","followers_url":"https://api.github.com/users/edx-solutions/followers","following_url":"https://api.github.com/users/edx-solutions/following{/other_user}","gists_url":"https://api.github.com/users/edx-solutions/gists{/gist_id}","starred_url":"https://api.github.com/users/edx-solutions/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/edx-solutions/subscriptions","organizations_url":"https://api.github.com/users/edx-solutions/orgs","repos_url":"https://api.github.com/users/edx-solutions/repos","events_url":"https://api.github.com/users/edx-solutions/events{/privacy}","received_events_url":"https://api.github.com/users/edx-solutions/received_events","type":"Organization","site_admin":false},"repo":{"id":27890225,"name":"edx-search","full_name":"edx-solutions/edx-search","owner":{"login":"edx-solutions","id":6585054,"avatar_url":"https://avatars.githubusercontent.com/u/6585054?v=3","gravatar_id":"","url":"https://api.github.com/users/edx-solutions","html_url":"https://github.com/edx-solutions","followers_url":"https://api.github.com/users/edx-solutions/followers","following_url":"https://api.github.com/users/edx-solutions/following{/other_user}","gists_url":"https://api.github.com/users/edx-solutions/gists{/gist_id}","starred_url":"https://api.github.com/users/edx-solutions/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/edx-solutions/subscriptions","organizations_url":"https://api.github.com/users/edx-solutions/orgs","repos_url":"https://api.github.com/users/edx-solutions/repos","events_url":"https://api.github.com/users/edx-solutions/events{/privacy}","received_events_url":"https://api.github.com/users/edx-solutions/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/edx-solutions/edx-search","description":"Search/index programming interface for use within edx platform components","fork":false,"url":"https://api.github.com/repos/edx-solutions/edx-search","forks_url":"https://api.github.com/repos/edx-solutions/edx-search/forks","keys_url":"https://api.github.com/repos/edx-solutions/edx-search/keys{/key_id}","collaborators_url":"https://api.github.com/repos/edx-solutions/edx-search/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/edx-solutions/edx-search/teams","hooks_url":"https://api.github.com/repos/edx-solutions/edx-search/hooks","issue_events_url":"https://api.github.com/repos/edx-solutions/edx-search/issues/events{/number}","events_url":"https://api.github.com/repos/edx-solutions/edx-search/events","assignees_url":"https://api.github.com/repos/edx-solutions/edx-search/assignees{/user}","branches_url":"https://api.github.com/repos/edx-solutions/edx-search/branches{/branch}","tags_url":"https://api.github.com/repos/edx-solutions/edx-search/tags","blobs_url":"https://api.github.com/repos/edx-solutions/edx-search/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/edx-solutions/edx-search/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/edx-solutions/edx-search/git/refs{/sha}","trees_url":"https://api.github.com/repos/edx-solutions/edx-search/git/trees{/sha}","statuses_url":"https://api.github.com/repos/edx-solutions/edx-search/statuses/{sha}","languages_url":"https://api.github.com/repos/edx-solutions/edx-search/languages","stargazers_url":"https://api.github.com/repos/edx-solutions/edx-search/stargazers","contributors_url":"https://api.github.com/repos/edx-solutions/edx-search/contributors","subscribers_url":"https://api.github.com/repos/edx-solutions/edx-search/subscribers","subscription_url":"https://api.github.com/repos/edx-solutions/edx-search/subscription","commits_url":"https://api.github.com/repos/edx-solutions/edx-search/commits{/sha}","git_commits_url":"https://api.github.com/repos/edx-solutions/edx-search/git/commits{/sha}","comments_url":"https://api.github.com/repos/edx-solutions/edx-search/comments{/number}","issue_comment_url":"https://api.github.com/repos/edx-solutions/edx-search/issues/comments/{number}","contents_url":"https://api.github.com/repos/edx-solutions/edx-search/contents/{+path}","compare_url":"https://api.github.com/repos/edx-solutions/edx-search/compare/{base}...{head}","merges_url":"https://api.github.com/repos/edx-solutions/edx-search/merges","archive_url":"https://api.github.com/repos/edx-solutions/edx-search/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/edx-solutions/edx-search/downloads","issues_url":"https://api.github.com/repos/edx-solutions/edx-search/issues{/number}","pulls_url":"https://api.github.com/repos/edx-solutions/edx-search/pulls{/number}","milestones_url":"https://api.github.com/repos/edx-solutions/edx-search/milestones{/number}","notifications_url":"https://api.github.com/repos/edx-solutions/edx-search/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/edx-solutions/edx-search/labels{/name}","releases_url":"https://api.github.com/repos/edx-solutions/edx-search/releases{/id}","created_at":"2014-12-11T20:26:57Z","updated_at":"2014-12-17T18:38:08Z","pushed_at":"2015-01-13T14:34:24Z","git_url":"git://github.com/edx-solutions/edx-search.git","ssh_url":"git@github.com:edx-solutions/edx-search.git","clone_url":"https://github.com/edx-solutions/edx-search.git","svn_url":"https://github.com/edx-solutions/edx-search","homepage":null,"size":360,"stargazers_count":1,"watchers_count":1,"language":null,"has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":1,"forks":0,"open_issues":1,"watchers":1,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/edx-solutions/edx-search/pulls/1"},"html":{"href":"https://github.com/edx-solutions/edx-search/pull/1"},"issue":{"href":"https://api.github.com/repos/edx-solutions/edx-search/issues/1"},"comments":{"href":"https://api.github.com/repos/edx-solutions/edx-search/issues/1/comments"},"review_comments":{"href":"https://api.github.com/repos/edx-solutions/edx-search/pulls/1/comments"},"review_comment":{"href":"https://api.github.com/repos/edx-solutions/edx-search/pulls/comments/{number}"},"commits":{"href":"https://api.github.com/repos/edx-solutions/edx-search/pulls/1/commits"},"statuses":{"href":"https://api.github.com/repos/edx-solutions/edx-search/statuses/d3dc36b96ac0607136413356257d5099567f2d67"}}}}