Esempio n. 1
0
    def test_one_track_channel_map(self):
        channel_maps_list = [{
            "channel": {
                "name": "channel",
                "architecture": "arch",
                "track": "track",
                "risk": "risk",
                "released-at": "2019-01-12T16:48:41.821037+00:00",
            },
            "created-at": "2019-01-12T16:48:41.821037+00:00",
            "confinement": "confinement",
            "download": {
                "size": "size"
            },
            "version": "version",
        }]

        result = logic.convert_channel_maps(channel_maps_list)
        expected_result = {
            "arch": {
                "track": [{
                    "channel": "channel",
                    "released-at": "12 January 2019",
                    "confinement": "confinement",
                    "size": "size",
                    "risk": "risk",
                    "version": "version",
                }]
            }
        }

        self.assertEqual(result, expected_result)
    def test_one_track_channel_map(self):
        channel_maps_list = [
            {
                "channel": {
                    "name": "channel",
                    "architecture": "arch",
                    "track": "track",
                    "risk": "risk",
                },
                "created-at": "2019-01-12T16:48:41.821037+00:00",
                "confinement": "confinement",
                "download": {"size": "size"},
                "version": "version",
            }
        ]

        result = logic.convert_channel_maps(channel_maps_list)
        expected_result = {
            "arch": {
                "track": [
                    {
                        "channel": "channel",
                        "created-at": "12 January 2019",
                        "confinement": "confinement",
                        "size": "size",
                        "risk": "risk",
                        "version": "version",
                    }
                ]
            }
        }

        self.assertEqual(result, expected_result)
    def test_one_track_channel_map(self):
        channel_maps_list = [{
            "channel": {
                "name": "channel",
                "architecture": "arch",
                "track": "track",
                "risk": "risk",
            },
            "created-at": "date",
            "confinement": "confinement",
            "download": {
                "size": "size"
            },
            "version": "version",
        }]

        result = logic.convert_channel_maps(channel_maps_list)
        expected_result = {
            "arch": {
                "track": [{
                    "channel": "channel",
                    "created-at": "date",
                    "confinement": "confinement",
                    "size": "size",
                    "risk": "risk",
                    "version": "version",
                }]
            }
        }

        self.assertEqual(result, expected_result)
