コード例 #1
0
def test_find_crash_id():
    # A good string, no prefix
    input_str = '1234abcd-ef56-7890-ab12-abcdef130802'
    crash_id = utils.find_crash_id(input_str)
    assert crash_id == input_str

    # A good string, with prefix
    input_str = 'bp-1234abcd-ef56-7890-ab12-abcdef130802'
    crash_id = utils.find_crash_id(input_str)
    assert crash_id == '1234abcd-ef56-7890-ab12-abcdef130802'

    # A good looking string but not a real day
    input_str = '1234abcd-ef56-7890-ab12-abcdef130230'  # Feb 30th 2013
    assert not utils.find_crash_id(input_str)
    input_str = 'bp-1234abcd-ef56-7890-ab12-abcdef130230'
    assert not utils.find_crash_id(input_str)

    # A bad string, one character missing
    input_str = 'bp-1234abcd-ef56-7890-ab12-abcdef12345'
    assert not utils.find_crash_id(input_str)

    # A bad string, one character not allowed
    input_str = 'bp-1234abcd-ef56-7890-ab12-abcdef12345g'
    assert not utils.find_crash_id(input_str)

    # Close but doesn't end with 6 digits
    input_str = 'f48e9617-652a-11dd-a35a-001a4bd43ed6'
    assert not utils.find_crash_id(input_str)

    # A random string that does not match
    input_str = 'somerandomstringthatdoesnotmatch'
    assert not utils.find_crash_id(input_str)
コード例 #2
0
ファイル: test_utils.py プロジェクト: mozilla/socorro
def test_find_crash_id():
    # A good string, no prefix
    input_str = '1234abcd-ef56-7890-ab12-abcdef130802'
    crash_id = utils.find_crash_id(input_str)
    assert crash_id == input_str

    # A good string, with prefix
    input_str = 'bp-1234abcd-ef56-7890-ab12-abcdef130802'
    crash_id = utils.find_crash_id(input_str)
    assert crash_id == '1234abcd-ef56-7890-ab12-abcdef130802'

    # A good looking string but not a real day
    input_str = '1234abcd-ef56-7890-ab12-abcdef130230'  # Feb 30th 2013
    assert not utils.find_crash_id(input_str)
    input_str = 'bp-1234abcd-ef56-7890-ab12-abcdef130230'
    assert not utils.find_crash_id(input_str)

    # A bad string, one character missing
    input_str = 'bp-1234abcd-ef56-7890-ab12-abcdef12345'
    assert not utils.find_crash_id(input_str)

    # A bad string, one character not allowed
    input_str = 'bp-1234abcd-ef56-7890-ab12-abcdef12345g'
    assert not utils.find_crash_id(input_str)

    # Close but doesn't end with 6 digits
    input_str = 'f48e9617-652a-11dd-a35a-001a4bd43ed6'
    assert not utils.find_crash_id(input_str)

    # A random string that does not match
    input_str = 'somerandomstringthatdoesnotmatch'
    assert not utils.find_crash_id(input_str)
コード例 #3
0
ファイル: forms.py プロジェクト: mdemori/socorro
 def clean_crash_id(self):
     value = self.cleaned_data['crash_id'].strip()
     crash_id = find_crash_id(value)
     if not crash_id:
         raise forms.ValidationError(
             'Does not appear to be a valid crash ID')
     return crash_id
コード例 #4
0
ファイル: forms.py プロジェクト: Krispy2009/socorro
 def clean_crash_id(self):
     value = self.cleaned_data['crash_id'].strip()
     crash_id = find_crash_id(value)
     if not crash_id:
         raise forms.ValidationError(
             'Does not appear to be a valid crash ID'
         )
     return crash_id
コード例 #5
0
ファイル: test_utils.py プロジェクト: bighuggies/socorro
    def test_find_crash_id(self):
        # A good string, no prefix
        input_str = '1234abcd-ef56-7890-ab12-abcdef123456'
        crash_id = utils.find_crash_id(input_str)
        eq_(crash_id, input_str)

        # A good string, with prefix
        input_str = 'bp-1234abcd-ef56-7890-ab12-abcdef123456'
        crash_id = utils.find_crash_id(input_str)
        eq_(crash_id, '1234abcd-ef56-7890-ab12-abcdef123456')

        # A bad string, one character missing
        input_str = 'bp-1234abcd-ef56-7890-ab12-abcdef12345'
        ok_(not utils.find_crash_id(input_str))

        # A bad string, one character not allowed
        input_str = 'bp-1234abcd-ef56-7890-ab12-abcdef12345g'
        ok_(not utils.find_crash_id(input_str))

        # A random string that does not match
        input_str = 'somerandomstringthatdoesnotmatch'
        ok_(not utils.find_crash_id(input_str))
