#4664 api modifications for project tags
Merged a year ago by pingou. Opened a year ago by jlanda.
jlanda/pagure api-tags-namespaces  into  master

file modified
+7 -65
@@ -127,6 +127,7 @@ 

      EPLUGINDISABLED = "Plugin disabled"

      EPLUGINCHANGENOTALLOWED = "This plugin cannot be changed"

      EPLUGINNOTINSTALLED = "Project doesn't have this plugin installed"

+     ENOTAG = "Tag not found"

  

  

  def get_authorized_api_project(session, repo, user=None, namespace=None):
@@ -454,66 +455,6 @@ 

      return flask.jsonify(output)

  

  

- @API.route("/<repo>/tags")

- @API.route("/<repo>/tags/")

- @API.route("/fork/<username>/<repo>/tags")

- @API.route("/fork/<username>/<repo>/tags/")

- def api_project_tags(repo, username=None):

-     """

-     List all the tags of a project

-     ------------------------------

-     List the tags made on the project's issues.

- 

-     ::

- 

-         GET /api/0/<repo>/tags

- 

-     ::

- 

-         GET /api/0/fork/<username>/<repo>/tags

- 

-     Parameters

-     ^^^^^^^^^^

- 

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

-     | Key           | Type     | Optionality   | Description              |

-     +===============+==========+===============+==========================+

-     | ``pattern``   | string   | Optional      | | Filters the starting   |

-     |               |          |               |   letters of the tags    |

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

- 

-     Sample response

-     ^^^^^^^^^^^^^^^

- 

-     ::

- 

-         {

-           "total_tags": 2,

-           "tags": ["tag1", "tag2"]

-         }

- 

-     """

- 

-     pattern = flask.request.args.get("pattern", None)

-     if pattern is not None and not pattern.endswith("*"):

-         pattern += "*"

- 

-     project_obj = get_authorized_api_project(flask.g.session, repo, username)

-     if not project_obj:

-         output = {"output": "notok", "error": "Project not found"}

-         jsonout = flask.jsonify(output)

-         jsonout.status_code = 404

-         return jsonout

- 

-     tags = pagure.lib.query.get_tags_of_project(

-         flask.g.session, project_obj, pattern=pattern

-     )

- 

-     return flask.jsonify(

-         {"total_tags": len(tags), "tags": [tag.tag for tag in tags]}

-     )

- 

- 

  @API.route("/error_codes/")

  @API.route("/error_codes")

  @API.route("/-/error_codes")
@@ -551,6 +492,9 @@ 

      api_project_doc = load_doc(project.api_project)

      api_projects_doc = load_doc(project.api_projects)

      api_project_watchers_doc = load_doc(project.api_project_watchers)

+     api_project_tags_doc = load_doc(project.api_project_tags)

+     api_project_tags_new_doc = load_doc(project.api_project_tags_new)

+     api_project_tag_delete_doc = load_doc(project.api_project_tag_delete)

      api_git_tags_doc = load_doc(project.api_git_tags)

      api_project_git_urls_doc = load_doc(project.api_project_git_urls)

      api_git_branches_doc = load_doc(project.api_git_branches)
@@ -633,15 +577,10 @@ 

      api_view_plugins_project_doc = load_doc(plugins.api_view_plugins_project)

      api_view_plugins_doc = load_doc(plugins.api_view_plugins)

  

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

-         api_project_tags_doc = load_doc(api_project_tags)

      api_error_codes_doc = load_doc(api_error_codes)

  

      extras = [api_whoami_doc, api_version_doc, api_error_codes_doc]

  

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

-         extras.append(api_project_tags_doc)

- 

      return flask.render_template(

          "api.html",

          version=pagure.__api_version__,
@@ -651,6 +590,9 @@ 

              api_modify_project_doc,

              api_project_doc,

              api_projects_doc,

+             api_project_tags_doc,

+             api_project_tags_new_doc,

+             api_project_tag_delete_doc,

              api_git_tags_doc,

              api_project_git_urls_doc,

              api_project_watchers_doc,

file modified
+240 -1
@@ -34,13 +34,252 @@ 

      get_page,

      get_per_page,

  )

- from pagure.api.utils import _get_repo, _check_token

+ from pagure.api.utils import _get_repo, _check_token, _get_project_tag

  from pagure.config import config as pagure_config

  

  

  _log = logging.getLogger(__name__)

  

  

+ @API.route("/<repo>/tags")

+ @API.route("/<repo>/tags/")

+ @API.route("/<namespace>/<repo>/tags")

+ @API.route("/<namespace>/<repo>/tags/")

+ @API.route("/fork/<username>/<repo>/tags")

+ @API.route("/fork/<username>/<repo>/tags/")

+ @API.route("/fork/<username>/<namespace>/<repo>/tags")

+ @API.route("/fork/<username>/<namespace>/<repo>/tags/")

+ @api_method

+ def api_project_tags(repo, username=None, namespace=None):

+     """

+     List all the tags of a project

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

+     List the tags made on the project's issues.

+ 

+     ::

+ 

+         GET /api/0/<repo>/tags

+         GET /api/0/<namespace>/<repo>/git/tags

+ 

+     ::

+ 

+         GET /api/0/fork/<username>/<repo>/tags

+         GET /api/0/fork/<username>/<namespace>/<repo>/tags

+ 

+     Parameters

+     ^^^^^^^^^^

+ 

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

+     | Key           | Type     | Optionality   | Description              |

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

+     | ``pattern``   | string   | Optional      | | Filters the starting   |

+     |               |          |               |   letters of the tags    |

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

+ 

+     Sample response

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

+ 

+     ::

+ 

+         {

+           "total_tags": 2,

+           "tags": ["tag1", "tag2"]

+         }

+ 

+     """