Esempio n. 4
0
    def test_one_track_channel_map(self):
        channel_maps_list = [{
            'channel': {
                'name': 'channel',
                'architecture': 'arch',
                'track': 'track',
                'risk': 'risk'
            },
            'created-at': 'date',
            'confinement': 'confinement',
            'download': {
                'size': 'size'
            },
            'version': 'version'
        }]

        result = logic.convert_channel_maps(channel_maps_list)
        expected_result = {
            'arch': {
                'track': [{
                    'channel': 'channel',
                    'created-at': 'date',
                    'confinement': 'confinement',
                    'size': 'size',
                    'risk': 'risk',
                    'version': 'version'
                }]
            }
        }

        self.assertEqual(result, expected_result)
    def test_multiple_track_different_arch_channel_map(self):
        channel_maps_list = [
            {
                "channel": {
                    "name": "channel",
                    "architecture": "arch",
                    "track": "track",
                    "risk": "risk",
                },
                "created-at": "date",
                "confinement": "confinement",
                "download": {"size": "size"},
                "version": "version",
            },
            {
                "channel": {
                    "name": "channel",
                    "architecture": "arch1",
                    "track": "track",
                    "risk": "risk",
                },
                "created-at": "date",
                "confinement": "confinement",
                "download": {"size": "size"},
                "version": "version",
            },
        ]

        result = logic.convert_channel_maps(channel_maps_list)
        expected_result = {
            "arch": {
                "track": [
                    {
                        "channel": "channel",
                        "created-at": "date",
                        "confinement": "confinement",
                        "size": "size",
                        "risk": "risk",
                        "version": "version",
                    }
                ]
            },
            "arch1": {
                "track": [
                    {
                        "channel": "channel",
                        "created-at": "date",
                        "confinement": "confinement",
                        "size": "size",
                        "risk": "risk",
                        "version": "version",
                    }
                ]
            },
        }

        self.assertEqual(result, expected_result)
    def test_empty_channel_map(self):
        channel_maps_list = []
        result = logic.convert_channel_maps(channel_maps_list)

        self.assertEqual(result, {})
    def _get_context_snap_details(snap_name):
        try:
            details = api.get_item_details(snap_name, api_version=2)
        except StoreApiTimeoutError as api_timeout_error:
            flask.abort(504, str(api_timeout_error))
        except StoreApiResponseDecodeError as api_response_decode_error:
            flask.abort(502, str(api_response_decode_error))
        except StoreApiResponseErrorList 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 StoreApiResponseError as api_response_error:
            flask.abort(502, str(api_response_error))
        except StoreApiCircuitBreaker:
            flask.abort(503)
        except (StoreApiError, ApiError) as api_error:
            flask.abort(502, str(api_error))

        # When removing all the channel maps of an existing 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))

        clean_description = bleach.clean(details["snap"]["description"],
                                         tags=[])
        formatted_description = parse_markdown_description(clean_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"]

        # filter out banner and banner-icon images from screenshots
        screenshots = logic.filter_screenshots(details["snap"]["media"])

        icons = logic.get_icon(details["snap"]["media"])

        publisher_info = helpers.get_yaml(
            "{}{}.yaml".format(
                flask.current_app.config["CONTENT_DIRECTORY"]
                ["PUBLISHER_PAGES"],
                details["snap"]["publisher"]["username"],
            ),
            typ="safe",
        )

        publisher_snaps = helpers.get_yaml(
            "{}{}-snaps.yaml".format(
                flask.current_app.config["CONTENT_DIRECTORY"]
                ["PUBLISHER_PAGES"],
                details["snap"]["publisher"]["username"],
            ),
            typ="safe",
        )

        publisher_featured_snaps = None

        if publisher_info:
            publisher_featured_snaps = publisher_info.get("featured_snaps")
            publisher_snaps = logic.get_n_random_snaps(
                publisher_snaps["snaps"], 4)

        videos = logic.get_videos(details["snap"]["media"])

        # until default tracks are supported by the API we special case node
        # to use 10, rather then latest
        default_track = helpers.get_default_track(details["name"])
        if not default_track:
            default_track = (details.get("default-track")
                             if details.get("default-track") 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)

        last_version = logic.get_version(channel_maps_list, default_track,
                                         lowest_risk_available)

        is_users_snap = False
        if authentication.is_authenticated(flask.session):
            if (flask.session.get("openid").get("nickname")
                    == details["snap"]["publisher"]["username"]
                ) or ("user_shared_snaps" in flask.session
                      and snap_name in flask.session.get("user_shared_snaps")):
                is_users_snap = True

        # build list of categories of a snap
        categories = logic.get_snap_categories(details["snap"]["categories"])

        developer = logic.get_snap_developer(details["name"])

        context = {
            "snap-id": details.get("snap-id"),
            # Data direct from details API
            "snap_title": details["snap"]["title"],
            "package_name": details["name"],
            "categories": categories,
            "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,
            "videos": videos,
            "publisher_snaps": publisher_snaps,
            "publisher_featured_snaps": publisher_featured_snaps,
            "has_publisher_page": publisher_info is not None,
            "prices": details["snap"]["prices"],
            "contact": details["snap"].get("contact"),
            "website": details["snap"].get("website"),
            "summary": details["snap"]["summary"],
            "description": formatted_description,
            "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,
            "trending": details["snap"]["trending"],
            # Transformed API data
            "filesize": humanize.naturalsize(binary_filesize),
            "last_updated": logic.convert_date(last_updated),
            "last_updated_raw": last_updated,
            "is_users_snap": is_users_snap,
            "unlisted": details["snap"]["unlisted"],
            "developer": developer,
            # TODO: This is horrible and hacky
            "appliances": {
                "adguard-home": "adguard",
                "mosquitto": "mosquitto",
                "nextcloud": "nextcloud",
                "plexmediaserver": "plex",
                "openhab": "openhab",
            },
        }

        return context
    def test_empty_channel_map(self):
        channel_maps_list = []
        result = logic.convert_channel_maps(channel_maps_list)

        self.assertEqual(result, {})
    def _get_context_snap_details(snap_name):
        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))

        clean_description = bleach.clean(details["snap"]["description"])
        formatted_description = parse_markdown_description(clean_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"]

        # filter out banner and banner-icon images from screenshots
        screenshots = logic.filter_screenshots(details["snap"]["media"])

        icons = logic.get_icon(details["snap"]["media"])

        videos = logic.get_videos(details["snap"]["media"])

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

        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
        )

        last_version = logic.get_version(
            channel_maps_list, default_track, lowest_risk_available
        )

        is_users_snap = False
        if flask.session and "openid" in flask.session:
            if (
                flask.session.get("openid").get("nickname")
                == details["snap"]["publisher"]["username"]
            ):
                is_users_snap = True

        # build list of categories of a snap
        categories = logic.get_snap_categories(details["snap"]["categories"])

        context = {
            "snap-id": details.get("snap-id"),
            # Data direct from details API
            "snap_title": details["snap"]["title"],
            "package_name": details["name"],
            "categories": categories,
            "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,
            "videos": videos,
            "prices": details["snap"]["prices"],
            "contact": details["snap"].get("contact"),
            "website": details["snap"].get("website"),
            "summary": details["snap"]["summary"],
            "description": formatted_description,
            "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": logic.convert_date(last_updated),
            "last_updated_raw": last_updated,
            "is_users_snap": is_users_snap,
        }

        return context
Esempio n. 10
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,
        )
Esempio n. 11
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
Esempio n. 12
0
    def _get_context_snap_details(snap_name):
        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))

        clean_description = bleach.clean(details["snap"]["description"],
                                         tags=[])
        formatted_description = parse_markdown_description(clean_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"]

        # filter out banner and banner-icon images from screenshots
        screenshots = logic.filter_screenshots(details["snap"]["media"])

        icons = logic.get_icon(details["snap"]["media"])

        videos = logic.get_videos(details["snap"]["media"])

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

        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)

        last_version = logic.get_version(channel_maps_list, default_track,
                                         lowest_risk_available)

        is_users_snap = False
        if flask.session and "openid" in flask.session:
            if (flask.session.get("openid").get("nickname") == details["snap"]
                ["publisher"]["username"]):
                is_users_snap = True

        # build list of categories of a snap
        categories = logic.get_snap_categories(details["snap"]["categories"])

        context = {
            "snap-id": details.get("snap-id"),
            # Data direct from details API
            "snap_title": details["snap"]["title"],
            "package_name": details["name"],
            "categories": categories,
            "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,
            "videos": videos,
            "prices": details["snap"]["prices"],
            "contact": details["snap"].get("contact"),
            "website": details["snap"].get("website"),
            "summary": details["snap"]["summary"],
            "description": formatted_description,
            "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": logic.convert_date(last_updated),
            "last_updated_raw": last_updated,
            "is_users_snap": is_users_snap,
        }

        return context
Esempio n. 13
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:
                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"]

        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,
        )