コード例 #6
0
def quick_search(request):
    query = request.GET.get('query', '').strip()
    crash_id = utils.find_crash_id(query)

    if crash_id:
        url = reverse('crashstats:report_index',
                      kwargs=dict(crash_id=crash_id))
    elif query:
        url = '%s?signature=%s' % (reverse('supersearch:search'),
                                   urlquote('~%s' % query))
    else:
        url = reverse('supersearch:search')

    return redirect(url)
コード例 #7
0
ファイル: views.py プロジェクト: richev/socorro
def quick_search(request):
    query = request.GET.get("query", "").strip()
    crash_id = utils.find_crash_id(query)

    if crash_id:
        url = reverse("crashstats:report_index",
                      kwargs=dict(crash_id=crash_id))
    elif query:
        url = "%s?signature=%s" % (
            reverse("supersearch:search"),
            urlquote("~%s" % query),
        )
    else:
        url = reverse("supersearch:search")

    return redirect(url)
コード例 #8
0
ファイル: views.py プロジェクト: mozilla/socorro
def quick_search(request):
    query = request.GET.get('query', '').strip()
    crash_id = utils.find_crash_id(query)

    if crash_id:
        url = reverse(
            'crashstats:report_index',
            kwargs=dict(crash_id=crash_id)
        )
    elif query:
        url = '%s?signature=%s' % (
            reverse('supersearch:search'),
            urlquote('~%s' % query)
        )
    else:
        url = reverse('supersearch:search')

    return redirect(url)