+ 

+     pattern = flask.request.args.get("pattern", None)

+     if pattern is not None and not pattern.endswith("*"):

+         pattern += "*"

+ 

+     project_obj = _get_repo(repo, username, namespace)

+ 

+     tags = pagure.lib.query.get_tags_of_project(

+         flask.g.session, project_obj, pattern=pattern

+     )

+ 

+     return flask.jsonify(

+         {"total_tags": len(tags), "tags": [tag.tag for tag in tags]}

+     )

+ 

+ 

+ @API.route("/<repo>/tag/<tag>", methods=["GET"])

+ @API.route("/<namespace>/<repo>/tag/<tag>", methods=["GET"])

+ @API.route("/fork/<username>/<repo>/tag/<tag>", methods=["GET"])

+ @API.route("/fork/<username>/<namespace>/<repo>/tag/<tag>", methods=["GET"])

+ @api_method

+ def api_project_tag_view(repo, tag, username=None, namespace=None):

+     """

+     View a tag of a project

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

+     View a tag on project's issues or pull requests.

+ 

+     ::

+ 

+         GET /api/0/<repo>/tag/<tag>

+         GET /api/0/<repo>/tag/<tag>

+ 

+     ::

+         GET /api/0/fork/<username>/<repo>/tag/<tag>

+         GET /api/0/fork/<username>/<namespace>/tag/<tag>

+ 

+         Sample response

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

+ 

+     ::

+ 

+         {

+             "tag": "tag1",

+             "tag_color": "DeepBlueSky"

+             "tag_description": "Our blue tag"

+         }

+ 

+     """

+     repo = _get_repo(repo, username, namespace)

+     _check_token(repo, project_token=False)

+     tag = _get_project_tag(repo.id, tag)

+     output = tag.to_json()

+ 

+     jsonout = flask.jsonify(output)

+     return jsonout

+ 

+ 

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

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

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

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

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

+ @api_method

+ def api_project_tags_new(repo, username=None, namespace=None):

+     """

+     Create a new tag on a project

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

+ 

+     Create a new tag on the project's issues and pull requests.

+ 

+     ::

+ 

+         POST /api/0/<repo>/tags/new

+         POST /api/0/<namespace>/<repo>/tags/new

+ 

+     ::

+ 

+         POST /api/0/fork/<username>/<repo>/tags/new

+         POST /api/0/fork/<username>/<namespace>/<repo>/tags/new

+ 

+     Input

+     ^^^^^

+ 

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

+     | Key               | Type   | Optionality | Description               |

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

+     | 'tag'             | string | Mandatory   | The name of the tag       |

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

+     | 'tag_color'       | string | Mandatory   | The color of the tag      |

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

+     | 'tag_description' | string | Optional    | | The description of the  |

+     |                   |        |             |   tag                     |

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

+ 

+     Sample response

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

+ 

+     ::

+ 

+         {

+             "tag": {

+                 "tag": "tag1",

+                 "tag_color": "DeepBlueSky",

+                 "tag_description": "Our blue tag"

+             },

+             "message": "Tag created"

+         }

+ 

+     """

+     output = {}

+     repo = _get_repo(repo, username, namespace)

+     _check_token(repo, project_token=False)

+ 

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

+     if form.validate_on_submit():

+         tag_name = form.tag.data

+         tag_description = form.tag_description.data

+         tag_color = form.tag_color.data

+         try:

+             tag = pagure.lib.query.new_tag(

+                 flask.g.session, tag_name, tag_description, tag_color, repo.id

+             )

+             flask.g.session.commit()

+             output["message"] = "Tag created"

+             output["tag"] = tag.to_json()

+ 

+         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>/tag/<tag>", methods=["DELETE"])

+ @API.route("/<namespace>/<repo>/tag/<tag>", methods=["DELETE"])

+ @API.route("/fork/<username>/<repo>/tag/<tag>", methods=["DELETE"])

+ @API.route("/fork/<username>/<namespace>/<repo>/tag/<tag>", methods=["DELETE"])

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

+ @api_method

+ def api_project_tag_delete(repo, tag, username=None, namespace=None):

+     """

+     Delete a tag on a project

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

+ 

+     Delete a tag on project's issues and pull requests.

+ 

+     ::

+ 

+         DELETE /api/0/<repo>/tag/<tag>

+         DELETE /api/0/<namespace>/<repo>/tag/<tag>

+ 

+     ::

+ 

+         DELETE /api/0/fork/<username>/<repo>/tag/<tag>

+         DELETE /api/0/fork/<username>/<namespace>/<repo>/tag/<tag>

+ 

+     Sample response

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

+ 

+     ::

+         {

+             "message": "Tag blue has been deleted"

+         }

+ 

+     """

+     output = {}

+     repo = _get_repo(repo, username, namespace)

+     _check_token(repo)

+     tag = _get_project_tag(repo.id, tag)

+     tags = tag.tag

+ 

+     try:

+         msgs = pagure.lib.query.remove_tags(

+             flask.g.session, repo, tags, user=flask.g.fas_user.username

+         )

+         flask.g.session.commit()

+         output["message"] = msgs[0]

+     except SQLAlchemyError as err:  # pragma: no cover

+         flask.g.session.rollback()

