예제 #1
0
    def snap_details(snap_name):
        """
        A view to display the snap details page for specific snaps.

        This queries the snapcraft API (api.snapcraft.io) and passes
        some of the data through to the snap-details.html template,
        with appropriate sanitation.
        """

        error_info = {}
        status_code = 200

        context = _get_context_snap_details(snap_name)

        webapp_config = flask.current_app.config.get("WEBAPP_CONFIG")

        if "STORE_QUERY" not in webapp_config:
            country_metric_name = "weekly_installed_base_by_country_percent"
            os_metric_name = (
                "weekly_installed_base_by_operating_system_normalized")

            end = metrics_helper.get_last_metrics_processed_date()

            metrics_query_json = [
                metrics_helper.get_filter(
                    metric_name=country_metric_name,
                    snap_id=context["snap-id"],
                    start=end,
                    end=end,
                ),
                metrics_helper.get_filter(
                    metric_name=os_metric_name,
                    snap_id=context["snap-id"],
                    start=end,
                    end=end,
                ),
            ]

            try:
                metrics_response = api.get_public_metrics(metrics_query_json)
            except (StoreApiError, ApiError) as api_error:
                status_code, error_info = handle_errors(api_error)
                metrics_response = None

            os_metrics = None
            country_devices = None
            if metrics_response:
                oses = metrics_helper.find_metric(metrics_response,
                                                  os_metric_name)
                os_metrics = metrics.OsMetric(
                    name=oses["metric_name"],
                    series=oses["series"],
                    buckets=oses["buckets"],
                    status=oses["status"],
                )

                territories = metrics_helper.find_metric(
                    metrics_response, country_metric_name)
                country_devices = metrics.CountryDevices(
                    name=territories["metric_name"],
                    series=territories["series"],
                    buckets=territories["buckets"],
                    status=territories["status"],
                    private=False,
                )
        else:
            os_metrics = None
            country_devices = None

        context.update({
            "countries":
            (country_devices.country_data if country_devices else None),
            "normalized_os":
            os_metrics.os if os_metrics else None,
            # Context info
            "is_linux":
            ("Linux" in flask.request.headers.get("User-Agent", "")
             and "Android" not in flask.request.headers.get("User-Agent", "")),
            "error_info":
            error_info,
        })

        return (
            flask.render_template("store/snap-details.html", **context),
            status_code,
        )