コード例 #9
0
ファイル: views.py プロジェクト: richev/socorro
def report_index(request, crash_id, default_context=None):
    valid_crash_id = utils.find_crash_id(crash_id)
    if not valid_crash_id:
        return http.HttpResponseBadRequest("Invalid crash ID")

    # Sometimes, in Socorro we use a prefix on the crash ID. Usually it's
    # 'bp-' but this is configurable.
    # If you try to use this to reach the perma link for a crash, it should
    # redirect to the report index with the correct crash ID.
    if valid_crash_id != crash_id:
        return redirect(
            reverse("crashstats:report_index", args=(valid_crash_id, )))

    context = default_context or {}
    context["crash_id"] = crash_id

    refresh_cache = request.GET.get("refresh") == "cache"

    raw_api = models.RawCrash()
    try:
        context["raw"] = raw_api.get(crash_id=crash_id,
                                     refresh_cache=refresh_cache)
    except CrashIDNotFound:
        # If the raw crash can't be found, we can't do much.
        return render(request,
                      "crashstats/report_index_not_found.html",
                      context,
                      status=404)
    utils.enhance_raw(context["raw"])

    context["your_crash"] = (request.user.is_active and
                             context["raw"].get("Email") == request.user.email)

    api = models.UnredactedCrash()
    try:
        context["report"] = api.get(crash_id=crash_id,
                                    refresh_cache=refresh_cache)
    except CrashIDNotFound:
        # ...if we haven't already done so.
        cache_key = "priority_job:{}".format(crash_id)
        if not cache.get(cache_key):
            priority_api = models.PriorityJob()
            priority_api.post(crash_ids=[crash_id])
            cache.set(cache_key, True, 60)
        return render(request, "crashstats/report_index_pending.html", context)

    if "json_dump" in context["report"]:
        json_dump = context["report"]["json_dump"]
        if "sensitive" in json_dump and not request.user.has_perm(
                "crashstats.view_pii"):
            del json_dump["sensitive"]
        context["raw_stackwalker_output"] = json.dumps(json_dump,
                                                       sort_keys=True,
                                                       indent=4,
                                                       separators=(",", ": "))
        utils.enhance_json_dump(json_dump, settings.VCS_MAPPINGS)
        parsed_dump = json_dump
    else:
        context["raw_stackwalker_output"] = "No dump available"
        parsed_dump = {}

    # NOTE(willkg): pull cpu count from parsed_dump if it's not in report;
    # remove in July 2019
    if "cpu_count" not in context["report"]:
        context["report"]["cpu_count"] = parsed_dump.get("system_info",
                                                         {}).get("cpu_count")
    # NOTE(willkg): "cpu_name" is deprecated, but populate "cpu_arch" if
    # cpu_arch is empty; remove in July 2019.
    if "cpu_arch" not in context["report"]:
        context["report"]["cpu_arch"] = context["report"]["cpu_name"]

    context["crashing_thread"] = parsed_dump.get("crash_info",
                                                 {}).get("crashing_thread")
    if context["report"]["signature"].startswith("shutdownhang"):
        # For shutdownhang signatures, we want to use thread 0 as the
        # crashing thread, because that's the thread that actually contains
        # the useful data about what happened.
        context["crashing_thread"] = 0

    context["parsed_dump"] = parsed_dump
    context["bug_product_map"] = settings.BUG_PRODUCT_MAP

    context["bug_associations"] = list(
        models.BugAssociation.objects.filter(
            signature=context["report"]["signature"]).values(
                "bug_id", "signature").order_by("-bug_id"))

    context["raw_keys"] = []
    if request.user.has_perm("crashstats.view_pii"):
        # hold nothing back
        context["raw_keys"] = context["raw"].keys()
    else:
        context["raw_keys"] = [
            x for x in context["raw"] if x in models.RawCrash.API_ALLOWLIST()
        ]
    # Sort keys case-insensitively
    context["raw_keys"] = sorted(context["raw_keys"], key=lambda s: s.lower())

    if request.user.has_perm("crashstats.view_rawdump"):
        context["raw_dump_urls"] = [
            reverse("crashstats:raw_data", args=(crash_id, "dmp")),
            reverse("crashstats:raw_data", args=(crash_id, "json")),
        ]
        if context["raw"].get("additional_minidumps"):
            suffixes = [
                x.strip()
                for x in context["raw"]["additional_minidumps"].split(",")
                if x.strip()
            ]
            for suffix in suffixes:
                name = "upload_file_minidump_%s" % (suffix, )
                context["raw_dump_urls"].append(
                    reverse("crashstats:raw_data_named",
                            args=(crash_id, name, "dmp")))
        if (context["raw"].get("ContainsMemoryReport")
                and context["report"].get("memory_report")
                and not context["report"].get("memory_report_error")):
            context["raw_dump_urls"].append(
                reverse(
                    "crashstats:raw_data_named",
                    args=(crash_id, "memory_report", "json.gz"),
                ))

    # Add descriptions to all fields.
    all_fields = SuperSearchFields().get()
    descriptions = {}
    for field in all_fields.values():
        key = "{}.{}".format(field["namespace"], field["in_database_name"])
        descriptions[key] = "{} Search: {}".format(
            field.get("description", "").strip()
            or "No description for this field.",
            field["is_exposed"] and field["name"] or "N/A",
        )

    def make_raw_crash_key(key):
        """In the report_index.html template we need to create a key
        that we can use to look up against the 'fields_desc' dict.
        Because you can't do something like this in jinja::

            {{ fields_desc.get(u'raw_crash.{}'.format(key), empty_desc) }}

        we do it here in the function instead.
        The trick is that the lookup key has to be a unicode object or
        else you get UnicodeEncodeErrors in the template rendering.
        """
        return "raw_crash.{}".format(key)

    context["make_raw_crash_key"] = make_raw_crash_key
    context["fields_desc"] = descriptions
    context["empty_desc"] = "No description for this field. Search: unknown"

    context["BUG_PRODUCT_MAP"] = settings.BUG_PRODUCT_MAP

    # report.addons used to be a list of lists.
    # In https://bugzilla.mozilla.org/show_bug.cgi?id=1250132
    # we changed it from a list of lists to a list of strings, using
    # a ':' to split the name and version.
    # See https://bugzilla.mozilla.org/show_bug.cgi?id=1250132#c7
    # Considering legacy, let's tackle both.
    # In late 2017, this code is going to be useless and can be removed.
    if context["report"].get("addons") and isinstance(
            context["report"]["addons"][0], (list, tuple)):
        # This is the old legacy format. This crash hasn't been processed
        # the new way.
        context["report"]["addons"] = [
            ":".join(x) for x in context["report"]["addons"]
        ]

    content = loader.render_to_string("crashstats/report_index.html", context,
                                      request)
    utf8_content = content.encode("utf-8", errors="backslashreplace")
    return HttpResponse(utf8_content, charset="utf-8")