+         _log.exception(err)

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

+ 

+     jsonout = flask.jsonify(output)

+     return jsonout

+ 

+ 

  @API.route("/<repo>/git/tags")

  @API.route("/<namespace>/<repo>/git/tags")

  @API.route("/fork/<username>/<repo>/git/tags")

file modified
+18
@@ -264,3 +264,21 @@ 

          )

  

      return plugin

+ 

+ 

+ def _get_project_tag(project_id, tag_name):

+     """Check if tag exists and get tag obj

+     : param project_id: id of the project

+     : param tag_name: name of the tag

+     : raises pagure.exceptions.APIError: when tag_name doesn't exist on

+         project with id = project_id

+     : return tag object

+     """

+     tag = pagure.lib.query.get_colored_tag(

+         flask.g.session, tag_name, project_id

+     )

+ 

+     if tag is None:

+         raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOTAG)

+ 

+     return tag

file modified
+20
@@ -366,6 +366,26 @@ 

      )

  

  

+ class ApiAddIssueTagForm(PagureForm):

+     """ Form to add a tag to a project from the API endpoint """

+ 

+     tag = wtforms.StringField(

+         "Tag",

+         [

+             wtforms.validators.DataRequired(),

+             wtforms.validators.Regexp(TAGS_REGEX, flags=re.IGNORECASE),

+             wtforms.validators.Length(max=255),

+         ],

+     )

+ 

+     tag_description = wtforms.StringField(

+         "Tag Description", [wtforms.validators.Optional()]

+     )

+     tag_color = wtforms.StringField(

+         "Tag Color", [wtforms.validators.DataRequired()]

+     )

+ 

+ 

  class StatusForm(PagureForm):

      """ Form to add/change the status of an issue. """

  

file modified
+8
@@ -1819,6 +1819,14 @@ 

          ),

      )

  

+     def to_json(self):

+         output = {

+             "tag": self.tag,

+             "tag_description": self.tag_description,

+             "tag_color": self.tag_color,

+         }

+         return output

+ 

      def __repr__(self):

          return "TagColored(id: %s, tag:%s, tag_description:%s, color:%s)" % (

              self.id,

file modified
+1 -1
@@ -942,7 +942,7 @@ 

      new_tag_color,

      user,

  ):

-     """ Removes the specified tag of a project. """

+     """ Edits the specified tag of a project. """

      user_obj = get_user(session, user)

      old_tag_name = old_tag

  

@@ -87,67 +87,6 @@ 

          self.assertEqual(data["version"], pagure.__api_version__)

          self.assertEqual(sorted(data.keys()), ["version"])

  

-     def test_api_project_tags(self):

-         """ Test the api_project_tags function.  """

-         tests.create_projects(self.session)

- 

-         output = self.app.get("/api/0/foo/tags/")

-         self.assertEqual(output.status_code, 404)

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

-         self.assertEqual(set(data.keys()), set(["output", "error"]))

-         self.assertEqual(data["output"], "notok")

-         self.assertEqual(data["error"], "Project not found")

- 

-         output = self.app.get("/api/0/test/tags/")

-         self.assertEqual(output.status_code, 200)

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

-         self.assertEqual(sorted(data.keys()), ["tags", "total_tags"])

-         self.assertEqual(data["tags"], [])

-         self.assertEqual(data["total_tags"], 0)

- 

-         # Add an issue and tag it so that we can list them

-         item = pagure.lib.model.Issue(

-             id=1,

-             uid="foobar",

-             project_id=1,

-             title="issue",

-             content="a bug report",

-             user_id=1,  # pingou

-         )

-         self.session.add(item)

-         self.session.commit()

-         item = pagure.lib.model.TagColored(

-             tag="tag1", tag_color="DeepBlueSky", project_id=1

-         )

-         self.session.add(item)

-         self.session.commit()

-         item = pagure.lib.model.TagIssueColored(

-             issue_uid="foobar", tag_id=item.id

-         )

-         self.session.add(item)

-         self.session.commit()

- 

-         output = self.app.get("/api/0/test/tags/")

-         self.assertEqual(output.status_code, 200)

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

-         self.assertEqual(sorted(data.keys()), ["tags", "total_tags"])

-         self.assertEqual(data["tags"], ["tag1"])

-         self.assertEqual(data["total_tags"], 1)

- 

-         output = self.app.get("/api/0/test/tags/?pattern=t")

-         self.assertEqual(output.status_code, 200)

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

-         self.assertEqual(sorted(data.keys()), ["tags", "total_tags"])

-         self.assertEqual(data["tags"], ["tag1"])

-         self.assertEqual(data["total_tags"], 1)

- 

-         output = self.app.get("/api/0/test/tags/?pattern=p")

-         self.assertEqual(output.status_code, 200)

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

-         self.assertEqual(sorted(data.keys()), ["tags", "total_tags"])

-         self.assertEqual(data["tags"], [])

-         self.assertEqual(data["total_tags"], 0)

- 

      def test_api_groups(self):

          """ Test the api_groups function.  """

  
@@ -241,7 +180,7 @@ 

          output = self.app.get("/api/0/-/error_codes")

          self.assertEqual(output.status_code, 200)

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

-         self.assertEqual(len(data), 41)

+         self.assertEqual(len(data), 42)

          self.assertEqual(

              sorted(data.keys()),

              sorted(
@@ -287,6 +226,7 @@ 

                      "EPLUGINDISABLED",

                      "EPLUGINCHANGENOTALLOWED",

                      "EPLUGINNOTINSTALLED",

+                     "ENOTAG",

                  ]

              ),

          )