예제 #2
0
def publisher_snap_metrics(snap_name):
    """
    A view to display the snap metrics page for specific snaps.

    This queries the snapcraft API (api.snapcraft.io) and passes
    some of the data through to the publisher/metrics.html template,
    with appropriate sanitation.
    """
    try:
        details = api.get_snap_info(snap_name, flask.session)
    except ApiResponseErrorList as api_response_error_list:
        if api_response_error_list.status_code == 404:
            return flask.abort(404, "No snap named {}".format(snap_name))
        else:
            return _handle_error_list(api_response_error_list.errors)
    except ApiError as api_error:
        return _handle_error(api_error)

    metric_requested = logic.extract_metrics_period(
        flask.request.args.get("period", default="30d", type=str))

    installed_base_metric = logic.verify_base_metrics(
        flask.request.args.get("active-devices", default="version", type=str))

    installed_base = logic.get_installed_based_metric(installed_base_metric)
    metrics_query_json = metrics_helper.build_metrics_json(
        snap_id=details["snap_id"],
        installed_base=installed_base,
        metric_period=metric_requested["int"],
        metric_bucket=metric_requested["bucket"],
    )

    try:
        metrics_response = api.get_publisher_metrics(flask.session,
                                                     json=metrics_query_json)
    except ApiResponseErrorList as api_response_error_list:
        if api_response_error_list.status_code == 404:
            return flask.abort(404, "No snap named {}".format(snap_name))
        else:
            return _handle_error_list(api_response_error_list.errors)
    except ApiError as api_error:
        return _handle_error(api_error)

    active_metrics = metrics_helper.find_metric(metrics_response["metrics"],
                                                installed_base)
    active_devices = metrics.ActiveDevices(
        name=active_metrics["metric_name"],
        series=active_metrics["series"],
        buckets=active_metrics["buckets"],
        status=active_metrics["status"],
    )

    latest_active = 0
    if active_devices:
        latest_active = active_devices.get_number_latest_active_devices()

    country_metric = metrics_helper.find_metric(
        metrics_response["metrics"], "weekly_installed_base_by_country")
    country_devices = metrics.CountryDevices(
        name=country_metric["metric_name"],
        series=country_metric["series"],
        buckets=country_metric["buckets"],
        status=country_metric["status"],
        private=True,
    )

    territories_total = 0
    if country_devices:
        territories_total = country_devices.get_number_territories()

    nodata = not any([country_devices, active_devices])

    # until default tracks are supported by the API we special case node
    # to use 10, rather then latest
    default_track = helpers.get_default_track(snap_name)

    annotations = {"name": "annotations", "series": [], "buckets": []}

    for category in details["categories"]["items"]:
        date = category["since"].split("T")[0]
        new_date = logic.convert_date(category["since"])

        if date not in annotations["buckets"]:
            annotations["buckets"].append(date)

        index_of_date = annotations["buckets"].index(date)

        single_series = {
            "values": [0] * (len(annotations)),
            "name": category["name"],
            "display_name": category["name"].capitalize().replace("-", " "),
            "display_date": new_date,
            "date": date,
        }

        single_series["values"][index_of_date] = 1

        annotations["series"].append(single_series)

    annotations["series"] = sorted(annotations["series"],
                                   key=lambda k: k["date"])

    context = {
        # Data direct from details API
        "snap_name": snap_name,
        "snap_title": details["title"],
        "metric_period": metric_requested["period"],
        "active_device_metric": installed_base_metric,
        "default_track": default_track,
        "private": details["private"],
        # Metrics data
        "nodata": nodata,
        "latest_active_devices": latest_active,
        "active_devices": dict(active_devices),
        "territories_total": territories_total,
        "territories": country_devices.country_data,
        "active_devices_annotations": annotations,
        # Context info
        "is_linux": "Linux" in flask.request.headers["User-Agent"],
    }

    return flask.render_template("publisher/metrics.html", **context)
