def post_build(snap_name): 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) try: if launchpad.is_snap_building(details["snap_name"]): launchpad.cancel_snap_builds(details["snap_name"]) launchpad.build_snap(details["snap_name"]) except HTTPError as e: # Timeout or not found from Launchpad if e.response.status_code in [408, 404]: flask.flash("An error occurred, please try again.", "negative") return flask.redirect( flask.url_for(".get_snap_builds", snap_name=snap_name)) raise e flask.flash("Build triggered", "positive") return flask.redirect( flask.url_for(".get_snap_builds", snap_name=snap_name))
def get_publicise_badges(snap_name): try: snap_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) if snap_details["private"]: return flask.abort(404, "No snap named {}".format(snap_name)) try: snap_public_details = store_api.get_item_details(snap_name, api_version=2) except StoreApiError as api_error: return _handle_error(api_error) context = { "snap_name": snap_details["snap_name"], "snap_title": snap_details["title"], "snap_id": snap_details["snap_id"], "trending": snap_public_details["snap"]["trending"], } return flask.render_template("publisher/publicise/github_badges.html", **context)
def post_update_gh_webhooks(snap_name): 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) lp_snap = launchpad.get_snap_by_store_name(details["snap_name"]) gh_link = lp_snap["git_repository_url"][19:] gh_owner, gh_repo = gh_link.split("/") github = GitHub(flask.session.get("github_auth_secret")) old_url = f"https://build.snapcraft.io/{gh_owner}/{gh_repo}/webhook/notify" old_hook = github.get_hook_by_url(gh_owner, gh_repo, old_url) if old_hook: github.update_hook_url( gh_owner, gh_repo, old_hook["id"], f"https://snapcraft.io/{snap_name}/webhook/notify", ) return flask.redirect( flask.url_for(".get_snap_builds", snap_name=snap_name))
def get_publicise_cards(snap_name): try: snap_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) if snap_details["private"]: return flask.abort(404, "No snap named {}".format(snap_name)) context = { "snap_name": snap_details["snap_name"], "snap_title": snap_details["title"], "snap_id": snap_details["snap_id"], "available": {}, "download_version": "v1.2", } return flask.render_template( "publisher/publicise/embedded_cards.html", **context )
def get_publicise_cards(snap_name): try: snap_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) if snap_details["private"]: return flask.abort(404, "No snap named {}".format(snap_name)) screenshots = filter_screenshots(snap_details["media"]) has_screenshot = True if screenshots else False context = { "has_screenshot": has_screenshot, "snap_name": snap_details["snap_name"], "snap_title": snap_details["title"], "snap_id": snap_details["snap_id"], } return flask.render_template("publisher/publicise/embedded_cards.html", **context)
def get_release_history(snap_name): try: release_history = api.snap_release_history(flask.session, snap_name) except ApiResponseErrorList as api_response_error_list: return _handle_error_list(api_response_error_list.errors) except ApiError as api_error: return _handle_errors(api_error) try: info = 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) context = { "snap_name": snap_name, "release_history": release_history, "private": info.get("private"), "channel_maps_list": info.get("channel_maps_list"), } return flask.render_template("publisher/release-history.html", **context)
def get_release_history(snap_name): try: release_history = api.snap_release_history(flask.session, snap_name) except ApiResponseErrorList as api_response_error_list: return _handle_error_list(api_response_error_list.errors) except ApiError as api_error: return _handle_error(api_error) try: info = 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) context = { "snap_name": snap_name, "release_history": release_history, "private": info.get("private"), "channel_maps_list": info.get("channel_maps_list"), "default_track": info.get("default_track"), } return flask.render_template("publisher/release-history.html", **context)
def get_listing_snap(snap_name): try: snap_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) details_metrics_enabled = snap_details["public_metrics_enabled"] details_blacklist = snap_details["public_metrics_blacklist"] is_on_stable = logic.is_snap_on_stable(snap_details["channel_maps_list"]) # Filter icon & screenshot urls from the media set. icon_urls = [ m["url"] for m in snap_details["media"] if m["type"] == "icon" ] screenshot_urls = [ m["url"] for m in snap_details["media"] if m["type"] == "screenshot" ] licenses = [] for license in get_licenses(): licenses.append({"key": license["licenseId"], "name": license["name"]}) license = snap_details["license"] license_type = "custom" if " AND " not in license.upper() and " WITH " not in license.upper(): license_type = "simple" context = { "snap_id": snap_details["snap_id"], "snap_name": snap_details["snap_name"], "snap_title": snap_details["title"], "summary": snap_details["summary"], "description": snap_details["description"], "icon_url": icon_urls[0] if icon_urls else None, "publisher_name": snap_details["publisher"]["display-name"], "username": snap_details["publisher"]["username"], "screenshot_urls": screenshot_urls, "contact": snap_details["contact"], "private": snap_details["private"], "website": snap_details["website"] or "", "public_metrics_enabled": details_metrics_enabled, "public_metrics_blacklist": details_blacklist, "license": license, "license_type": license_type, "licenses": licenses, "video_urls": snap_details["video_urls"], "is_on_stable": is_on_stable, } return flask.render_template("publisher/listing.html", **context)
def get_publicise(snap_name): try: snap_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) if snap_details["private"]: return flask.abort(404, "No snap named {}".format(snap_name)) available_languages = { "en": { "title": "English", "text": "Get it from the Snap Store" }, "de": { "title": "Deutsch", "text": "Installieren vom Snap Store" }, "es": { "title": "Español", "text": "Instalar desde Snap Store" }, "fr": { "title": "Français", "text": "Installer à partir du Snap Store", }, "jp": { "title": "日本語", "text": "Snap Store から入手ください" }, "ru": { "title": "русский язык", "text": "Загрузите из Snap Store" }, "tw": { "title": "中文(台灣)", "text": "安裝軟體敬請移駕 Snap Store" }, } context = { "snap_name": snap_details["snap_name"], "snap_title": snap_details["title"], "snap_id": snap_details["snap_id"], "available": available_languages, "download_version": "v1.2", } return flask.render_template("publisher/publicise.html", **context)
def get_settings(snap_name): try: snap_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) if "whitelist_country_codes" in snap_details: whitelist_country_codes = ( snap_details["whitelist_country_codes"] if len(snap_details["whitelist_country_codes"]) > 0 else []) else: whitelist_country_codes = [] if "blacklist_country_codes" in snap_details: blacklist_country_codes = ( snap_details["blacklist_country_codes"] if len(snap_details["blacklist_country_codes"]) > 0 else []) else: blacklist_country_codes = [] countries = [] for country in pycountry.countries: countries.append({"key": country.alpha_2, "name": country.name}) is_on_lp = False lp_snap = launchpad.get_snap_by_store_name(snap_name) if lp_snap: is_on_lp = True context = { "snap_name": snap_details["snap_name"], "snap_title": snap_details["title"], "snap_id": snap_details["snap_id"], "publisher_name": snap_details["publisher"]["display-name"], "license": license, "private": snap_details["private"], "unlisted": snap_details["unlisted"], "countries": countries, "whitelist_country_codes": whitelist_country_codes, "blacklist_country_codes": blacklist_country_codes, "price": snap_details["price"], "store": snap_details["store"], "keywords": snap_details["keywords"], "status": snap_details["status"], "is_on_lp": is_on_lp, } return flask.render_template("publisher/settings.html", **context)
def get_snap_builds(snap_name): try: details = api.get_snap_info(snap_name, flask.session) # API call to make users without needed permissions refresh the session # Users needs package_upload_request permission to use this feature api.get_package_upload_macaroon(session=flask.session, snap_name=snap_name, channels=["edge"]) 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) context = { "snap_id": details["snap_id"], "snap_name": details["snap_name"], "snap_title": details["title"], "snap_builds_enabled": False, "snap_builds": [], "total_builds": 0, } # Get built snap in launchpad with this store name github = GitHub(flask.session.get("github_auth_secret")) lp_snap = launchpad.get_snap_by_store_name(details["snap_name"]) if lp_snap: # Git repository without GitHub hostname context["github_repository"] = lp_snap["git_repository_url"][19:] github_owner, github_repo = context["github_repository"].split("/") context["yaml_file_exists"] = github.get_snapcraft_yaml_location( github_owner, github_repo) if not context["yaml_file_exists"]: flask.flash("This repository doesn't contain a snapcraft.yaml", "negative") context = get_builds(lp_snap, context, slice(0, BUILDS_PER_PAGE)) else: try: context["github_user"] = github.get_user() except Unauthorized: context["github_user"] = None if context["github_user"]: context["github_orgs"] = github.get_orgs() return flask.render_template("publisher/builds.html", **context)
def post_preview(snap_name): try: snap_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) context = { "publisher": snap_details["publisher"]["display-name"], "username": snap_details["publisher"]["username"], "developer_validation": snap_details["publisher"]["validation"], } state = loads(flask.request.form["state"]) for item in state: if item == "description": context[item] = parse_markdown_description( bleach.clean(state[item]) ) else: context[item] = state[item] context["is_preview"] = True context["package_name"] = context["snap_name"] context["snap_title"] = context["title"] # Images icons = get_icon(context["images"]) context["screenshots"] = filter_screenshots(context["images"]) context["icon_url"] = icons[0] if icons else None context["videos"] = get_videos(context["images"]) # Channel map context["default_track"] = "latest" context["lowest_risk_available"] = "stable" context["version"] = "test" context["has_stable"] = True # metadata context["last_updated"] = "Preview" context["filesize"] = "1mb" # maps context["countries"] = preview_data.get_countries() context["normalized_os"] = preview_data.get_normalised_oses() return flask.render_template("store/snap-details.html", **context)
def post_preview(snap_name): try: snap_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) context = { "publisher": snap_details["publisher"]["display-name"], "username": snap_details["publisher"]["username"], "developer_validation": snap_details["publisher"]["validation"], } state = loads(flask.request.form["state"]) for item in state: if item == "description": context[item] = parse_markdown_description( bleach.clean(state[item], tags=[]) ) else: context[item] = state[item] context["is_preview"] = True context["package_name"] = context["snap_name"] context["snap_title"] = context["title"] # Images icons = get_icon(context["images"]) context["screenshots"] = filter_screenshots(context["images"]) context["icon_url"] = icons[0] if icons else None context["videos"] = get_videos(context["images"]) # Channel map context["default_track"] = "latest" context["lowest_risk_available"] = "stable" context["version"] = "test" context["has_stable"] = True # metadata context["last_updated"] = "Preview" context["filesize"] = "1mb" # maps context["countries"] = preview_data.get_countries() context["normalized_os"] = preview_data.get_normalised_oses() return flask.render_template("store/snap-details.html", **context)
def get_settings(snap_name): try: snap_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) if "whitelist_country_codes" in snap_details: whitelist_country_codes = ( snap_details["whitelist_country_codes"] if len(snap_details["whitelist_country_codes"]) > 0 else [] ) else: whitelist_country_codes = [] if "blacklist_country_codes" in snap_details: blacklist_country_codes = ( snap_details["blacklist_country_codes"] if len(snap_details["blacklist_country_codes"]) > 0 else [] ) else: blacklist_country_codes = [] countries = [] for country in pycountry.countries: countries.append({"key": country.alpha_2, "name": country.name}) context = { "snap_name": snap_details["snap_name"], "snap_title": snap_details["title"], "snap_id": snap_details["snap_id"], "license": license, "private": snap_details["private"], "unlisted": snap_details["unlisted"], "countries": countries, "whitelist_country_codes": whitelist_country_codes, "blacklist_country_codes": blacklist_country_codes, "price": snap_details["price"], "store": snap_details["store"], "keywords": snap_details["keywords"], "status": snap_details["status"], } return flask.render_template("publisher/settings.html", **context)
def get_listing_snap(snap_name): try: snap_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) is_on_stable = logic.is_snap_on_stable(snap_details["channel_maps_list"]) # Filter icon & screenshot urls from the media set. icon_urls = [ m["url"] for m in snap_details["media"] if m["type"] == "icon" ] screenshot_urls = [ m["url"] for m in snap_details["media"] if m["type"] == "screenshot" ] licenses = get_licenses() countries = [] for country in pycountry.countries: countries.append({"alpha_2": country.alpha_2, "name": country.name}) context = { "snap_id": snap_details["snap_id"], "snap_name": snap_details["snap_name"], "snap_title": snap_details["title"], "summary": snap_details["summary"], "description": snap_details["description"], "license": snap_details["license"], "icon_url": icon_urls[0] if icon_urls else None, "publisher_name": snap_details["publisher"]["display-name"], "username": snap_details["publisher"]["username"], "screenshot_urls": screenshot_urls, "contact": snap_details["contact"], "private": snap_details["private"], "website": snap_details["website"] or "", "public_metrics_enabled": snap_details["public_metrics_enabled"], "public_metrics_blacklist": snap_details["public_metrics_blacklist"], "is_on_stable": is_on_stable, "licenses": licenses, "countries": countries, } return flask.render_template("publisher/listing.html", **context)
def get_snap_build(snap_name, build_id): 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) context = { "snap_id": details["snap_id"], "snap_name": details["snap_name"], "snap_title": details["title"], "snap_build": {}, } # Get build by snap name and build_id lp_build = launchpad.get_snap_build(details["snap_name"], build_id) if lp_build: status = map_build_and_upload_states( lp_build["buildstate"], lp_build["store_upload_status"] ) context["snap_build"] = { "id": lp_build["self_link"].split("/")[-1], "arch_tag": lp_build["arch_tag"], "datebuilt": lp_build["datebuilt"], "duration": lp_build["duration"], "logs": lp_build["build_log_url"], "revision_id": lp_build["revision_id"], "status": status, "title": lp_build["title"], } if context["snap_build"]["logs"]: context["raw_logs"] = launchpad.get_snap_build_log( details["snap_name"], build_id ) return flask.render_template("publisher/build.html", **context)
def get_validate_repo(snap_name): 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) owner, repo = flask.request.args.get("repo").split("/") return flask.jsonify( validate_repo( flask.session.get("github_auth_secret"), details["snap_name"], owner, repo, ))
def post_build(snap_name): 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) if launchpad.is_snap_building(details["snap_name"]): launchpad.cancel_snap_builds(details["snap_name"]) launchpad.build_snap(details["snap_name"]) flask.flash("Build triggered", "positive") return flask.redirect( flask.url_for(".get_snap_builds", snap_name=snap_name))
def get_listing_snap(snap_name): try: snap_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) is_on_stable = logic.is_snap_on_stable(snap_details['channel_maps_list']) # Filter icon & screenshot urls from the media set. icon_urls = [ m['url'] for m in snap_details['media'] if m['type'] == 'icon' ] screenshot_urls = [ m['url'] for m in snap_details['media'] if m['type'] == 'screenshot' ] context = { "snap_id": snap_details['snap_id'], "snap_name": snap_details['snap_name'], "snap_title": snap_details['title'], "summary": snap_details['summary'], "description": snap_details['description'], "license": snap_details['license'], "icon_url": icon_urls[0] if icon_urls else None, "publisher_name": snap_details['publisher']['display-name'], "screenshot_urls": screenshot_urls, "contact": snap_details['contact'], "private": snap_details['private'], "website": snap_details['website'] or '', "public_metrics_enabled": snap_details['public_metrics_enabled'], "public_metrics_blacklist": snap_details['public_metrics_blacklist'], "is_on_stable": is_on_stable, } return flask.render_template('publisher/listing.html', **context)
def post_disconnect_repo(snap_name): 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) lp_snap = launchpad.get_snap_by_store_name(snap_name) launchpad.delete_snap(details["snap_name"]) # Try to remove the GitHub webhook if possible if flask.session.get("github_auth_secret"): github = GitHub(flask.session.get("github_auth_secret")) try: gh_owner, gh_repo = lp_snap["git_repository_url"][19:].split("/") old_hook = github.get_hook_by_url( gh_owner, gh_repo, f"https://snapcraft.io/{snap_name}/webhook/notify", ) if old_hook: github.remove_hook( gh_owner, gh_repo, old_hook["id"], ) except HTTPError: pass return flask.redirect( flask.url_for(".get_snap_builds", snap_name=snap_name))
def get_publicise(snap_name): try: snap_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) if snap_details["private"]: return flask.abort(404, "No snap named {}".format(snap_name)) available_languages = { "en": {"title": "English", "text": "Get it from the Snap Store"}, "de": {"title": "Deutsch", "text": "Installieren vom Snap Store"}, "es": {"title": "Español", "text": "Instalar desde Snap Store"}, "fr": { "title": "Français", "text": "Installer à partir du Snap Store", }, "jp": {"title": "日本語", "text": "Snap Store から入手ください"}, "ru": {"title": "русский язык", "text": "Загрузите из Snap Store"}, "tw": {"title": "中文(台灣)", "text": "安裝軟體敬請移駕 Snap Store"}, } context = { "snap_name": snap_details["snap_name"], "snap_title": snap_details["title"], "snap_id": snap_details["snap_id"], "available": available_languages, "download_version": "v1.2", } return flask.render_template( "publisher/publicise/store_buttons.html", **context )
def get_snap_builds_json(snap_name): 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) context = {"snap_builds": []} start = flask.request.args.get("start", 0, type=int) size = flask.request.args.get("size", 15, type=int) build_slice = slice(start, start + size) # Get built snap in launchpad with this store name lp_snap = launchpad.get_snap_by_store_name(details["snap_name"]) if lp_snap: context.update(get_builds(lp_snap, build_slice)) return flask.jsonify(context)
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)
def get_listing_snap(snap_name): try: snap_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) details_metrics_enabled = snap_details["public_metrics_enabled"] details_blacklist = snap_details["public_metrics_blacklist"] is_on_stable = logic.is_snap_on_stable(snap_details["channel_maps_list"]) # Filter icon & screenshot urls from the media set. icon_urls, screenshot_urls, banner_urls = logic.categorise_media( snap_details["media"]) licenses = [] for license in get_licenses(): licenses.append({"key": license["licenseId"], "name": license["name"]}) license = snap_details["license"] license_type = "custom" if " AND " not in license.upper() and " WITH " not in license.upper(): license_type = "simple" referrer = None if flask.request.args.get("from"): referrer = flask.request.args.get("from") try: categories_results = store_api.get_categories() except StoreApiError: categories_results = [] categories = sorted( get_categories(categories_results), key=lambda category: category["slug"], ) snap_categories = logic.replace_reserved_categories_key( snap_details["categories"]) snap_categories = logic.filter_categories(snap_categories) snap_categories["categories"] = [ category["name"] for category in snap_categories["categories"] ] filename = f"publisher/content/listing_tour.yaml" tour_steps = helpers.get_yaml(filename, typ="rt") context = { "snap_id": snap_details["snap_id"], "snap_name": snap_details["snap_name"], "snap_title": snap_details["title"], "snap_categories": snap_categories, "summary": snap_details["summary"], "description": snap_details["description"], "icon_url": icon_urls[0] if icon_urls else None, "publisher_name": snap_details["publisher"]["display-name"], "username": snap_details["publisher"]["username"], "screenshot_urls": screenshot_urls, "banner_urls": banner_urls, "contact": snap_details["contact"], "private": snap_details["private"], "website": snap_details["website"] or "", "public_metrics_enabled": details_metrics_enabled, "public_metrics_blacklist": details_blacklist, "license": license, "license_type": license_type, "licenses": licenses, "video_urls": snap_details["video_urls"], "is_on_stable": is_on_stable, "from": referrer, "categories": categories, "tour_steps": tour_steps, } return flask.render_template("publisher/listing.html", **context)
def post_listing_snap(snap_name): changes = None changed_fields = flask.request.form.get("changes") if changed_fields: changes = loads(changed_fields) if changes: snap_id = flask.request.form.get("snap_id") error_list = [] if "images" in changes: # Add existing screenshots try: current_screenshots = api.snap_screenshots( snap_id, 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) images_json, images_files = logic.build_changed_images( changes["images"], current_screenshots, flask.request.files.get("icon"), flask.request.files.getlist("screenshots"), flask.request.files.get("banner-image"), ) try: api.snap_screenshots(snap_id, flask.session, images_json, images_files) 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: error_list = error_list + api_response_error_list.errors except ApiError as api_error: return _handle_error(api_error) body_json = logic.filter_changes_data(changes) if body_json: if "description" in body_json: body_json["description"] = logic.remove_invalid_characters( body_json["description"]) try: api.snap_metadata(snap_id, flask.session, body_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: error_list = error_list + api_response_error_list.errors except ApiError as api_error: return _handle_error(api_error) if error_list: try: snap_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: error_list = error_list + api_response_error_list.errors except ApiError as api_error: return _handle_error(api_error) field_errors, other_errors = logic.invalid_field_errors(error_list) details_metrics_enabled = snap_details["public_metrics_enabled"] details_blacklist = snap_details["public_metrics_blacklist"] is_on_stable = logic.is_snap_on_stable( snap_details["channel_maps_list"]) # Filter icon & screenshot urls from the media set. icon_urls, screenshot_urls, banner_urls = logic.categorise_media( snap_details["media"]) licenses = [] for license in get_licenses(): licenses.append({ "key": license["licenseId"], "name": license["name"] }) license = snap_details["license"] license_type = "custom" if (" AND " not in license.upper() and " WITH " not in license.upper()): license_type = "simple" try: categories_results = store_api.get_categories() except StoreApiError: categories_results = [] categories = get_categories(categories_results) snap_categories = logic.replace_reserved_categories_key( snap_details["categories"]) snap_categories = logic.filter_categories(snap_categories) filename = f"publisher/content/listing_tour.yaml" tour_steps = helpers.get_yaml(filename, typ="rt") context = { # read-only values from details API "snap_id": snap_details["snap_id"], "snap_name": snap_details["snap_name"], "snap_categories": snap_categories, "icon_url": icon_urls[0] if icon_urls else None, "publisher_name": snap_details["publisher"]["display-name"], "username": snap_details["publisher"]["username"], "screenshot_urls": screenshot_urls, "banner_urls": banner_urls, "display_title": snap_details["title"], # values posted by user "snap_title": (changes["title"] if "title" in changes else snap_details["title"] or ""), "summary": (changes["summary"] if "summary" in changes else snap_details["summary"] or ""), "description": (changes["description"] if "description" in changes else snap_details["description"] or ""), "contact": (changes["contact"] if "contact" in changes else snap_details["contact"] or ""), "private": snap_details["private"], "website": (changes["website"] if "website" in changes else snap_details["website"] or ""), "public_metrics_enabled": details_metrics_enabled, "video_urls": ([changes["video_urls"]] if "video_urls" in changes else snap_details["video_urls"]), "public_metrics_blacklist": details_blacklist, "license": license, "license_type": license_type, "licenses": licenses, "is_on_stable": is_on_stable, "categories": categories, # errors "error_list": error_list, "field_errors": field_errors, "other_errors": other_errors, "tour_steps": tour_steps, } return flask.render_template("publisher/listing.html", **context) flask.flash("Changes applied successfully.", "positive") else: flask.flash("No changes to save.", "information") return flask.redirect( flask.url_for(".get_listing_snap", snap_name=snap_name))
def post_snap_builds(snap_name): 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) redirect_url = flask.url_for(".get_snap_builds", snap_name=snap_name) # Get built snap in launchpad with this store name github = GitHub(flask.session.get("github_auth_secret")) owner, repo = flask.request.form.get("github_repository").split("/") if not github.check_permissions_over_repo(owner, repo): flask.flash( "Your GitHub account doesn't have permissions in the repository", "negative", ) return flask.redirect(redirect_url) repo_validation = validate_repo(flask.session.get("github_auth_secret"), snap_name, owner, repo) if not repo_validation["success"]: flask.flash(repo_validation["error"]["message"], "negative") return flask.redirect(redirect_url) lp_snap = launchpad.get_snap_by_store_name(details["snap_name"]) git_url = f"https://github.com/{owner}/{repo}" if not lp_snap: lp_snap_name = md5(git_url.encode("UTF-8")).hexdigest() try: repo_exist = bool(launchpad.get_snap(lp_snap_name)) except HTTPError as e: if e.response.status_code == 404: repo_exist = False else: raise e if repo_exist: error_msg = ( "The specified GitHub repository is being used by another snap" ) flask.flash(error_msg, "negative") return flask.redirect(redirect_url) macaroon = api.get_package_upload_macaroon(session=flask.session, snap_name=snap_name, channels=["edge" ])["macaroon"] launchpad.create_snap(snap_name, git_url, macaroon) # Create webhook in the repo, it should also trigger the first build github.create_hook(owner, repo, f"https://snapcraft.io/{snap_name}/webhook/notify") flask.flash("The GitHub repository was linked correctly.", "positive") elif lp_snap["git_repository_url"] != git_url: # In the future, create a new record, delete the old one raise AttributeError( f"Snap {snap_name} already has a build repository associated") return flask.redirect(redirect_url)
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)
def post_settings(snap_name): changes = None changed_fields = flask.request.form.get("changes") if changed_fields: changes = loads(changed_fields) if changes: snap_id = flask.request.form.get("snap_id") error_list = [] body_json = logic.filter_changes_data(changes) if body_json: try: api.snap_metadata(snap_id, flask.session, body_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: error_list = error_list + api_response_error_list.errors except ApiError as api_error: return _handle_error(api_error) if error_list: try: snap_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: error_list = error_list + api_response_error_list.errors except ApiError as api_error: return _handle_error(api_error) field_errors, other_errors = logic.invalid_field_errors(error_list) countries = [] for country in pycountry.countries: countries.append({ "key": country.alpha_2, "name": country.name }) if "whitelist_country_codes" in snap_details: whitelist_country_codes = ( snap_details["whitelist_country_codes"] if len(snap_details["whitelist_country_codes"]) > 0 else []) else: whitelist_country_codes = [] if "blacklist_country_codes" in snap_details: blacklist_country_codes = ( snap_details["blacklist_country_codes"] if len(snap_details["blacklist_country_codes"]) > 0 else []) else: blacklist_country_codes = [] context = { # read-only values from details API "snap_name": snap_details["snap_name"], "snap_title": snap_details["title"], "snap_id": snap_details["snap_id"], "private": snap_details["private"], "unlisted": snap_details["unlisted"], "countries": countries, "whitelist_country_codes": whitelist_country_codes, "blacklist_country_codes": blacklist_country_codes, "price": snap_details["price"], "store": snap_details["store"], "keywords": snap_details["keywords"], "status": snap_details["status"], # errors "error_list": error_list, "field_errors": field_errors, "other_errors": other_errors, } return flask.render_template("publisher/settings.html", **context) flask.flash("Changes applied successfully.", "positive") else: flask.flash("No changes to save.", "information") return flask.redirect(flask.url_for(".get_settings", snap_name=snap_name))
def post_listing_snap(snap_name): changes = None changed_fields = flask.request.form.get("changes") if changed_fields: changes = loads(changed_fields) if changes: snap_id = flask.request.form.get("snap_id") error_list = [] if "images" in changes: # Add existing screenshots try: current_screenshots = api.snap_screenshots( snap_id, 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) images_json, images_files = logic.build_changed_images( changes["images"], current_screenshots, flask.request.files.get("icon"), flask.request.files.getlist("screenshots"), ) try: api.snap_screenshots( snap_id, flask.session, images_json, images_files ) 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: error_list = error_list + api_response_error_list.errors except ApiError as api_error: return _handle_errors(api_error) body_json = logic.filter_changes_data(changes) if body_json: if "description" in body_json: body_json["description"] = logic.remove_invalid_characters( body_json["description"] ) try: api.snap_metadata(snap_id, flask.session, body_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: error_list = error_list + api_response_error_list.errors except ApiError as api_error: return _handle_errors(api_error) if error_list: try: snap_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: error_list = error_list + api_response_error_list.errors except ApiError as api_error: return _handle_errors(api_error) field_errors, other_errors = logic.invalid_field_errors(error_list) details_metrics_enabled = snap_details["public_metrics_enabled"] details_blacklist = snap_details["public_metrics_blacklist"] is_on_stable = logic.is_snap_on_stable( snap_details["channel_maps_list"] ) # Filter icon & screenshot urls from the media set. icon_urls = [ m["url"] for m in snap_details["media"] if m["type"] == "icon" ] screenshot_urls = [ m["url"] for m in snap_details["media"] if m["type"] == "screenshot" ] licenses = [] for license in get_licenses(): licenses.append( {"key": license["licenseId"], "name": license["name"]} ) license = snap_details["license"] license_type = "custom" if ( " AND " not in license.upper() and " WITH " not in license.upper() ): license_type = "simple" try: categories_results = store_api.get_categories() except ApiError: categories_results = [] categories = get_categories(categories_results) snap_categories = logic.replace_reserved_categories_key( snap_details["categories"] ) snap_categories = logic.filter_categories(snap_categories) context = { # read-only values from details API "snap_id": snap_details["snap_id"], "snap_name": snap_details["snap_name"], "snap_categories": snap_categories, "icon_url": icon_urls[0] if icon_urls else None, "publisher_name": snap_details["publisher"]["display-name"], "username": snap_details["publisher"]["username"], "screenshot_urls": screenshot_urls, "display_title": snap_details["title"], # values posted by user "snap_title": ( changes["title"] if "title" in changes else snap_details["title"] or "" ), "summary": ( changes["summary"] if "summary" in changes else snap_details["summary"] or "" ), "description": ( changes["description"] if "description" in changes else snap_details["description"] or "" ), "contact": ( changes["contact"] if "contact" in changes else snap_details["contact"] or "" ), "private": snap_details["private"], "website": ( changes["website"] if "website" in changes else snap_details["website"] or "" ), "public_metrics_enabled": details_metrics_enabled, "video_urls": ( [changes["video_urls"]] if "video_urls" in changes else snap_details["video_urls"] ), "public_metrics_blacklist": details_blacklist, "license": license, "license_type": license_type, "licenses": licenses, "is_on_stable": is_on_stable, "categories": categories, # errors "error_list": error_list, "field_errors": field_errors, "other_errors": other_errors, } return flask.render_template("publisher/listing.html", **context) flask.flash("Changes applied successfully.", "positive") else: flask.flash("No changes to save.", "information") return flask.redirect( flask.url_for(".get_listing_snap", snap_name=snap_name) )
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)
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]) # 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)
def get_listing_snap(snap_name): try: snap_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) details_metrics_enabled = snap_details["public_metrics_enabled"] details_blacklist = snap_details["public_metrics_blacklist"] is_on_stable = logic.is_snap_on_stable(snap_details["channel_maps_list"]) # Filter icon & screenshot urls from the media set. icon_urls = [ m["url"] for m in snap_details["media"] if m["type"] == "icon" ] screenshot_urls = [ m["url"] for m in snap_details["media"] if m["type"] == "screenshot" ] licenses = [] for license in get_licenses(): licenses.append({"key": license["licenseId"], "name": license["name"]}) license = snap_details["license"] license_type = "custom" if " AND " not in license.upper() and " WITH " not in license.upper(): license_type = "simple" referrer = None if flask.request.args.get("from"): referrer = flask.request.args.get("from") try: categories_results = store_api.get_categories() except ApiError: categories_results = [] categories = sorted( get_categories(categories_results), key=lambda category: category["slug"], ) snap_categories = logic.replace_reserved_categories_key( snap_details["categories"] ) snap_categories = logic.filter_categories(snap_categories) context = { "snap_id": snap_details["snap_id"], "snap_name": snap_details["snap_name"], "snap_title": snap_details["title"], "snap_categories": snap_categories, "summary": snap_details["summary"], "description": snap_details["description"], "icon_url": icon_urls[0] if icon_urls else None, "publisher_name": snap_details["publisher"]["display-name"], "username": snap_details["publisher"]["username"], "screenshot_urls": screenshot_urls, "contact": snap_details["contact"], "private": snap_details["private"], "website": snap_details["website"] or "", "public_metrics_enabled": details_metrics_enabled, "public_metrics_blacklist": details_blacklist, "license": license, "license_type": license_type, "licenses": licenses, "video_urls": snap_details["video_urls"], "is_on_stable": is_on_stable, "from": referrer, "categories": categories, } return flask.render_template("publisher/listing.html", **context)
def post_snap_builds(snap_name): 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) # Don't allow changes from Admins that are no contributors account_snaps = api.get_account_snaps(flask.session) if snap_name not in account_snaps: flask.flash("You do not have permissions to modify this Snap", "negative") return flask.redirect( flask.url_for(".get_snap_builds", snap_name=snap_name)) redirect_url = flask.url_for(".get_snap_builds", snap_name=snap_name) # Get built snap in launchpad with this store name github = GitHub(flask.session.get("github_auth_secret")) owner, repo = flask.request.form.get("github_repository").split("/") if not github.check_permissions_over_repo(owner, repo): flask.flash( "Your GitHub account doesn't have permissions in the repository", "negative", ) return flask.redirect(redirect_url) repo_validation = validate_repo(flask.session.get("github_auth_secret"), snap_name, owner, repo) if not repo_validation["success"]: flask.flash(repo_validation["error"]["message"], "negative") return flask.redirect(redirect_url) lp_snap = launchpad.get_snap_by_store_name(details["snap_name"]) git_url = f"https://github.com/{owner}/{repo}" if not lp_snap: lp_snap_name = md5(git_url.encode("UTF-8")).hexdigest() try: repo_exist = launchpad.get_snap(lp_snap_name) except HTTPError as e: if e.response.status_code == 404: repo_exist = False else: raise e if repo_exist: # The user registered the repo in BSI but didn't register a name # We can remove it and continue with the normal process if not repo_exist["store_name"]: # This conditional should be removed when issue 2657 is solved launchpad._request(path=repo_exist["self_link"][32:], method="DELETE") else: flask.flash( "The specified repository is being used by another snap:" f" {repo_exist['store_name']}", "negative", ) return flask.redirect(redirect_url) macaroon = api.get_package_upload_macaroon(session=flask.session, snap_name=snap_name, channels=["edge" ])["macaroon"] launchpad.create_snap(snap_name, git_url, macaroon) flask.flash("The GitHub repository was linked correctly.", "positive") # Create webhook in the repo, it should also trigger the first build github_hook_url = f"https://snapcraft.io/{snap_name}/webhook/notify" try: hook = github.get_hook_by_url(owner, repo, github_hook_url) # We create the webhook if doesn't exist already in this repo if not hook: github.create_hook(owner, repo, github_hook_url) except HTTPError: flask.flash( "The GitHub Webhook could not be created. " "Please trigger a new build manually.", "caution", ) elif lp_snap["git_repository_url"] != git_url: # In the future, create a new record, delete the old one raise AttributeError( f"Snap {snap_name} already has a build repository associated") return flask.redirect(redirect_url)
def post_listing_snap(snap_name): changes = None changed_fields = flask.request.form.get('changes') if changed_fields: changes = loads(changed_fields) if changes: snap_id = flask.request.form.get('snap_id') error_list = [] if 'images' in changes: # Add existing screenshots try: current_screenshots = api.snap_screenshots( snap_id, 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) images_json, images_files = logic.build_changed_images( changes['images'], current_screenshots, flask.request.files.get('icon'), flask.request.files.getlist('screenshots')) try: api.snap_screenshots(snap_id, flask.session, images_json, images_files) 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: error_list = error_list + api_response_error_list.errors except ApiError as api_error: return _handle_errors(api_error) body_json = logic.filter_changes_data(changes) if body_json: if 'public_metrics_blacklist' in body_json: converted_metrics = logic.convert_metrics_blacklist( body_json['public_metrics_blacklist']) body_json['public_metrics_blacklist'] = converted_metrics if 'description' in body_json: body_json['description'] = logic.remove_invalid_characters( body_json['description']) try: api.snap_metadata(snap_id, flask.session, body_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: error_list = error_list + api_response_error_list.errors except ApiError as api_error: return _handle_errors(api_error) if error_list: try: snap_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: error_list = error_list + api_response_error_list.errors except ApiError as api_error: return _handle_errors(api_error) details_metrics_enabled = snap_details['public_metrics_enabled'] details_blacklist = snap_details['public_metrics_blacklist'] field_errors, other_errors = logic.invalid_field_errors(error_list) is_on_stable = logic.is_snap_on_stable( snap_details['channel_maps_list']) # Filter icon & screenshot urls from the media set. icon_urls = [ m['url'] for m in snap_details['media'] if m['type'] == 'icon' ] screenshot_urls = [ m['url'] for m in snap_details['media'] if m['type'] == 'screenshot' ] context = { # read-only values from details API "snap_id": snap_details['snap_id'], "snap_name": snap_details['snap_name'], "license": snap_details['license'], "icon_url": icon_urls[0] if icon_urls else None, "publisher_name": snap_details['publisher']['display-name'], "screenshot_urls": screenshot_urls, "public_metrics_enabled": details_metrics_enabled, "public_metrics_blacklist": details_blacklist, "display_title": snap_details['title'], # values posted by user "snap_title": (changes['title'] if 'title' in changes else snap_details['title'] or ''), "summary": (changes['summary'] if 'summary' in changes else snap_details['summary'] or ''), "description": (changes['description'] if 'description' in changes else snap_details['description'] or ''), "contact": (changes['contact'] if 'contact' in changes else snap_details['contact'] or ''), "private": snap_details['private'], "website": (changes['website'] if 'website' in changes else snap_details['website'] or ''), "is_on_stable": is_on_stable, # errors "error_list": error_list, "field_errors": field_errors, "other_errors": other_errors } return flask.render_template('publisher/listing.html', **context) flask.flash("Changes applied successfully.", 'positive') else: flask.flash("No changes to save.", 'information') return flask.redirect( flask.url_for('.get_listing_snap', snap_name=snap_name))
def post_settings(snap_name): changes = None changed_fields = flask.request.form.get("changes") if changed_fields: changes = loads(changed_fields) if changes: snap_id = flask.request.form.get("snap_id") error_list = [] body_json = logic.filter_changes_data(changes) if body_json: try: api.snap_metadata(snap_id, flask.session, body_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: error_list = error_list + api_response_error_list.errors except ApiError as api_error: return _handle_errors(api_error) if error_list: try: snap_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: error_list = error_list + api_response_error_list.errors except ApiError as api_error: return _handle_errors(api_error) field_errors, other_errors = logic.invalid_field_errors(error_list) countries = [] for country in pycountry.countries: countries.append( {"key": country.alpha_2, "name": country.name} ) if "whitelist_country_codes" in snap_details: whitelist_country_codes = ( snap_details["whitelist_country_codes"] if len(snap_details["whitelist_country_codes"]) > 0 else [] ) else: whitelist_country_codes = [] if "blacklist_country_codes" in snap_details: blacklist_country_codes = ( snap_details["blacklist_country_codes"] if len(snap_details["blacklist_country_codes"]) > 0 else [] ) else: blacklist_country_codes = [] context = { # read-only values from details API "snap_name": snap_details["snap_name"], "snap_title": snap_details["title"], "snap_id": snap_details["snap_id"], "private": snap_details["private"], "unlisted": snap_details["unlisted"], "countries": countries, "whitelist_country_codes": whitelist_country_codes, "blacklist_country_codes": blacklist_country_codes, "price": snap_details["price"], "store": snap_details["store"], "keywords": snap_details["keywords"], "status": snap_details["status"], # errors "error_list": error_list, "field_errors": field_errors, "other_errors": other_errors, } return flask.render_template("publisher/settings.html", **context) flask.flash("Changes applied successfully.", "positive") else: flask.flash("No changes to save.", "information") return flask.redirect(flask.url_for(".get_settings", snap_name=snap_name))
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)