コード例 #10
0
ファイル: views.py プロジェクト: mozilla/socorro
def report_index(request, crash_id, default_context=None):
    valid_crash_id = utils.find_crash_id(crash_id)
    if not valid_crash_id:
        return http.HttpResponseBadRequest('Invalid crash ID')

    # Sometimes, in Socorro we use a prefix on the crash ID. Usually it's
    # 'bp-' but this is configurable.
    # If you try to use this to reach the perma link for a crash, it should
    # redirect to the report index with the correct crash ID.
    if valid_crash_id != crash_id:
        return redirect(reverse('crashstats:report_index', args=(valid_crash_id,)))

    context = default_context or {}
    context['crash_id'] = crash_id

    refresh_cache = request.GET.get('refresh') == 'cache'

    raw_api = models.RawCrash()
    try:
        context['raw'] = raw_api.get(crash_id=crash_id, refresh_cache=refresh_cache)
    except CrashIDNotFound:
        # If the raw crash can't be found, we can't do much.
        return render(request, 'crashstats/report_index_not_found.html', context, status=404)
    utils.enhance_raw(context['raw'])

    context['your_crash'] = (
        request.user.is_active and
        context['raw'].get('Email') == request.user.email
    )

    api = models.UnredactedCrash()
    try:
        context['report'] = api.get(crash_id=crash_id, refresh_cache=refresh_cache)
    except CrashIDNotFound:
        # ...if we haven't already done so.
        cache_key = 'priority_job:{}'.format(crash_id)
        if not cache.get(cache_key):
            priority_api = models.PriorityJob()
            priority_api.post(crash_ids=[crash_id])
            cache.set(cache_key, True, 60)
        return render(request, 'crashstats/report_index_pending.html', context)

    if 'json_dump' in context['report']:
        json_dump = context['report']['json_dump']
        if 'sensitive' in json_dump and not request.user.has_perm('crashstats.view_pii'):
            del json_dump['sensitive']
        context['raw_stackwalker_output'] = json.dumps(
            json_dump,
            sort_keys=True,
            indent=4,
            separators=(',', ': ')
        )
        utils.enhance_json_dump(json_dump, settings.VCS_MAPPINGS)
        parsed_dump = json_dump
    else:
        context['raw_stackwalker_output'] = 'No dump available'
        parsed_dump = {}

    # NOTE(willkg): pull cpu count from parsed_dump if it's not in report;
    # remove in July 2019
    if 'cpu_count' not in context['report']:
        context['report']['cpu_count'] = parsed_dump.get('system_info', {}).get('cpu_count')
    # NOTE(willkg): "cpu_name" is deprecated, but populate "cpu_arch" if
    # cpu_arch is empty; remove in July 2019.
    if 'cpu_arch' not in context['report']:
        context['report']['cpu_arch'] = context['report']['cpu_name']

    context['crashing_thread'] = parsed_dump.get('crash_info', {}).get('crashing_thread')
    if context['report']['signature'].startswith('shutdownhang'):
        # For shutdownhang signatures, we want to use thread 0 as the
        # crashing thread, because that's the thread that actually contains
        # the useful data about what happened.
        context['crashing_thread'] = 0

    context['parsed_dump'] = parsed_dump
    context['bug_product_map'] = settings.BUG_PRODUCT_MAP

    context['bug_associations'] = list(
        models.BugAssociation.objects
        .filter(signature=context['report']['signature'])
        .values('bug_id', 'signature')
        .order_by('-bug_id')
    )

    context['raw_keys'] = []
    if request.user.has_perm('crashstats.view_pii'):
        # hold nothing back
        context['raw_keys'] = context['raw'].keys()
    else:
        context['raw_keys'] = [
            x for x in context['raw']
            if x in models.RawCrash.API_ALLOWLIST()
        ]
    # Sort keys case-insensitively
    context['raw_keys'] = sorted(context['raw_keys'], key=lambda s: s.lower())

    if request.user.has_perm('crashstats.view_rawdump'):
        context['raw_dump_urls'] = [
            reverse('crashstats:raw_data', args=(crash_id, 'dmp')),
            reverse('crashstats:raw_data', args=(crash_id, 'json'))
        ]
        if context['raw'].get('additional_minidumps'):
            suffixes = [
                x.strip()
                for x in context['raw']['additional_minidumps'].split(',')
                if x.strip()
            ]
            for suffix in suffixes:
                name = 'upload_file_minidump_%s' % (suffix,)
                context['raw_dump_urls'].append(
                    reverse('crashstats:raw_data_named', args=(crash_id, name, 'dmp'))
                )
        if (
            context['raw'].get('ContainsMemoryReport') and
            context['report'].get('memory_report') and
            not context['report'].get('memory_report_error')
        ):
            context['raw_dump_urls'].append(
                reverse('crashstats:raw_data_named', args=(crash_id, 'memory_report', 'json.gz'))
            )

    # Add descriptions to all fields.
    all_fields = SuperSearchFields().get()
    descriptions = {}
    for field in all_fields.values():
        key = '{}.{}'.format(field['namespace'], field['in_database_name'])
        descriptions[key] = '{} Search: {}'.format(
            field.get('description', '').strip() or 'No description for this field.',
            field['is_exposed'] and field['name'] or 'N/A',
        )

    def make_raw_crash_key(key):
        """In the report_index.html template we need to create a key
        that we can use to look up against the 'fields_desc' dict.
        Because you can't do something like this in jinja::

            {{ fields_desc.get(u'raw_crash.{}'.format(key), empty_desc) }}

        we do it here in the function instead.
        The trick is that the lookup key has to be a unicode object or
        else you get UnicodeEncodeErrors in the template rendering.
        """
        return u'raw_crash.{}'.format(key)

    context['make_raw_crash_key'] = make_raw_crash_key
    context['fields_desc'] = descriptions
    context['empty_desc'] = 'No description for this field. Search: unknown'

    context['BUG_PRODUCT_MAP'] = settings.BUG_PRODUCT_MAP

    # report.addons used to be a list of lists.
    # In https://bugzilla.mozilla.org/show_bug.cgi?id=1250132
    # we changed it from a list of lists to a list of strings, using
    # a ':' to split the name and version.
    # See https://bugzilla.mozilla.org/show_bug.cgi?id=1250132#c7
    # Considering legacy, let's tackle both.
    # In late 2017, this code is going to be useless and can be removed.
    if (
        context['report'].get('addons') and
        isinstance(context['report']['addons'][0], (list, tuple))
    ):
        # This is the old legacy format. This crash hasn't been processed
        # the new way.
        context['report']['addons'] = [
            ':'.join(x) for x in context['report']['addons']
        ]

    return render(request, 'crashstats/report_index.html', context)