예제 #3
0
def publisher_snap_metrics(snap_name):
    """
    A view to display the snap metrics page for specific snaps.

    This queries the snapcraft API (api.snapcraft.io) and passes
    some of the data through to the publisher/metrics.html template,
    with appropriate sanitation.
    """
    try:
        details = api.get_snap_info(snap_name, flask.session)
    except ApiResponseErrorList as api_response_error_list:
        if api_response_error_list.status_code == 404:
            return flask.abort(404, 'No snap named {}'.format(snap_name))
        else:
            return _handle_error_list(api_response_error_list.errors)
    except ApiError as api_error:
        return _handle_errors(api_error)

    metric_requested = logic.extract_metrics_period(
        flask.request.args.get('period', default='30d', type=str))

    installed_base_metric = logic.verify_base_metrics(
        flask.request.args.get('active-devices', default='version', type=str))

    installed_base = logic.get_installed_based_metric(installed_base_metric)
    metrics_query_json = metrics_helper.build_metrics_json(
        snap_id=details['snap_id'],
        installed_base=installed_base,
        metric_period=metric_requested['int'],
        metric_bucket=metric_requested['bucket'])

    try:
        metrics_response = api.get_publisher_metrics(flask.session,
                                                     json=metrics_query_json)
    except ApiResponseErrorList as api_response_error_list:
        if api_response_error_list.status_code == 404:
            return flask.abort(404, 'No snap named {}'.format(snap_name))
        else:
            return _handle_error_list(api_response_error_list.errors)
    except ApiError as api_error:
        return _handle_errors(api_error)

    active_metrics = metrics_helper.find_metric(metrics_response['metrics'],
                                                installed_base)
    active_devices = metrics.ActiveDevices(name=active_metrics['metric_name'],
                                           series=active_metrics['series'],
                                           buckets=active_metrics['buckets'],
                                           status=active_metrics['status'])

    latest_active = 0
    if active_devices:
        latest_active = active_devices.get_number_latest_active_devices()

    country_metric = metrics_helper.find_metric(
        metrics_response['metrics'], "weekly_installed_base_by_country")
    country_devices = metrics.CountryDevices(
        name=country_metric['metric_name'],
        series=country_metric['series'],
        buckets=country_metric['buckets'],
        status=country_metric['status'],
        private=True)

    territories_total = 0
    if country_devices:
        territories_total = country_devices.get_number_territories()

    nodata = not any([country_devices, active_devices])

    context = {
        # Data direct from details API
        'snap_name': snap_name,
        'snap_title': details['title'],
        'metric_period': metric_requested['period'],
        'active_device_metric': installed_base_metric,

        # Metrics data
        'nodata': nodata,
        'latest_active_devices': latest_active,
        'active_devices': dict(active_devices),
        'territories_total': territories_total,
        'territories': country_devices.country_data,

        # Context info
        'is_linux': 'Linux' in flask.request.headers['User-Agent']
    }

    return flask.render_template('publisher/metrics.html', **context)
