def update_dashboard(id_, dashboard=None, token_info=None, user=None): """Update a dashboard :param id: ID of test dashboard :type id: str :param body: Dashboard :type body: dict | bytes :rtype: Dashboard """ if not connexion.request.is_json: return "Bad request, JSON required", 400 dashboard_dict = connexion.request.get_json() if dashboard_dict.get("metadata", {}).get("project") and not project_has_user( dashboard_dict["metadata"]["project"], user ): return "Forbidden", 403 dashboard = Dashboard.query.get(id_) if not dashboard: return "Dashboard not found", 404 if project_has_user(dashboard.project, user): return "Forbidden", 403 dashboard.update(connexion.request.get_json()) session.add(dashboard) session.commit() return dashboard.to_dict()
def update_run(id_, run=None, token_info=None, user=None): """Updates a single run :param id: ID of run to update :type id: int :param body: Run :type body: dict :rtype: Run """ if not connexion.request.is_json: return "Bad request, JSON required", 400 run_dict = connexion.request.get_json() if run_dict.get("metadata", {}).get("project"): run_dict["project_id"] = get_project_id( run_dict["metadata"]["project"]) if not project_has_user(run_dict["project_id"], user): return "Forbidden", 403 run = Run.query.get(id_) if run and not project_has_user(run.project, user): return "Forbidden", 403 if not run: return "Run not found", 404 run.update(run_dict) session.add(run) session.commit() update_run_task.apply_async((id_, ), countdown=5) return run.to_dict()
def view_artifact(id_, token_info=None, user=None): """Stream an artifact directly to the client/browser :param id: ID of the artifact to download :type id: str :rtype: file """ artifact, response = _build_artifact_response(id_) if artifact.result and not project_has_user(artifact.result.project, user): return "Forbidden", 403 elif artifact.run and not project_has_user(artifact.run.project, user): return "Forbidden", 403 return response
def add_run(run=None, token_info=None, user=None): """Create a new run :param body: Run object :type body: dict | bytes :rtype: Run """ if not connexion.request.is_json: return "Bad request, JSON is required", 400 run = Run.from_dict(**connexion.request.get_json()) if run.data and not (run.data.get("project") or run.project_id): return "Bad request, project or project_id is required", 400 project = get_project(run.data["project"]) if not project_has_user(project, user): return "Forbidden", 403 run.project = project run.env = run.data.get("env") if run.data else None run.component = run.data.get("component") if run.data else None # allow start_time to be set by update_run task if no start_time present run.start_time = run.start_time if run.start_time else datetime.utcnow() # if not present, created is the time at which the run is added to the DB run.created = run.created if run.created else datetime.utcnow() session.add(run) session.commit() update_run_task.apply_async((run.id, ), countdown=5) return run.to_dict(), 201
def update_widget_config(id_, token_info=None, user=None): """Updates a single widget config :param id: ID of widget to update :type id: int :param body: Result :type body: dict :rtype: Result """ if not connexion.request.is_json: return "Bad request, JSON required", 400 data = connexion.request.get_json() if data.get("widget") and data["widget"] not in WIDGET_TYPES.keys(): return "Bad request, widget type does not exist", 400 # Look up the project id if data.get("project"): project = get_project(data.pop("project")) if not project_has_user(project, user): return "Forbidden", 403 data["project_id"] = project.id widget_config = WidgetConfig.query.get(id_) # add default weight of 10 if not widget_config.weight: widget_config.weight = 10 # default to make views navigable if data.get("navigable") and isinstance(data["navigable"], str): data["navigable"] = data["navigable"][0] in ALLOWED_TRUE_BOOLEANS if data.get("type") and data["type"] == "view" and data.get("navigable") is None: data["navigable"] = True widget_config.update(data) session.add(widget_config) session.commit() return widget_config.to_dict()
def add_widget_config(widget_config=None, token_info=None, user=None): """Create a new widget config :param widget_config: The widget_config to save :type widget_config: dict | bytes :rtype: WidgetConfig """ if not connexion.request.is_json: return "Bad request, JSON required", 400 data = connexion.request.json if data["widget"] not in WIDGET_TYPES.keys(): return "Bad request, widget type does not exist", 400 # add default weight of 10 if not data.get("weight"): data["weight"] = 10 # Look up the project id if data.get("project"): project = get_project(data.pop("project")) if not project_has_user(project, user): return "Forbidden", 403 data["project_id"] = project.id # default to make views navigable if data.get("navigable") and isinstance(data["navigable"], str): data["navigable"] = data["navigable"][0] in ALLOWED_TRUE_BOOLEANS if data.get("type") == "view" and data.get("navigable") is None: data["navigable"] = True widget_config = WidgetConfig.from_dict(**data) session.add(widget_config) session.commit() return widget_config.to_dict(), 201
def add_import( import_file: Optional[FileStorage] = None, project: Optional[str] = None, metadata: Optional[str] = None, source: Optional[str] = None, token_info: Optional[str] = None, user: Optional[str] = None, ): """Imports a JUnit XML file and creates a test run and results from it. :param import_file: file to upload :type import_file: werkzeug.datastructures.FileStorage :param project: the project to add this test run to :type project: str :param metadata: extra metadata to add to the run and the results, in a JSON string :type metadata: str :param source: the source of the test run :type source: str :rtype: Import """ if "importFile" in connexion.request.files: import_file = connexion.request.files["importFile"] if not import_file: return "Bad request, no file uploaded", 400 data = {} if connexion.request.form.get("project"): project = connexion.request.form["project"] if project: project = get_project(project) if not project_has_user(project, user): return "Forbidden", 403 data["project_id"] = project.id if connexion.request.form.get("metadata"): metadata = json.loads(connexion.request.form.get("metadata")) data["metadata"] = metadata if connexion.request.form.get("source"): data["source"] = connexion.request.form["source"] new_import = Import.from_dict( **{ "status": "pending", "filename": import_file.filename, "format": "", "data": data }) session.add(new_import) session.commit() new_file = ImportFile(import_id=new_import.id, content=import_file.read()) session.add(new_file) session.commit() if import_file.filename.endswith(".xml"): run_junit_import.delay(new_import.to_dict()) elif import_file.filename.endswith(".tar.gz"): run_archive_import.delay(new_import.to_dict()) else: return "Unsupported Media Type", 415 return new_import.to_dict(), 202
def download_artifact(id_, token_info=None, user=None): """Download an artifact :param id: ID of artifact to download :type id: str :rtype: file """ artifact, response = _build_artifact_response(id_) if not project_has_user(artifact.result.project, user): return "Forbidden", 403 response.headers["Content-Disposition"] = "attachment; filename={}".format(artifact.filename) return response
def get_run(id_, token_info=None, user=None): """Get a run :param id: The ID of the run :type id: str :rtype: Run """ run = Run.query.get(id_) if not run: return "Run not found", 404 if not project_has_user(run.project, user): return "Forbidden", 403 return run.to_dict()
def get_artifact(id_, token_info=None, user=None): """Return a single artifact :param id: ID of the artifact :type id: str :rtype: Artifact """ artifact = Artifact.query.get(id_) if not artifact: return "Not Found", 404 if not project_has_user(artifact.result.project, user): return "Forbidden", 403 return artifact.to_dict()
def get_dashboard(id_, token_info=None, user=None): """Get a single dashboard by ID :param id: ID of test dashboard :type id: str :rtype: Dashboard """ dashboard = Dashboard.query.get(id_) if not dashboard: return "Dashboard not found", 404 if dashboard and dashboard.project and not project_has_user(dashboard.project, user): return "Forbidden", 403 return dashboard.to_dict()
def get_import(id_, token_info=None, user=None): """Get a run :param id: The ID of the run :type id: str :rtype: Run """ import_ = Import.query.get(id_) if import_ and import_.data.get("project_id"): project = get_project(import_.data["project_id"]) if project and not project_has_user(project, user): return "Forbidden", 403 if not import_: return "Not Found", 404 return import_.to_dict()
def delete_artifact(id_, token_info=None, user=None): """Deletes an artifact :param id: ID of the artifact to delete :type id: str :rtype: tuple """ artifact = Artifact.query.get(id_) if not artifact: return "Not Found", 404 if not project_has_user(artifact.result.project, user): return "Forbidden", 403 session.delete(artifact) session.commit() return "OK", 200
def add_dashboard(dashboard=None, token_info=None, user=None): """Create a dashboard :param body: Dashboard :type body: dict | bytes :rtype: Dashboard """ if not connexion.request.is_json: return "Bad request, JSON required", 400 dashboard = Dashboard.from_dict(**connexion.request.get_json()) if dashboard.project_id and not project_has_user(dashboard.project_id, user): return "Forbidden", 403 session.add(dashboard) session.commit() return dashboard.to_dict(), 201
def bulk_update(filter_=None, page_size=1, token_info=None, user=None): """Updates multiple runs with common metadata Note: can only be used to update metadata on runs, limited to 25 runs :param filter_: A list of filters to apply :param page_size: Limit the number of runs updated, defaults to 1 :rtype: List[Run] """ if not connexion.request.is_json: return "Bad request, JSON required", 400 run_dict = connexion.request.get_json() if not run_dict.get("metadata"): return "Bad request, can only update metadata", 401 # ensure only metadata is updated run_dict = {"metadata": run_dict.pop("metadata")} if page_size > 25: return "Bad request, cannot update more than 25 runs at a time", 405 if run_dict.get("metadata", {}).get("project"): project = get_project(run_dict["metadata"]["project"]) if not project_has_user(project, user): return "Forbidden", 403 run_dict["project_id"] = project.id runs = get_run_list(filter_=filter_, page_size=page_size, estimate=True).get("runs") if not runs: return f"No runs found with {filter_}", 404 model_runs = [] for run_json in runs: run = Run.query.get(run_json.get("id")) # update the json dict of the run with the new metadata merge_dicts(run_dict, run_json) run.update(run_json) session.add(run) model_runs.append(run) session.commit() return [run.to_dict() for run in model_runs]
def get_dashboard_list( filter_=None, project_id=None, page=1, page_size=25, token_info=None, user=None ): """Get a list of dashboards :param project_id: Filter dashboards by project ID :type project_id: str :param user_id: Filter dashboards by user ID :type user_id: str :param limit: Limit the dashboards :type limit: int :param offset: Offset the dashboards :type offset: int :rtype: DashboardList """ query = Dashboard.query project = None if "project_id" in connexion.request.args: project = Project.query.get(connexion.request.args["project_id"]) if project: if not project_has_user(project, user): return "Forbidden", 403 query = query.filter(Dashboard.project_id == project_id) if filter_: for filter_string in filter_: filter_clause = convert_filter(filter_string, Dashboard) if filter_clause is not None: query = query.filter(filter_clause) query = query.order_by(Dashboard.title.asc()) offset = (page * page_size) - page_size total_items = query.count() total_pages = (total_items // page_size) + (1 if total_items % page_size > 0 else 0) dashboards = query.offset(offset).limit(page_size).all() return { "dashboards": [dashboard.to_dict() for dashboard in dashboards], "pagination": { "page": page, "pageSize": page_size, "totalItems": total_items, "totalPages": total_pages, }, }
def get_project(id_, token_info=None, user=None): """Get a single project by ID :param id: ID of test project :type id: str :rtype: Project """ if not is_uuid(id_): id_ = convert_objectid_to_uuid(id_) project = Project.query.filter(Project.name == id_).first() if not project: project = Project.query.get(id_) if project and not project_has_user(project, user): return "Unauthorized", 401 if not project: return "Project not found", 404 return project.to_dict()
def delete_dashboard(id_, token_info=None, user=None): """Deletes a dashboard :param id: ID of the dashboard to delete :type id: str :rtype: tuple """ dashboard = Dashboard.query.get(id_) if not dashboard: return "Not Found", 404 if not project_has_user(dashboard.project, user): return "Forbidden", 403 widget_configs = WidgetConfig.query.filter(WidgetConfig.dashboard_id == dashboard.id).all() for widget_config in widget_configs: session.delete(widget_config) session.delete(dashboard) session.commit() return "OK", 200
def upload_artifact(body, token_info=None, user=None): """Uploads a artifact artifact :param result_id: ID of result to attach artifact to :type result_id: str :param run_id: ID of run to attach artifact to :type run_id: str :param filename: filename for storage :type filename: string :param file: file to upload :type file: werkzeug.datastructures.FileStorage :param additional_metadata: Additional data to pass to server :type additional_metadata: object :rtype: tuple """ result_id = body.get("result_id") or body.get("resultId") run_id = body.get("run_id") or body.get("runId") result = Result.query.get(result_id) if result and not project_has_user(result.project, user): return "Forbidden", 403 filename = body.get("filename") additional_metadata = body.get("additional_metadata", {}) file_ = connexion.request.files["file"] content_type = magic.from_buffer(file_.read()) data = { "contentType": content_type, "resultId": result_id, "runId": run_id, "filename": filename, } if additional_metadata: if isinstance(additional_metadata, str): try: additional_metadata = json.loads(additional_metadata) except (ValueError, TypeError): return "Bad request, additionalMetadata is not valid JSON", 400 if not isinstance(additional_metadata, dict): return "Bad request, additionalMetadata is not a JSON object", 400 data["additionalMetadata"] = additional_metadata # Reset the file pointer file_.seek(0) if data.get("runId"): artifact = Artifact( filename=filename, run_id=data["runId"], content=file_.read(), upload_date=datetime.utcnow(), data=additional_metadata, ) else: artifact = Artifact( filename=filename, result_id=data["resultId"], content=file_.read(), upload_date=datetime.utcnow(), data=additional_metadata, ) session.add(artifact) session.commit() return artifact.to_dict(), 201