#4710 API: add new endpoint to edit issue title and content
Merged 4 years ago by pingou. Opened 4 years ago by jlanda.
jlanda/pagure rfe-edit-issue  into  master

file modified
+2 -1
@@ -66,7 +66,7 @@ 

      ENOCODE = "Variable message describing the issue"

      ENOPROJECT = "Project not found"

      ENOPROJECTS = "No projects found"

-     ETRACKERDISABLED = "Issue tracker disabled for this project"

+     ETRACKERDISABLED = "Issue tracker disabled"

      EDBERROR = (

          "An error occurred at the database level and prevent the "

          + "action from reaching completion"
@@ -518,6 +518,7 @@ 

      issues = []

      if pagure_config.get("ENABLE_TICKETS", True):

          issues.append(load_doc(issue.api_new_issue))

+         issues.append(load_doc(issue.api_issue_update))

          issues.append(load_doc(issue.api_view_issues))

          issues.append(load_doc(issue.api_view_issue))

          issues.append(load_doc(issue.api_view_issue_comment))

file modified
+116
@@ -579,6 +579,122 @@ 

      return jsonout

  

  

+ @API.route("/<repo>/issue/<issueid>", methods=["POST"])

+ @API.route("/<namespace>/<repo>/issue/<issueid>", methods=["POST"])

+ @API.route("/fork/<username>/<repo>/issue/<issueid>", methods=["POST"])

+ @API.route(

+     "/fork/<username>/<namespace>/<repo>/issue/<issueid>", methods=["POST"]

+ )

+ @api_login_required(acls=["issue_update"])

+ @api_method

+ def api_issue_update(repo, issueid, username=None, namespace=None):

+     """

+     Update issue information

+     ------------------------

+     Update the title and issue content of an existing issue.

+ 

+     ::

+ 

+         POST /api/0/<repo>/issue/<issue_id>

+         POST /api/0/<namespace>/<repo>/issue/<issue_id>

+ 

+     ::

+ 

+         POST /api/0/fork/<username>/<repo>/issue/<issue_id>

+         POST /api/0/fork/<username>/<namespace>/<repo>/issue/<issue_id>

+ 

+     Input

+     ^^^^^

+ 

+     +-------------------+--------+-------------+---------------------------+

+     | Key               | Type   | Optionality | Description               |

+     +===================+========+=============+===========================+

+     | ``title``         | string | Mandatory   | The title of the issue    |

+     +-------------------+--------+-------------+---------------------------+

+     | ``issue_content`` | string | Mandatory   | | The description of the  |

+     |                   |        |             |   issue                   |

+     +-------------------+--------+-------------+---------------------------+

+ 

+     Sample response

+     ^^^^^^^^^^^^^^^

+ 

+     ::

+ 

+         {

+           "issue": {

+             "assignee": null,

+             "blocks": [],

+             "close_status": null,

+             "closed_at": null,

+             "closed_by": null,

+             "comments": [],

+             "content": "This issue needs attention",

+             "custom_fields": [],

+             "date_created": "1479458613",

+             "depends": [],

+             "id": 1,

+             "milestone": null,

+             "priority": null,

+             "private": false,

+             "status": "Open",

+             "tags": [],

+             "title": "test issue",

+             "user": {

+               "fullname": "PY C",

+               "name": "pingou"

+             }

+           },

+           "message": "Issue edited"

+         }

+ 

+     """

+ 

+     output = {}

+     repo = _get_repo(repo, username, namespace)

+     _check_issue_tracker(repo)

+     _check_token(repo)

+ 

+     issue_id = issue_uid = None

+     try:

+         issue_id = int(issueid)

+     except (ValueError, TypeError):

+         issue_uid = issueid

+ 

+     issue = _get_issue(repo, issue_id, issueuid=issue_uid)

+     _check_private_issue_access(issue)

+ 

+     form = pagure.forms.IssueFormSimplied(csrf_enabled=False)

+ 

+     if form.validate_on_submit():

+         title = form.title.data.strip()

+         content = form.issue_content.data

+ 

+         try:

+             pagure.lib.query.edit_issue(

+                 session=flask.g.session,

+                 issue=issue,

+                 user=flask.g.fas_user.username,

+                 title=title,

+                 content=content,

+             )

+             flask.g.session.commit()

+ 

+             output["message"] = "Issue edited"

+             output["issue"] = issue.to_json(public=True)

+         except SQLAlchemyError as err:

+             flask.g.session.rollback()

+             _log.exception(err)

+             raise pagure.exceptions.APIError(400, error_code=APIERROR.EDBERROR)

+ 

+     else:

+         raise pagure.exceptions.APIError(

+             400, error_code=APIERROR.EINVALIDREQ, errors=form.errors

+         )

+ 

+     jsonout = flask.jsonify(output)

+     return jsonout

+ 

+ 

  @API.route("/<repo>/issue/<issueid>/comment/<int:commentid>")

  @API.route("/<namespace>/<repo>/issue/<issueid>/comment/<int:commentid>")

  @API.route("/fork/<username>/<repo>/issue/<issueid>/comment/<int:commentid>")

file modified
+9 -4
@@ -111,12 +111,17 @@ 

      :param repo: repository

      :raises pagure.exceptions.APIError: when issue tracker is disabled

      """

+     enable_tickets = pagure_config.get("ENABLE_TICKETS")

      ticket_namespaces = pagure_config.get("ENABLE_TICKETS_NAMESPACE")

      if (

-         ticket_namespaces

-         and repo.namespace

-         and repo.namespace not in ticket_namespaces

-     ) or not repo.settings.get("issue_tracker", True):

+         (

+             ticket_namespaces

+             and repo.namespace

+             and repo.namespace not in ticket_namespaces

+         )

+         or not enable_tickets

+         or not repo.settings.get("issue_tracker", True)

+     ):

          raise pagure.exceptions.APIError(

              404, error_code=APIERROR.ETRACKERDISABLED

          )

@@ -3954,7 +3954,7 @@ 

          self.assertDictEqual(

              data,

              {

-                 "error": "Issue tracker disabled for this project",

+                 "error": "Issue tracker disabled",

                  "error_code": "ETRACKERDISABLED",

              },

          )

@@ -0,0 +1,257 @@ 

+ # -*- coding: utf-8 -*-

+ 

+ """

+  Authors:

+    Julen Landa Alustiza <jlanda@fedoraproject.org>

+ """

+ 

+ from __future__ import unicode_literals, absolute_import

+ 

+ import datetime

+ import unittest

+ import sys

+ import os

+ import json

+ 

+ from mock import patch, MagicMock

+ 

+ sys.path.insert(

+     0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")

+ )

+ 

+ import pagure.lib.query  # noqa: E402

+ import tests  # noqa: E402

+ 

+ 

+ class PagureFlaskApiIssueUpdatetests(tests.Modeltests):

+     """ Tests for the flask API of pagure for updating an issue """

+ 

+     def setUp(self):

+         """ Set up the environnment, ran before every tests. """

+         super(PagureFlaskApiIssueUpdatetests, self).setUp()

+ 

+         pagure.config.config["TICKETS_FOLDER"] = None

+         tests.create_projects(self.session)

+         tests.create_tokens(self.session)

+ 

+     def test_api_issue_update_wrong_token(self):

+         """ Test the api_issue_update method of flask API """

+         tests.create_tokens_acl(self.session)

+         headers = {"Authorization": "token aaa"}

+         output = self.app.post("/api/0/foo/issue/1", headers=headers)

+         self.assertEqual(output.status_code, 401)

+         expected_rv = {

+             "error": "Invalid or expired token. Please visit "

+             "http://localhost.localdomain/settings#nav-api-tab to get or renew "

+             "your API token.",

+             "error_code": "EINVALIDTOK",

+             "errors": "Invalid token",

+         }

+         data = json.loads(output.get_data(as_text=True))

+         self.assertDictEqual(data, expected_rv)

+ 

+     def test_api_issue_update_wrong_project(self):

+         """ Test the api_issue_update method of flask API """

+         tests.create_tokens_acl(self.session)

+         headers = {"Authorization": "token aaabbbcccddd"}

+         output = self.app.post("/api/0/foo/issue/1", headers=headers)

+         self.assertEqual(output.status_code, 404)

+         expected_rv = {

+             "error": "Project not found",

+             "error_code": "ENOPROJECT",

+         }

+         data = json.loads(output.get_data(as_text=True))

+         self.assertDictEqual(data, expected_rv)

+ 

+     def test_api_issue_update_wrong_acls(self):

+         """ Test the api_issue_update method of flask API """

+         tests.create_tokens_acl(self.session, acl_name="issue_create")

+         headers = {"Authorization": "token aaabbbcccddd"}

+         output = self.app.post("/api/0/test/issue/1", headers=headers)

+         self.assertEqual(output.status_code, 401)

+         expected_rv = {

+             "error": "Invalid or expired token. Please visit "

+             "http://localhost.localdomain/settings#nav-api-tab to get or renew "

+             "your API token.",

+             "error_code": "EINVALIDTOK",

+             "errors": "Missing ACLs: issue_update",

+         }

+         data = json.loads(output.get_data(as_text=True))

+         self.assertDictEqual(data, expected_rv)

+ 

+     @patch.dict("pagure.config.config", {"ENABLE_TICKETS": False})

+     def test_api_issue_update_instance_tickets_disabled(self):

+         """ Test the api_issue_update method of flask API """

+         tests.create_tokens_acl(self.session)

+         headers = {"Authorization": "token aaabbbcccddd"}

+         output = self.app.post("/api/0/test/issue/1", headers=headers)

+         self.assertEqual(output.status_code, 404)

+         expected_rv = {

+             "error": "Issue tracker disabled",

+             "error_code": "ETRACKERDISABLED",

+         }

+         data = json.loads(output.get_data(as_text=True))

+         self.assertDictEqual(data, expected_rv)

+ 

+     def test_api_issue_update_project_tickets_disabled(self):

+         """ Test the api_issue_update method of flask API """

+         tests.create_tokens_acl(self.session)

+         # disable tickets on this repo

+         repo = pagure.lib.query.get_authorized_project(self.session, "test")

+         settings = repo.settings

+         settings["issue_tracker"] = False

+         repo.settings = settings

+         self.session.add(repo)

+         self.session.commit()

+         headers = {"Authorization": "token aaabbbcccddd"}

+         output = self.app.post("/api/0/test/issue/1", headers=headers)

+         self.assertEqual(output.status_code, 404)

+         expected_rv = {

+             "error": "Issue tracker disabled",

+             "error_code": "ETRACKERDISABLED",

+         }

+         data = json.loads(output.get_data(as_text=True))

+         self.assertDictEqual(data, expected_rv)

+ 

+     def test_api_issue_update_project_read_only_issue_tracker(self):

+         """ Test the api_issue_update method of flask API """

+         tests.create_tokens_acl(self.session)

+         # set read only issue tracke on this repo

+         repo = pagure.lib.query.get_authorized_project(self.session, "test")

+         settings = repo.settings

+         settings["issue_tracker_read_only"] = True

+         repo.settings = settings

+         self.session.add(repo)

+         self.session.commit()

+         headers = {"Authorization": "token aaabbbcccddd"}

+         output = self.app.post("/api/0/test/issue/1", headers=headers)

+         self.assertEqual(output.status_code, 401)

+         expected_rv = {

+             "error": "The issue tracker of this project is read-only",

+             "error_code": "ETRACKERREADONLY",

+         }

+         data = json.loads(output.get_data(as_text=True))

+         self.assertDictEqual(data, expected_rv)

+ 

+     def test_api_issue_update_wrong_issue(self):

+         """ Test the api_issue_update method of flask API """

+         tests.create_tokens_acl(self.session)

+         tests.create_projects_git(os.path.join(self.path, "tickets"))

+         headers = {"Authorization": "token aaabbbcccddd"}

+         output = self.app.post("/api/0/test/issue/1", headers=headers)

+         self.assertEqual(output.status_code, 404)

+         expected_rv = {"error": "Issue not found", "error_code": "ENOISSUE"}

+         data = json.loads(output.get_data(as_text=True))

+         self.assertDictEqual(data, expected_rv)

+ 

+     def test_api_issue_update_no_input(self):

+         """ Test the api_issue_update method of flask API """

+         tests.create_tokens_acl(self.session)

+         tests.create_projects_git(os.path.join(self.path, "tickets"))

+         headers = {"Authorization": "token aaabbbcccddd"}

+ 

+         # Create an issue

+         repo = pagure.lib.query.get_authorized_project(self.session, "test")

+         msg = pagure.lib.query.new_issue(

+             session=self.session,

+             repo=repo,

+             title="Test issue #1",

+             content="We should work on this",

+             user="pingou",

+             private=False,

+         )

+         self.session.commit()

+         self.assertEqual(msg.title, "Test issue #1")

+         headers = {"Authorization": "token aaabbbcccddd"}

+         output = self.app.post("/api/0/test/issue/1", headers=headers)

+         self.assertEqual(output.status_code, 400)

+         expected_rv = {

+             "error": "Invalid or incomplete input submitted",

+             "error_code": "EINVALIDREQ",

+             "errors": {

+                 "issue_content": ["This field is required."],

+                 "title": ["This field is required."],

+             },

+         }

+         data = json.loads(output.get_data(as_text=True))

+         self.assertDictEqual(data, expected_rv)

+ 

+     def test_api_issue_update_partial_input(self):

+         """ Test the api_issue_update method of flask API """

+         tests.create_tokens_acl(self.session)

+         tests.create_projects_git(os.path.join(self.path, "tickets"))

+         headers = {"Authorization": "token aaabbbcccddd"}

+ 

+         # Create an issue

+         repo = pagure.lib.query.get_authorized_project(self.session, "test")

+         msg = pagure.lib.query.new_issue(

+             session=self.session,

+             repo=repo,

+             title="Test issue #1",

+             content="We should work on this",

+             user="pingou",

+             private=False,

+         )

+         self.session.commit()

+         self.assertEqual(msg.title, "Test issue #1")

+         headers = {"Authorization": "token aaabbbcccddd"}

+         # missing issue_content

+         data = {"title": "New title"}

+         output = self.app.post(

+             "/api/0/test/issue/1", data=data, headers=headers

+         )

+         self.assertEqual(output.status_code, 400)

+         expected_rv = {

+             "error": "Invalid or incomplete input submitted",

+             "error_code": "EINVALIDREQ",

+             "errors": {"issue_content": ["This field is required."]},

+         }

+         data = json.loads(output.get_data(as_text=True))

+         self.assertDictEqual(data, expected_rv)

+         # missing title

+         data = {"issue_content": "New content"}

+         output = self.app.post(

+             "/api/0/test/issue/1", data=data, headers=headers

+         )

+         self.assertEqual(output.status_code, 400)

+         expected_rv = {

+             "error": "Invalid or incomplete input submitted",

+             "error_code": "EINVALIDREQ",

+             "errors": {"title": ["This field is required."]},

+         }

+         data = json.loads(output.get_data(as_text=True))

+         self.assertDictEqual(data, expected_rv)

+ 

+     def test_api_issue_update(self):

+         """ Test the api_issue_update method of flask API """

+         tests.create_tokens_acl(self.session)

+         tests.create_projects_git(os.path.join(self.path, "tickets"))

+         headers = {"Authorization": "token aaabbbcccddd"}

+ 

+         # Create an issue

+         repo = pagure.lib.query.get_authorized_project(self.session, "test")

+         msg = pagure.lib.query.new_issue(

+             session=self.session,

+             repo=repo,

+             title="Test issue #1",

+             content="We should work on this",

+             user="pingou",

+             private=False,

+         )

+         self.session.commit()

+         self.assertEqual(msg.title, "Test issue #1")

+         headers = {"Authorization": "token aaabbbcccddd"}

+         data = {"title": "New title", "issue_content": "New content"}

+         output = self.app.post(

+             "/api/0/test/issue/1", data=data, headers=headers

+         )

+         self.assertEqual(output.status_code, 200)

+         data = json.loads(output.get_data(as_text=True))

+         self.assertEqual(data["message"], "Issue edited")

+         self.assertEqual(data["issue"]["title"], "New title")

+         self.assertEqual(data["issue"]["content"], "New content")

+         output = self.app.get("/api/0/test/issue/1", headers=headers)

+         self.assertEqual(output.status_code, 200)

+         data = json.loads(output.get_data(as_text=True))

+         self.assertEqual(data["title"], "New title")

+         self.assertEqual(data["content"], "New content")