예제 #4
0
    def snap_details(snap_name):
        """
        A view to display the snap details page for specific snaps.

        This queries the snapcraft API (api.snapcraft.io) and passes
        some of the data through to the snap-details.html template,
        with appropriate sanitation.
        """

        error_info = {}
        status_code = 200

        try:
            details = api.get_snap_details(snap_name)
        except ApiTimeoutError as api_timeout_error:
            flask.abort(504, str(api_timeout_error))
        except ApiResponseDecodeError as api_response_decode_error:
            flask.abort(502, str(api_response_decode_error))
        except ApiResponseErrorList as api_response_error_list:
            if api_response_error_list.status_code == 404:
                flask.abort(404, "No snap named {}".format(snap_name))
            else:
                if api_response_error_list.errors:
                    error_messages = ", ".join(
                        api_response_error_list.errors.key())
                else:
                    error_messages = "An error occurred."
                flask.abort(502, error_messages)
        except ApiResponseError as api_response_error:
            flask.abort(502, str(api_response_error))
        except ApiCircuitBreaker:
            flask.abort(503)
        except ApiError as api_error:
            flask.abort(502, str(api_error))

        # When removing all the channel maps of an exsting snap the API,
        # responds that the snaps still exists with data.
        # Return a 404 if not channel maps, to avoid having a error.
        # For example: mir-kiosk-browser
        if not details.get("channel-map"):
            flask.abort(404, "No snap named {}".format(snap_name))

        formatted_paragraphs = logic.split_description_into_paragraphs(
            details["snap"]["description"])

        channel_maps_list = logic.convert_channel_maps(
            details.get("channel-map"))

        latest_channel = logic.get_last_updated_version(
            details.get("channel-map"))

        last_updated = latest_channel["created-at"]
        last_version = latest_channel["version"]
        binary_filesize = latest_channel["download"]["size"]

        country_metric_name = "weekly_installed_base_by_country_percent"
        os_metric_name = "weekly_installed_base_by_operating_system_normalized"

        webapp_config = flask.current_app.config.get("WEBAPP_CONFIG")

        if "STORE_QUERY" not in webapp_config:
            end = metrics_helper.get_last_metrics_processed_date()

            metrics_query_json = [
                metrics_helper.get_filter(
                    metric_name=country_metric_name,
                    snap_id=details["snap-id"],
                    start=end,
                    end=end,
                ),
                metrics_helper.get_filter(
                    metric_name=os_metric_name,
                    snap_id=details["snap-id"],
                    start=end,
                    end=end,
                ),
            ]

            try:
                metrics_response = api.get_public_metrics(
                    snap_name, metrics_query_json)
            except ApiError as api_error:
                status_code, error_info = _handle_errors(api_error)
                metrics_response = None

            os_metrics = None
            country_devices = None
            if metrics_response:
                oses = metrics_helper.find_metric(metrics_response,
                                                  os_metric_name)
                os_metrics = metrics.OsMetric(
                    name=oses["metric_name"],
                    series=oses["series"],
                    buckets=oses["buckets"],
                    status=oses["status"],
                )

                territories = metrics_helper.find_metric(
                    metrics_response, country_metric_name)
                country_devices = metrics.CountryDevices(
                    name=territories["metric_name"],
                    series=territories["series"],
                    buckets=territories["buckets"],
                    status=territories["status"],
                    private=False,
                )
        else:
            os_metrics = None
            country_devices = None

        # filter out banner and banner-icon images from screenshots
        screenshots = [
            m["url"] for m in details["snap"]["media"]
            if m["type"] == "screenshot" and "banner" not in m["url"]
        ]
        icons = [
            m["url"] for m in details["snap"]["media"] if m["type"] == "icon"
        ]

        # until default tracks are supported by the API we special case node
        # to use 10, rather then latest
        default_track = "10" if details["name"] == "node" else "latest"

        lowest_risk_available = logic.get_lowest_available_risk(
            channel_maps_list, default_track)

        confinement = logic.get_confinement(channel_maps_list, default_track,
                                            lowest_risk_available)

        context = {
            # Data direct from details API
            "snap_title":
            details["snap"]["title"],
            "package_name":
            details["name"],
            "icon_url":
            icons[0] if icons else None,
            "version":
            last_version,
            "license":
            details["snap"]["license"],
            "publisher":
            details["snap"]["publisher"]["display-name"],
            "username":
            details["snap"]["publisher"]["username"],
            "screenshots":
            screenshots,
            "prices":
            details["snap"]["prices"],
            "contact":
            details["snap"].get("contact"),
            "website":
            details["snap"].get("website"),
            "summary":
            details["snap"]["summary"],
            "description_paragraphs":
            formatted_paragraphs,
            "channel_map":
            channel_maps_list,
            "has_stable":
            logic.has_stable(channel_maps_list),
            "developer_validation":
            details["snap"]["publisher"]["validation"],
            "default_track":
            default_track,
            "lowest_risk_available":
            lowest_risk_available,
            "confinement":
            confinement,
            # Transformed API data
            "filesize":
            humanize.naturalsize(binary_filesize),
            "last_updated": (humanize.naturaldate(parser.parse(last_updated))),
            "last_updated_raw":
            last_updated,
            # Data from metrics API
            "countries":
            (country_devices.country_data if country_devices else None),
            "normalized_os":
            os_metrics.os if os_metrics else None,
            # Context info
            "is_linux":
            ("Linux" in flask.request.headers.get("User-Agent", "")
             and "Android" not in flask.request.headers.get("User-Agent", "")),
            "error_info":
            error_info,
        }

        return (
            flask.render_template("store/snap-details.html", **context),
            status_code,
        )