コード例 #11
0
def report_index(request, crash_id, default_context=None):
    valid_crash_id = utils.find_crash_id(crash_id)
    if not valid_crash_id:
        return http.HttpResponseBadRequest('Invalid crash ID')

    # Sometimes, in Socorro we use a prefix on the crash ID. Usually it's
    # 'bp-' but this is configurable.
    # If you try to use this to reach the perma link for a crash, it should
    # redirect to the report index with the correct crash ID.
    if valid_crash_id != crash_id:
        return redirect(
            reverse('crashstats:report_index', args=(valid_crash_id, )))

    context = default_context or {}
    context['crash_id'] = crash_id

    refresh_cache = request.GET.get('refresh') == 'cache'

    raw_api = models.RawCrash()
    try:
        context['raw'] = raw_api.get(crash_id=crash_id,
                                     refresh_cache=refresh_cache)
    except CrashIDNotFound:
        # If the raw crash can't be found, we can't do much.
        return render(request,
                      'crashstats/report_index_not_found.html',
                      context,
                      status=404)
    utils.enhance_raw(context['raw'])

    context['your_crash'] = (request.user.is_active and
                             context['raw'].get('Email') == request.user.email)

    api = models.UnredactedCrash()
    try:
        context['report'] = api.get(crash_id=crash_id,
                                    refresh_cache=refresh_cache)
    except CrashIDNotFound:
        # ...if we haven't already done so.
        cache_key = 'priority_job:{}'.format(crash_id)
        if not cache.get(cache_key):
            priority_api = models.Priorityjob()
            priority_api.post(crash_ids=[crash_id])
            cache.set(cache_key, True, 60)
        return render(request, 'crashstats/report_index_pending.html', context)

    if 'json_dump' in context['report']:
        json_dump = context['report']['json_dump']
        if 'sensitive' in json_dump and not request.user.has_perm(
                'crashstats.view_pii'):
            del json_dump['sensitive']
        context['raw_stackwalker_output'] = json.dumps(json_dump,
                                                       sort_keys=True,
                                                       indent=4,
                                                       separators=(',', ': '))
        utils.enhance_json_dump(json_dump, settings.VCS_MAPPINGS)
        parsed_dump = json_dump
    else:
        context['raw_stackwalker_output'] = 'No dump available'
        parsed_dump = {}

    # NOTE(willkg): pull cpu count from parsed_dump if it's not in report;
    # remove in July 2019
    if 'cpu_count' not in context['report']:
        context['report']['cpu_count'] = parsed_dump.get('system_info',
                                                         {}).get('cpu_count')
    # NOTE(willkg): "cpu_name" is deprecated, but populate "cpu_arch" if
    # cpu_arch is empty; remove in July 2019.
    if 'cpu_arch' not in context['report']:
        context['report']['cpu_arch'] = context['report']['cpu_name']

    context['crashing_thread'] = parsed_dump.get('crash_info',
                                                 {}).get('crashing_thread')
    if context['report']['signature'].startswith('shutdownhang'):
        # For shutdownhang signatures, we want to use thread 0 as the
        # crashing thread, because that's the thread that actually contains
        # the useful data about what happened.
        context['crashing_thread'] = 0

    context['parsed_dump'] = parsed_dump
    context['bug_product_map'] = settings.BUG_PRODUCT_MAP

    context['bug_associations'] = list(
        models.BugAssociation.objects.filter(
            signature=context['report']['signature']).values(
                'bug_id', 'signature').order_by('-bug_id'))

    context['raw_keys'] = []
    if request.user.has_perm('crashstats.view_pii'):
        # hold nothing back
        context['raw_keys'] = context['raw'].keys()
    else:
        context['raw_keys'] = [
            x for x in context['raw'] if x in models.RawCrash.API_WHITELIST()
        ]
    # Sort keys case-insensitively
    context['raw_keys'] = sorted(context['raw_keys'], key=lambda s: s.lower())

    if request.user.has_perm('crashstats.view_rawdump'):
        context['raw_dump_urls'] = [
            reverse('crashstats:raw_data', args=(crash_id, 'dmp')),
            reverse('crashstats:raw_data', args=(crash_id, 'json'))
        ]
        if context['raw'].get('additional_minidumps'):
            suffixes = [
                x.strip()
                for x in context['raw']['additional_minidumps'].split(',')
                if x.strip()
            ]
            for suffix in suffixes:
                name = 'upload_file_minidump_%s' % (suffix, )
                context['raw_dump_urls'].append(
                    reverse('crashstats:raw_data_named',
                            args=(crash_id, name, 'dmp')))
        if (context['raw'].get('ContainsMemoryReport')
                and context['report'].get('memory_report')
                and not context['report'].get('memory_report_error')):
            context['raw_dump_urls'].append(
                reverse('crashstats:raw_data_named',
                        args=(crash_id, 'memory_report', 'json.gz')))

    # Add descriptions to all fields.
    all_fields = SuperSearchFields().get()
    descriptions = {}
    for field in all_fields.values():
        key = '{}.{}'.format(field['namespace'], field['in_database_name'])
        descriptions[key] = '{} Search: {}'.format(
            field.get('description', '').strip()
            or 'No description for this field.',
            field['is_exposed'] and field['name'] or 'N/A',
        )

    def make_raw_crash_key(key):
        """In the report_index.html template we need to create a key
        that we can use to look up against the 'fields_desc' dict.
        Because you can't do something like this in jinja::

            {{ fields_desc.get(u'raw_crash.{}'.format(key), empty_desc) }}

        we do it here in the function instead.
        The trick is that the lookup key has to be a unicode object or
        else you get UnicodeEncodeErrors in the template rendering.
        """
        return u'raw_crash.{}'.format(key)

    context['make_raw_crash_key'] = make_raw_crash_key
    context['fields_desc'] = descriptions
    context['empty_desc'] = 'No description for this field. Search: unknown'

    context['BUG_PRODUCT_MAP'] = settings.BUG_PRODUCT_MAP

    # report.addons used to be a list of lists.
    # In https://bugzilla.mozilla.org/show_bug.cgi?id=1250132
    # we changed it from a list of lists to a list of strings, using
    # a ':' to split the name and version.
    # See https://bugzilla.mozilla.org/show_bug.cgi?id=1250132#c7
    # Considering legacy, let's tackle both.
    # In late 2017, this code is going to be useless and can be removed.
    if (context['report'].get('addons')
            and isinstance(context['report']['addons'][0], (list, tuple))):
        # This is the old legacy format. This crash hasn't been processed
        # the new way.
        context['report']['addons'] = [
            ':'.join(x) for x in context['report']['addons']
        ]

    return render(request, 'crashstats/report_index.html', context)