@@ -0,0 +1,409 @@ 

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

+ 

+ """

+  Authors:

+    Julen Landa Alustiza <jlanda@fedoraproject.org>

+ """

+ 

+ from __future__ import unicode_literals, absolute_import

+ 

+ import json

+ import sys

+ import os

+ 

+ sys.path.insert(

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

+ )

+ 

+ import tests

+ import pagure.lib.query

+ 

+ 

+ class PagureFlaskApiProjectTagstests(tests.Modeltests):

+     """ Tests for the flask API of pagure project tags """

+ 

+     def test_api_project_tags_no_project(self):

+         """ Test the api_project_tags function.  """

+         output = self.app.get("/api/0/foo/tags/")

+         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_project_tags(self):

+         """ Test the api_project_tags function.  """

+         tests.create_projects(self.session)

+ 

+         output = self.app.get("/api/0/test/tags/")

+         self.assertEqual(output.status_code, 200)

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

+         self.assertEqual(sorted(data.keys()), ["tags", "total_tags"])

+         self.assertEqual(data["tags"], [])

+         self.assertEqual(data["total_tags"], 0)

+ 

+         # Add a tag so that we can list it

+         item = pagure.lib.model.TagColored(

+             tag="tag1", tag_color="DeepBlueSky", project_id=1

+         )

+         self.session.add(item)

+         self.session.commit()

+ 

+         output = self.app.get("/api/0/test/tags/")

+         self.assertEqual(output.status_code, 200)

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

+         self.assertEqual(sorted(data.keys()), ["tags", "total_tags"])

+         self.assertEqual(data["tags"], ["tag1"])

+         self.assertEqual(data["total_tags"], 1)

+ 

+         output = self.app.get("/api/0/test/tags/?pattern=t")

+         self.assertEqual(output.status_code, 200)

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

+         self.assertEqual(sorted(data.keys()), ["tags", "total_tags"])

+         self.assertEqual(data["tags"], ["tag1"])

+         self.assertEqual(data["total_tags"], 1)

+ 

+         output = self.app.get("/api/0/test/tags/?pattern=p")

+         self.assertEqual(output.status_code, 200)

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

+         self.assertEqual(sorted(data.keys()), ["tags", "total_tags"])

+         self.assertEqual(data["tags"], [])

+         self.assertEqual(data["total_tags"], 0)

+ 

+     def test_api_project_tags_new_wrong_token(self):

+         """ Test the api_tags_new method of the flask api. """

+         tests.create_projects(self.session)

+         tests.create_tokens(self.session)

+         tests.create_tokens_acl(self.session)

+ 

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