예제 #5
0
def snap_details(snap_name):
    """
    A view to display the snap details page for specific snaps.

    This queries the snapcraft API (api.snapcraft.io) and passes
    some of the data through to the snap-details.html template,
    with appropriate sanitation.
    """

    error_info = {}
    default_channel = logic.get_default_channel(snap_name)

    try:
        details = api.get_snap_details(
                snap_name, default_channel)
    except ApiTimeoutError as api_timeout_error:
        flask.abort(504, str(api_timeout_error))
    except ApiResponseDecodeError as api_response_decode_error:
        flask.abort(502, str(api_response_decode_error))
    except ApiResponseErrorList as api_response_error_list:
        if api_response_error_list.status_code == 404:
            flask.abort(404, 'No snap named {}'.format(snap_name))
        else:
            error_messages = ', '.join(api_response_error_list.errors.key())
            flask.abort(502, error_messages)
    except ApiResponseError as api_response_error:
        flask.abort(502, str(api_response_error))
    except ApiError as api_error:
        flask.abort(502, str(api_error))

    formatted_paragraphs = logic.split_description_into_paragraphs(
        details['description'])

    channel_maps_list = logic.convert_channel_maps(
        details.get('channel_maps_list'))

    end = metrics_helper.get_last_metrics_processed_date()
    country_metric_name = 'weekly_installed_base_by_country_percent'
    os_metric_name = 'weekly_installed_base_by_operating_system_normalized'

    metrics_query_json = [
        metrics_helper.get_filter(
            metric_name=country_metric_name,
            snap_id=details['snap_id'],
            start=end,
            end=end),
        metrics_helper.get_filter(
            metric_name=os_metric_name,
            snap_id=details['snap_id'],
            start=end,
            end=end)]

    status_code = 200
    try:
        metrics_response = api.get_public_metrics(
            snap_name,
            metrics_query_json)
    except ApiError as api_error:
        status_code, error_info = _handle_errors(api_error)

    oses = metrics_helper.find_metric(metrics_response, os_metric_name)
    os_metrics = metrics.OsMetric(
        name=oses['metric_name'],
        series=oses['series'],
        buckets=oses['buckets'],
        status=oses['status'])

    territories = metrics_helper.find_metric(
        metrics_response, country_metric_name)
    country_devices = metrics.CountryDevices(
        name=territories['metric_name'],
        series=territories['series'],
        buckets=territories['buckets'],
        status=territories['status'],
        private=False)

    # filter out banner and banner-icon images from screenshots
    screenshots = [
        m['url'] for m in details['media']
        if m['type'] == "screenshot" and "banner" not in m['url']
    ]
    icons = [m['url'] for m in details['media'] if m['type'] == "icon"]

    context = {
        # Data direct from details API
        'snap_title': details['title'],
        'package_name': details['package_name'],
        'icon_url': icons[0] if icons else None,
        'version': details['version'],
        'revision': details['revision'],
        'license': details['license'],
        'publisher': details['publisher'],
        'screenshots': screenshots,
        'prices': details['prices'],
        'contact': details.get('contact'),
        'website': details.get('website'),
        'summary': details['summary'],
        'description_paragraphs': formatted_paragraphs,
        'channel_map': channel_maps_list,
        'default_channel': default_channel,

        # Transformed API data
        'filesize': humanize.naturalsize(details['binary_filesize']),
        'last_updated': (
            humanize.naturaldate(
                parser.parse(details.get('last_updated'))
            )
        ),
        'last_updated_raw': details.get('last_updated'),

        # Data from metrics API
        'countries': country_devices.country_data,
        'normalized_os': os_metrics.os,

        # Context info
        'is_linux': (
            'Linux' in flask.request.headers.get('User-Agent', '') and
            'Android' not in flask.request.headers.get('User-Agent', '')
        ),

        'error_info': error_info
    }

    return flask.render_template(
        'snap-details.html',
        **context
    ), status_code
예제 #6
0
def publisher_snap_metrics(snap_name):
    """
    A view to display the snap metrics page for specific snaps.

    This queries the snapcraft API (api.snapcraft.io) and passes
    some of the data through to the publisher/metrics.html template,
    with appropriate sanitation.
    """
    try:
        details = api.get_snap_info(snap_name, flask.session)
    except ApiResponseErrorList as api_response_error_list:
        if api_response_error_list.status_code == 404:
            return flask.abort(404, "No snap named {}".format(snap_name))
        else:
            return _handle_error_list(api_response_error_list.errors)
    except ApiError as api_error:
        return _handle_errors(api_error)

    metric_requested = logic.extract_metrics_period(
        flask.request.args.get("period", default="30d", type=str)
    )

    installed_base_metric = logic.verify_base_metrics(
        flask.request.args.get("active-devices", default="version", type=str)
    )

    installed_base = logic.get_installed_based_metric(installed_base_metric)
    metrics_query_json = metrics_helper.build_metrics_json(
        snap_id=details["snap_id"],
        installed_base=installed_base,
        metric_period=metric_requested["int"],
        metric_bucket=metric_requested["bucket"],
    )

    try:
        metrics_response = api.get_publisher_metrics(
            flask.session, json=metrics_query_json
        )
    except ApiResponseErrorList as api_response_error_list:
        if api_response_error_list.status_code == 404:
            return flask.abort(404, "No snap named {}".format(snap_name))
        else:
            return _handle_error_list(api_response_error_list.errors)
    except ApiError as api_error:
        return _handle_errors(api_error)

    active_metrics = metrics_helper.find_metric(
        metrics_response["metrics"], installed_base
    )
    active_devices = metrics.ActiveDevices(
        name=active_metrics["metric_name"],
        series=active_metrics["series"],
        buckets=active_metrics["buckets"],
        status=active_metrics["status"],
    )

    latest_active = 0
    if active_devices:
        latest_active = active_devices.get_number_latest_active_devices()

    country_metric = metrics_helper.find_metric(
        metrics_response["metrics"], "weekly_installed_base_by_country"
    )
    country_devices = metrics.CountryDevices(
        name=country_metric["metric_name"],
        series=country_metric["series"],
        buckets=country_metric["buckets"],
        status=country_metric["status"],
        private=True,
    )

    territories_total = 0
    if country_devices:
        territories_total = country_devices.get_number_territories()

    nodata = not any([country_devices, active_devices])

    context = {
        # Data direct from details API
        "snap_name": snap_name,
        "snap_title": details["title"],
        "metric_period": metric_requested["period"],
        "active_device_metric": installed_base_metric,
        "private": details["private"],
        # Metrics data
        "nodata": nodata,
        "latest_active_devices": latest_active,
        "active_devices": dict(active_devices),
        "territories_total": territories_total,
        "territories": country_devices.country_data,
        # Context info
        "is_linux": "Linux" in flask.request.headers["User-Agent"],
    }

    return flask.render_template("publisher/metrics.html", **context)