+         output = self.app.post("/api/0/test/tags/new", 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_project_tags_new_wrong_project(self):

+         """ Test the api_tags_new method of the flask api. """

+ 

+         tests.create_projects(self.session)

+         tests.create_tokens(self.session)

+         tests.create_tokens_acl(self.session)

+ 

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

+         output = self.app.post("/api/0/foo/tags/new", 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_project_tags_new_wrong_acls(self):

+         """ Test the api_tags_new method of the flask api. """

+         tests.create_projects(self.session)

+         tests.create_tokens(self.session)

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

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

+         output = self.app.post("/api/0/test/tags/new", 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: modify_project",

+         }

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

+         self.assertDictEqual(data, expected_rv)

+ 

+     def test_api_project_tags_new_no_input(self):

+         """ Test the api_tags_new method of the flask api. """

+         tests.create_projects(self.session)

+         tests.create_tokens(self.session)

+         tests.create_tokens_acl(self.session)

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

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

+         self.assertEqual(output.status_code, 400)

+         expected_rv = {

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

+             "error_code": "EINVALIDREQ",

+             "errors": {

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

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

+             },

+         }

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

+         self.assertDictEqual(data, expected_rv)

+ 

+     def test_api_project_tags_new(self):

+         """ Test the api_tags_new method of the flask api. """

+         tests.create_projects(self.session)

+         tests.create_tokens(self.session)

+         tests.create_tokens_acl(self.session)

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

+         output = self.app.get("/api/0/test/tags/")

+         self.assertEqual(output.status_code, 200)

+         expected_rv = {"tags": [], "total_tags": 0}

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

+         self.assertDictEqual(data, expected_rv)

+ 

+         data = {"tag": "blue", "tag_color": "DeepBlueSky"}

+ 

+         output = self.app.post(

+             "/api/0/test/tags/new", headers=headers, data=data

+         )

+         self.assertEqual(output.status_code, 200)

+         expected_rv = {

+             "message": "Tag created",

+             "tag": {

+                 "tag": "blue",

+                 "tag_description": "",

+                 "tag_color": "DeepBlueSky",

+             },

+         }

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

+         self.assertDictEqual(data, expected_rv)

+ 

+         output = self.app.get("/api/0/test/tags/")

+         self.assertEqual(output.status_code, 200)

+         expected_rv = {"tags": ["blue"], "total_tags": 1}

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

+         self.assertDictEqual(data, expected_rv)

+ 

+     def test_api_project_tags_new_existing_tag(self):

+         """ Test the api_tags_new method of the flask api. """

+         tests.create_projects(self.session)

+         tests.create_tokens(self.session)

+         tests.create_tokens_acl(self.session)

+         # Add an issue and tag it so that we can list them

+         item = pagure.lib.model.TagColored(

+             tag="blue", tag_color="DeepBlueSky", project_id=1

+         )

+         self.session.add(item)

+         self.session.commit()

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

+         output = self.app.get("/api/0/test/tags/")

+         self.assertEqual(output.status_code, 200)

+         expected_rv = {"tags": ["blue"], "total_tags": 1}

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

+         self.assertDictEqual(data, expected_rv)

+ 

+         data = {"tag": "blue", "tag_color": "DeepBlueSky"}

+ 

+         output = self.app.post(

+             "/api/0/test/tags/new", headers=headers, data=data

+         )

+         self.assertEqual(output.status_code, 400)

+         expected_rv = {

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

+             "the action from reaching completion",

+             "error_code": "EDBERROR",

+         }

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

+         self.assertDictEqual(data, expected_rv)

+ 

+         output = self.app.get("/api/0/test/tags/")

+         self.assertEqual(output.status_code, 200)

+         expected_rv = {"tags": ["blue"], "total_tags": 1}

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

+         self.assertDictEqual(data, expected_rv)

+ 

+     def test_api_project_tag_delete_wrong_token(self):

+         """ Test the api_project_tag_delete method of flask api. """

+         tests.create_projects(self.session)

+         tests.create_tokens(self.session)

+         tests.create_tokens_acl(self.session)

+ 

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

+         output = self.app.delete("/api/0/test/tag/blue", 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_project_tag_delete_wrong_project(self):

+         """ Test the api_project_tag_delete method of flask api. """

+         tests.create_projects(self.session)

+         tests.create_tokens(self.session)

+         tests.create_tokens_acl(self.session)

+ 

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

+         output = self.app.delete("/api/0/foo/tag/blue", 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_project_tag_delete_wrong_tag(self):

+         """ Test the api_project_tag_delete method of flask api. """

+         tests.create_projects(self.session)

+         tests.create_tokens(self.session)

+         tests.create_tokens_acl(self.session)

+ 

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

+         output = self.app.delete("/api/0/test/tag/blue", headers=headers)

+         self.assertEqual(output.status_code, 404)

+         expected_rv = {"error": "Tag not found", "error_code": "ENOTAG"}

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

+         self.assertDictEqual(data, expected_rv)

+ 

+     def test_api_project_tag_delete(self):

+         """ Test the api_project_tag_delete method of flask api. """

+         tests.create_projects(self.session)

+         tests.create_tokens(self.session)

+         tests.create_tokens_acl(self.session)

+ 

+         item = pagure.lib.model.TagColored(

+             tag="blue", tag_color="DeepBlueSky", project_id=1

+         )

+         self.session.add(item)

+         self.session.commit()

+         output = self.app.get("/api/0/test/tags/")

+         self.assertEqual(output.status_code, 200)

+         expected_rv = {"tags": ["blue"], "total_tags": 1}

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

+         self.assertDictEqual(data, expected_rv)

+ 

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

+         output = self.app.delete("/api/0/test/tag/blue", headers=headers)

+         self.assertEqual(output.status_code, 200)

+         expected_rv = {"message": "Tag: blue has been deleted"}

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

+         self.assertDictEqual(data, expected_rv)

+         output = self.app.get("/api/0/test/tags/")

+         self.assertEqual(output.status_code, 200)

+         expected_rv = {"tags": [], "total_tags": 0}

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

+         self.assertDictEqual(data, expected_rv)

+ 

+     def test_api_project_tag_delete_with_assigned_issue_and_pr(self):

+         """ Test the api_project_tag_delete method of flask api. """

+         tests.create_projects(self.session)

+         tests.create_tokens(self.session)

+         tests.create_tokens_acl(self.session)

+ 

+         # Add a tag

+         item = pagure.lib.model.TagColored(

+             tag="blue", tag_color="DeepBlueSky", project_id=1

+         )

+         self.session.add(item)

+         self.session.commit()

+ 

+         # Add a tagged issue

+         item = pagure.lib.model.Issue(

+             id=1,

+             uid="foobar",

+             project_id=1,

+             title="issue",

+             content="a bug report",

+             user_id=1,  # pingou

+         )

+         self.session.add(item)

+         self.session.commit()

+         item = pagure.lib.model.TagIssueColored(issue_uid="foobar", tag_id=1)

+         self.session.add(item)

+         self.session.commit()

+ 

+         # Add a tagged pull request

+         item = pagure.lib.model.PullRequest(

+             id=1,

+             uid="barfoo",

+             project_id=1,

+             branch="master",

+             branch_from="master",

+             title="pull request",

+             allow_rebase=False,

+             user_id=1,  # pingou

+         )

+         self.session.add(item)

+         self.session.commit()

+         item = pagure.lib.model.TagPullRequest(request_uid="barfoo", tag_id=1)

+         self.session.add(item)

+         self.session.commit()

+ 

+         output = self.app.get("/api/0/test/tags/")

+         self.assertEqual(output.status_code, 200)

+         expected_rv = {"tags": ["blue"], "total_tags": 1}

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

+         self.assertDictEqual(data, expected_rv)

+ 

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

+         output = self.app.delete("/api/0/test/tag/blue", headers=headers)

+         self.assertEqual(output.status_code, 200)

+         expected_rv = {"message": "Tag: blue has been deleted"}

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

+         self.assertDictEqual(data, expected_rv)

+ 

+         output = self.app.get("/api/0/test/tags/")

+         self.assertEqual(output.status_code, 200)

+         expected_rv = {"tags": [], "total_tags": 0}

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

+         self.assertDictEqual(data, expected_rv)

+ 

+     def test_api_project_tag_view_no_project(self):

+         """ Test the api_project_tag_view method of the flask api.  """

+         output = self.app.get("/api/0/foo/tag/tag1")

+         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_project_tag_view_wrong_tag(self):

+         """ Test the api_project_tag_view method of the flask api.  """

+         tests.create_projects(self.session)

+         tests.create_tokens(self.session)

+         tests.create_tokens_acl(self.session)

+ 

+         item = pagure.lib.model.TagColored(

+             tag="blue", tag_color="DeepBlueSky", project_id=1

+         )

+         self.session.add(item)

+         self.session.commit()

+         output = self.app.get("/api/0/test/tag/tag1")

+         self.assertEqual(output.status_code, 404)

+         expected_rv = {"error": "Tag not found", "error_code": "ENOTAG"}

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

+         self.assertDictEqual(data, expected_rv)

+ 

+     def test_api_project_tag_view(self):

+         """ Test the api_project_tag_view method of the flask api.  """

+         tests.create_projects(self.session)

+         tests.create_tokens(self.session)

+         tests.create_tokens_acl(self.session)

+ 

+         item = pagure.lib.model.TagColored(

+             tag="blue", tag_color="DeepBlueSky", project_id=1

+         )

+         self.session.add(item)

+         self.session.commit()

+         output = self.app.get("/api/0/test/tag/blue")

+         self.assertEqual(output.status_code, 200)

+         expected_rv = {

+             "tag": "blue",

+             "tag_color": "DeepBlueSky",

+             "tag_description": "",

+         }

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

+         self.assertDictEqual(data, expected_rv)

I was going to work on #4663 and found some problems on the get endpoint, so fix them first:

  • Add namespace support
  • Move it to project with the rest of project|repo related endpoints
  • Use api_method decortator and _get_repo

And fixes #4663 after

Don't merge this yet, I wanna check some more things :)

rebased onto 63e3e4a2f94293271a943c98d12fadbc9bb37d0f

a year ago

rebased onto 280da6908ca1304a7052f6f037e54a3c348e9f75

a year ago

Should I remove the ENABLE_TICKETS check? tags can be used on pull requests too...

1 new commit added

  • Fix doc typo
a year ago

rebased onto befd3f0c180a461a0bd72510694d146ef27c2cc4

a year ago

1 new commit added

  • forms: tag must be set on *IssueTagForm
a year ago

1 new commit added

  • api docs: move project tags docs to project section
a year ago

1 new commit added

  • remove_tags query: use reusable remove_tags_obj() for issue|pull_request untagging
a year ago

8 new commits added

  • api/project: get project tag endpoint
  • api/project: patch project tag endpoint
  • forms: edit issue tag form
  • api/project: delete project tag endpoint
  • api.utils: _get_project_tag helper method
  • api/project: new project tag endpoint
  • tests: move project tags tests to their own file
  • models: to_json method on TagColored
a year ago

14 new commits added

  • api/project: get project tag endpoint
  • api/project: patch project tag endpoint
  • forms: edit issue tag form
  • api/project: delete project tag endpoint
  • api.utils: _get_project_tag helper method
  • api/project: new project tag endpoint
  • tests: move project tags tests to their own file
  • models: to_json method on TagColored
  • remove_tags query: use reusable remove_tags_obj() for issue|pull_request untagging
  • api docs: move project tags docs to project section
  • forms: tag must be set on *IssueTagForm
  • tests: move project tags test to project tests file
  • Fix doc typo
  • Some modifications on project tag get api endpoint:
a year ago

14 new commits added

  • api/project: get project tag endpoint
  • api/project: patch project tag endpoint
  • forms: edit issue tag form
  • api/project: delete project tag endpoint
  • api.utils: _get_project_tag helper method
  • api/project: new project tag endpoint
  • tests: move project tags tests to their own file
  • models: to_json method on TagColored
  • remove_tags query: use reusable remove_tags_obj() for issue|pull_request untagging
  • api docs: move project tags docs to project section
  • forms: tag must be set on *IssueTagForm
  • tests: move project tags test to project tests file
  • Fix doc typo
  • Some modifications on project tag get api endpoint:
a year ago

14 new commits added

  • api/project: get project tag endpoint
  • api/project: patch project tag endpoint
  • forms: edit issue tag form
  • api/project: delete project tag endpoint
  • api.utils: _get_project_tag helper method
  • api/project: new project tag endpoint
  • tests: move project tags tests to their own file
  • models: to_json method on TagColored
  • remove_tags query: use reusable remove_tags_obj() for issue|pull_request untagging
  • api docs: move project tags docs to project section
  • forms: tag must be set on *IssueTagForm
  • tests: move project tags test to project tests file
  • Fix doc typo
  • Some modifications on project tag get api endpoint:
a year ago

14 new commits added

  • api/project: get project tag endpoint
  • api/project: patch project tag endpoint
  • forms: edit issue tag form
  • api/project: delete project tag endpoint
  • api.utils: _get_project_tag helper method
  • api/project: new project tag endpoint
  • tests: move project tags tests to their own file
  • models: to_json method on TagColored
  • remove_tags query: use reusable remove_tags_obj() for issue|pull_request untagging
  • api docs: move project tags docs to project section
  • forms: tag must be set on *IssueTagForm
  • tests: move project tags test to project tests file
  • Fix doc typo
  • Some modifications on project tag get api endpoint:
a year ago

14 new commits added

  • api/project: get project tag endpoint
  • api/project: patch project tag endpoint
  • forms: edit issue tag form
  • api/project: delete project tag endpoint
  • api.utils: _get_project_tag helper method
  • api/project: new project tag endpoint
  • tests: move project tags tests to their own file
  • models: to_json method on TagColored
  • remove_tags query: use reusable remove_tags_obj() for issue|pull_request untagging
  • api docs: move project tags docs to project section
  • forms: tag must be set on *IssueTagForm
  • tests: move project tags test to project tests file
  • Fix doc typo
  • Some modifications on project tag get api endpoint:
a year ago

pretty please pagure-ci rebuild

a year ago

Not sure but you only add a tag here, not an issue. Is the comment wrong ?

Not sure but you only add a tag here, not an issue. Is the comment wrong ?

indeed, I added one but it was not necessary and removed it after, and did not update the comment. I'll fix it

So that looks good to me as well ! :thumbsup:

14 new commits added

  • api/project: get project tag endpoint
  • api/project: patch project tag endpoint
  • forms: edit issue tag form
  • api/project: delete project tag endpoint
  • api.utils: _get_project_tag helper method
  • api/project: new project tag endpoint
  • tests: move project tags tests to their own file
  • models: to_json method on TagColored
  • remove_tags query: use reusable remove_tags_obj() for issue|pull_request untagging
  • api docs: move project tags docs to project section
  • forms: tag must be set on *IssueTagForm
  • tests: move project tags test to project tests file
  • Fix doc typo
  • Some modifications on project tag get api endpoint:
a year ago

pretty please pagure-ci rebuild

a year ago

rebased onto 91bd166304ac087795317d2386b520a56d988455

a year ago

Hm, I need to check this more closely but the changes make me wonder if this is backward compatible?

Hm, I need to check this more closely but the changes make me wonder if this is backward compatible?

Current API endpoint has not been touched.

AFAIK, The possible non backward compatible change is that now tag.tag is mandatory while it was an optional one, but on the ui part I have been looking tag.tag was mandatory on html forms, so yeah, the POST call is not fully backward compatible, but the overall result is.

Anyhow, I have to fix the pr and resolve conflicts with #4669

rebased onto f11c9717c7a94e2f43a0c4efd6f462235b6ba3d5

a year ago

Rebased and ready for review. I ended stacking a bunch of commits so feel free to review commit by commit :)

Tests will fail on pip container due to the pygit2 thing, but it already passed on f29

@pingou you were right, there are some backward incompatible changes:

f11c9717c7a94e2f43a0c4efd6f462235b6ba3d5 : the error output on /api/0/<repo>/tags has changed the format. Previously it had a custom output when repo was not found, now uses the same helper method and same output that the rest of endpoints uses for this.

e881c7f096d8520ef64f0380e62a2b9bd6ebbc71: this modifies the form's mandatory fields, so the POST endpoint requirements had changed. But in frontend side this field was already mandatory, so the ui is backward compatible.

1e2e964d002967c82319b7cc813085504224e2bc: this produces ev and log events for each of the untagged issues|pull_requests when someone removes a tag that is actively used on issues|pull requests. improvement, but could be a bit spammer if someone removes a tag that had been used on 1k issues :)

The rest of the changes are new features or backward compatible afaik.

Arg, non-backward compatible changes are always tricky to handle :(

f11c971 : the error output on /api/0/<repo>/tags has changed the format. Previously it had a custom output when repo was not found, now uses the same helper method and same output that the rest of endpoints uses for this.

I'm kinda of ok with this one as that endpoint was the exception more than the rule

e881c7f: this modifies the form's mandatory fields, so the POST endpoint requirements had changed. But in frontend side this field was already mandatory, so the ui is backward compatible.

Shouldn't this be documented in the API as mandatory field then?
And wasn't the fact that it was optional used as a way to remove a tag?

1e2e964: this produces ev and log events for each of the untagged issues|pull_requests when someone removes a tag that is actively used on issues|pull requests. improvement, but could be a bit spammer if someone removes a tag that had been used on 1k issues :)

This is backward compatible but indeed, may lead to some notifications flooding :s

Arg, non-backward compatible changes are always tricky to handle :(

f11c971 : the error output on /api/0/<repo>/tags has changed the format. Previously it had a custom output when repo was not found, now uses the same helper method and same output that the rest of endpoints uses for this.

I'm kinda of ok with this one as that endpoint was the exception more than the rule

:thumbsup:

e881c7f: this modifies the form's mandatory fields, so the POST endpoint requirements had changed. But in frontend side this field was already mandatory, so the ui is backward compatible.

Shouldn't this be documented in the API as mandatory field then?
And wasn't the fact that it was optional used as a way to remove a tag?

I'll recheck what's going on the ui part with this since I don't remember

1e2e964: this produces ev and log events for each of the untagged issues|pull_requests when someone removes a tag that is actively used on issues|pull requests. improvement, but could be a bit spammer if someone removes a tag that had been used on 1k issues :)

This is backward compatible but indeed, may lead to some notifications flooding :s

The main reason for this is sending ev events so if you're viewing issue #n with tag foo and I remove the tag from the project then you receive the ev event on the same way that you would receive it if I just untag the issue. I'll open an issue about this and get the commit out from this pr, we can handle that on a different phase

rebased onto 91c16e7882876b31347ad73779b75d112847d1fc

a year ago

rebased onto f11c9717c7a94e2f43a0c4efd6f462235b6ba3d5

a year ago

rebased onto 87f4a6baa85e04813cc14f0079e1fc3c98f3a71d

a year ago

1e2e964 removed from pull request. Something is wrong for el7, I'll fix that before continuing with the rest of modifications

10 new commits added

  • api/project: get project tag endpoint
  • api/project: delete project tag endpoint
  • api.utils: _get_project_tag helper method
  • api/project: new project tag endpoint
  • tests: move project tags tests to their own file
  • models: to_json method on TagColored
  • api docs: move project tags docs to project section
  • tests: move project tags test to project tests file
  • Fix doc typo
  • Some modifications on project tag get api endpoint:
a year ago

10 new commits added

  • api/project: get project tag endpoint
  • api/project: delete project tag endpoint
  • api.utils: _get_project_tag helper method
  • api/project: new project tag endpoint
  • tests: move project tags tests to their own file
  • models: to_json method on TagColored
  • api docs: move project tags docs to project section
  • tests: move project tags test to project tests file
  • Fix doc typo
  • Some modifications on project tag get api endpoint:
a year ago

10 new commits added

  • api/project: get project tag endpoint
  • api/project: delete project tag endpoint
  • api.utils: _get_project_tag helper method
  • api/project: new project tag endpoint
  • tests: move project tags tests to their own file
  • models: to_json method on TagColored
  • api docs: move project tags docs to project section
  • tests: move project tags test to project tests file
  • Fix doc typo
  • Some modifications on project tag get api endpoint:
a year ago

I'll not have time to fix the issues with patch endpoint during next days, and that's not necessary to unblock @fbo , so I made some changes on the commit stack:

  • get rid of backward incompatible form changes. better safe than sorry.
  • align new tag api endpoint's doc string with new form.
  • remove the patch endpoint that is failing to validate the form on el7 environment

@jlanda can we land this? It's been lingering for too long... :'(

@jlanda can we land this? It's been lingering for too long... :'(

Yes. I removed the patch endpoint, we can land it later

rebased onto 41d733d417f4065f7c6d26e5807c4142901f34d1

a year ago

Indentation issue on this one.
Also note for these two lines that the | in the description should not be needed since it is a single line.

One tiny change and let's get this in :)

10 new commits added

  • api/project: get project tag endpoint
  • api/project: delete project tag endpoint
  • api.utils: _get_project_tag helper method
  • api/project: new project tag endpoint
  • tests: move project tags tests to their own file
  • models: to_json method on TagColored
  • api docs: move project tags docs to project section
  • tests: move project tags test to project tests file
  • Fix doc typo
  • Some modifications on project tag get api endpoint:
a year ago

Indentation issue on this one.
Also note for these two lines that the | in the description should not be needed since it is a single line.

Done.

Let's be consistent and remove the | here as well :)

10 new commits added

  • api/project: get project tag endpoint
  • api/project: delete project tag endpoint
  • api.utils: _get_project_tag helper method
  • api/project: new project tag endpoint
  • tests: move project tags tests to their own file
  • models: to_json method on TagColored
  • api docs: move project tags docs to project section
  • tests: move project tags test to project tests file
  • Fix doc typo
  • Some modifications on project tag get api endpoint:
a year ago

rebased onto 2dba705

a year ago

Pull-Request has been merged by pingou

a year ago
Metadata
Flags
jenkins
success (100%)
Build #3069 successful (commit: f6b1f497)
a year ago
jenkins
success (100%)
Build #3070 successful (commit: f6b1f497)
a year ago
jenkins
success (100%)
Build #3067 successful (commit: f6b1f497)
a year ago
jenkins
success (100%)
Build #3060 successful (commit: 38f4b16c)
a year ago
jenkins
success (100%)
Build #3033 successful (commit: c766881f)
a year ago
jenkins
success (100%)
Build #3032 successful (commit: c766881f)
a year ago
jenkins
failure
Build #3031 failed (commit: 2f0e1f65)
a year ago
jenkins
failure
Build #3030 failed (commit: 20da7a24)
a year ago
jenkins
failure
Build #3029 failed (commit: 1d23ff43)
a year ago
jenkins
failure
Build #3028 failed (commit: 3a021cd8)
a year ago
jenkins
failure
Build #3013 failed (commit: 95784df3)
a year ago
jenkins
failure
Build #2979 failed (commit: 432170de)
a year ago
jenkins
failure
Build #2978 failed (commit: fd713eba)
a year ago
jenkins
failure
Build #2968 failed (commit: 81ec5a85)
a year ago
jenkins
failure
Build #2967 failed (commit: 8effbfde)
a year ago
jenkins
failure
Build #2966 failed (commit: 8effbfde)
a year ago
jenkins
failure
Build #2965 failed (commit: 8effbfde)
a year ago
jenkins
failure
Build #2964 failed (commit: 71447b9e)
a year ago
jenkins
success (100%)
Build #2961 successful (commit: 0bfdf005)
a year ago
jenkins
success (100%)
Build #2960 successful (commit: d0f2eaa6)
a year ago
jenkins
success (100%)
Build #2959 successful (commit: 5e12f261)
a year ago
jenkins
success (100%)
Build #2958 successful (commit: 4f6a7034)
a year ago
jenkins
success (100%)
Build #2957 successful (commit: fe3710fb)
a year ago
jenkins
success (100%)
Build #2956 successful (commit: 280da690)
a year ago
jenkins
success (100%)
Build #2955 successful (commit: 280da690)
a year ago
jenkins
success (100%)
Build #2954 successful (commit: 7b235f85)
a year ago