예제 #7
0
    def snap_details(snap_name):
        """
        A view to display the snap details page for specific snaps.

        This queries the snapcraft API (api.snapcraft.io) and passes
        some of the data through to the snap-details.html template,
        with appropriate sanitation.
        """

        error_info = {}

        try:
            details = api.get_snap_details(snap_name)
        except ApiTimeoutError as api_timeout_error:
            flask.abort(504, str(api_timeout_error))
        except ApiResponseDecodeError as api_response_decode_error:
            flask.abort(502, str(api_response_decode_error))
        except ApiResponseErrorList as api_response_error_list:
            if api_response_error_list.status_code == 404:
                flask.abort(404, 'No snap named {}'.format(snap_name))
            else:
                error_messages = ', '.join(
                    api_response_error_list.errors.key())
                flask.abort(502, error_messages)
        except ApiResponseError as api_response_error:
            flask.abort(502, str(api_response_error))
        except ApiError as api_error:
            flask.abort(502, str(api_error))

        # When removing all the channel maps of an exsting snap the API,
        # responds that the snaps still exists with data.
        # Return a 404 if not channel maps, to avoid having a error.
        # For example: mir-kiosk-browser
        if not details.get('channel-map'):
            flask.abort(404, 'No snap named {}'.format(snap_name))

        formatted_paragraphs = logic.split_description_into_paragraphs(
            details['snap']['description'])

        channel_maps_list = logic.convert_channel_maps(
            details.get('channel-map'))

        latest_channel = logic.get_last_updated_version(
            details.get('channel-map'))

        last_updated = latest_channel['created-at']
        last_version = latest_channel['version']
        binary_filesize = latest_channel['download']['size']

        end = metrics_helper.get_last_metrics_processed_date()
        country_metric_name = 'weekly_installed_base_by_country_percent'
        os_metric_name = 'weekly_installed_base_by_operating_system_normalized'

        metrics_query_json = [
            metrics_helper.get_filter(metric_name=country_metric_name,
                                      snap_id=details['snap-id'],
                                      start=end,
                                      end=end),
            metrics_helper.get_filter(metric_name=os_metric_name,
                                      snap_id=details['snap-id'],
                                      start=end,
                                      end=end)
        ]

        status_code = 200
        try:
            metrics_response = api.get_public_metrics(snap_name,
                                                      metrics_query_json)
        except ApiError as api_error:
            status_code, error_info = _handle_errors(api_error)
            metrics_response = None

        os_metrics = None
        country_devices = None
        if metrics_response:
            oses = metrics_helper.find_metric(metrics_response, os_metric_name)
            os_metrics = metrics.OsMetric(name=oses['metric_name'],
                                          series=oses['series'],
                                          buckets=oses['buckets'],
                                          status=oses['status'])

            territories = metrics_helper.find_metric(metrics_response,
                                                     country_metric_name)
            country_devices = metrics.CountryDevices(
                name=territories['metric_name'],
                series=territories['series'],
                buckets=territories['buckets'],
                status=territories['status'],
                private=False)

        # filter out banner and banner-icon images from screenshots
        screenshots = [
            m['url'] for m in details['snap']['media']
            if m['type'] == "screenshot" and "banner" not in m['url']
        ]
        icons = [
            m['url'] for m in details['snap']['media'] if m['type'] == "icon"
        ]

        # until default tracks are supported by the API we special case node
        # to use 10, rather then latest
        default_track = '10' if details['name'] == 'node' else 'latest'

        lowest_risk_available = logic.get_lowest_available_risk(
            channel_maps_list, default_track)

        confinement = logic.get_confinement(channel_maps_list, default_track,
                                            lowest_risk_available)

        context = {
            # Data direct from details API
            'snap_title':
            details['snap']['title'],
            'package_name':
            details['name'],
            'icon_url':
            icons[0] if icons else None,
            'version':
            last_version,
            'license':
            details['snap']['license'],
            'publisher':
            details['snap']['publisher']['display-name'],
            'screenshots':
            screenshots,
            'prices':
            details['snap']['prices'],
            'contact':
            details['snap'].get('contact'),
            'website':
            details['snap'].get('website'),
            'summary':
            details['snap']['summary'],
            'description_paragraphs':
            formatted_paragraphs,
            'channel_map':
            channel_maps_list,
            'has_stable':
            logic.has_stable(channel_maps_list),
            'developer_validation':
            details['snap']['publisher']['validation'],
            'default_track':
            default_track,
            'lowest_risk_available':
            lowest_risk_available,
            'confinement':
            confinement,

            # Transformed API data
            'filesize':
            humanize.naturalsize(binary_filesize),
            'last_updated': (humanize.naturaldate(parser.parse(last_updated))),
            'last_updated_raw':
            last_updated,

            # Data from metrics API
            'countries':
            (country_devices.country_data if country_devices else None),
            'normalized_os':
            os_metrics.os if os_metrics else None,

            # Context info
            'is_linux':
            ('Linux' in flask.request.headers.get('User-Agent', '')
             and 'Android' not in flask.request.headers.get('User-Agent', '')),
            'error_info':
            error_info
        }

        return flask.render_template('store/snap-details.html',
                                     **context), status_code