Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ISSUE-1836: Add Dashboard Support #1837

Merged

Conversation

jpavlav
Copy link
Contributor

@jpavlav jpavlav commented Mar 12, 2024

Addresses ISSUE-1836
#1836
The impetus for these changes is a project I'm working on at work. We want to create a dynamically updating dashboard that helps prioritize maintenance operations. I realize this is a large PR, and one large commit, but I feel like the work in it's entirety makes sense when looked at together. Additionally, there are some changes that were just formatting, in the case where spacing or anything along those lines have changed, that was auto-formatting and accepted by the linting tox -e lint.

jira/client.py

  • Imported DashboardItemProperty, DashboardItemPropertyKey, and Gadget resources to client for use in new methods.
  • Updated the dashboards method to include the gadgets that exist on a given dashboard. This is a logical association that makes sense, but isn't directly exposed in the API.
  • Added create_dashboard method. It creates a dashboard via the API and returns a Dashbord object.
  • Added copy_dashboard method.
  • Added update_dashboard_automatic_refresh_seconds method. This calls the internal API, which is why it's decorated with experimental and cloud. This might change in the future, but it really is a handy thing to have, otherwise, the user has to configure this in the web interface.

  • Added dashboard_item_property method. This is available on both cloud and server.
  • Added dashboard_item_property_keys method. This is available on both cloud and server.
  • Added set_dashboard_item_property method. This is available on both cloud and server.

^^ These methods all provide a means of adding arbitrary metadata to dashboard_items (gadgets) and/or configure them via specific keys.

  • Added dashboard_gadgets method. This returns the gadgets associated with a given dashboard. It also iterates over the keys for this gadget's properties, generating a list of DashboardItemProperty objects that are associated with each gadget. This makes it really easy for the user to associate which configuration/metadata goes with which gadget.
  • Added gadgets method. This returns a list of from jira of all the gadgets that are available to add to any dashboard.
  • Added add_gadget_to_dashboard method. This allows the user to add gadgets to a specified dashboard.
  • Added the protected method _get_internal_url. This is very similar to get_url or get_latest url, where options are updated to allow for easy resolution of paths that are on the internal jira api.
  • Updated the _find_for_resource type hint on ids because it is possible that a resource requires more than 2 ids to resolve it's url.

jira/resources.py

  • Added the new resources DashboardItemProperty, DashboardItemPropertyKey, and Gadget to the __all__ list so they are represented.
  • Updated the typehint on the find method for the same reason as the _find_for_resource method.
  • Added a gadgets attribute to the Dashboard resource to house gadget references.
  • Added DashbordItemPropertyKey resource.
  • Added DashboardItemProperty resource. The update and delete methods are overridden here because it does not have a self attribute. This is kind of in an in between space as far as being considered a resource, but for ease of use as an interface, it makes sense for it to be considered.
  • Added Gadget resource. It too has overridden update and delete methods for the aforementioned reasons.

jira/utils/init.py

  • Added cloud convenience decorator for client methods that make calls that are only available on the cloud api. It checks the client instance to see if it _is_cloud. If not, it logs a warning and returns None. This was the convention seen on other endpoints on the client.
  • Added experimental convenience decorator for client methods that make calls that are experimental. It attempts to run the client method, if a JIRAError is raised that has a response object, the response is checked for a status code in [404, 405] indicating either the path no longer accepts the HTTP verb or no longer exists, and then logs a warning and returns None. Otherwise it reraises the error

^^ Regarding these convenience decorators, it makes it really apparent to developers which calls are for the cloud and/or experimental.

  • Added remove_empty_attributes convenience method. I found myself having to remove empty attributes or add a lot of branching in order to accommodate optional payload parameters or path parameters. This function made that easier.

Finally I wrote tests to cover these changes. These tests worked for me locally against my docker jira server instance. However, most of them didn't run, because the test checks the environment to find out where it is running.

For the cloud tests, I signed up for a trial account for jira cloud and ran them against my instance. They all panned out okay and cleaned up after themselves appropriately.

@jpavlav jpavlav force-pushed the jpavlav/issue-1836/add_dashboard_support branch from b91fa90 to f585a97 Compare March 12, 2024 22:03
@jpavlav jpavlav force-pushed the jpavlav/issue-1836/add_dashboard_support branch from 265daf3 to f58619e Compare March 12, 2024 22:25
@jpavlav jpavlav force-pushed the jpavlav/issue-1836/add_dashboard_support branch from 331b62b to 9370b18 Compare March 12, 2024 22:39
@jpavlav jpavlav force-pushed the jpavlav/issue-1836/add_dashboard_support branch from 31c0b18 to 6a502ba Compare March 13, 2024 01:05
@jpavlav jpavlav force-pushed the jpavlav/issue-1836/add_dashboard_support branch from 40021f5 to 6876c6f Compare March 13, 2024 14:25
@jpavlav jpavlav force-pushed the jpavlav/issue-1836/add_dashboard_support branch from 8e4fc5e to b82e326 Compare March 15, 2024 14:23
@jpavlav jpavlav force-pushed the jpavlav/issue-1836/add_dashboard_support branch from 6b3a7cb to 0c9a753 Compare March 15, 2024 15:32
jira/client.py Outdated Show resolved Hide resolved
@jpavlav jpavlav force-pushed the jpavlav/issue-1836/add_dashboard_support branch from 40cc086 to ae36f41 Compare March 17, 2024 22:24
jpavlav added 3 commits March 17, 2024 18:14
The impetus for these changes is a project I'm working on at work. We
want to create a dynamically updating dashboard that helps prioritize
maintenance operations. I realize this is a large PR, and one large
commit, but I feel like the work in it's entirety makes sense when
looked at together. Additionally, there are some changes that were just
formatting, in the case where spacing or anything along those lines have
changed, that was auto-formatting and accepted by the linting `tox -e
lint`.

`jira/client.py`
----------------
* Imported `DashboardItemProperty`, `DashboardItemPropertyKey`, and
`Gadget` resources to client for use in new methods.
* Updated the `dashboards` method to include the `gadgets` that exist on
a given dashboard. This is a logical association that makes sense, but
isn't directly exposed in the API.
* Added `create_dashboard` method. It creates a dashboard via the API
and returns a `Dashbord` object.
* Added `copy_dashboard` method.
* Added `update_dashboard_automatic_refresh_seconds` method. This calls
the `internal` API, which is why it's decorated with `experimental` and
`cloud`. This might change in the future, but it really is a handy thing
to have, otherwise, the user has to configure this in the web interface.

---
* Added `dashboard_item_property` method. This is available on both
`cloud` and `server`.
* Added `dashboard_item_property_keys` method. This is available on both
`cloud` and `server`.
* Added `set_dashboard_item_property` method. This is available on both
`cloud` and `server`.
---
^^ These methods all provide a means of adding arbitrary metadata to
`dashboard_items` (`gadgets`) and/or configure them via specific keys.

* Added `dashboard_gadgets` method. This returns the gadgets associated
with a given dashboard. It also iterates over the `keys` for this
`gadget`'s properties, generating a list of `DashboardItemProperty`
objects that are associated with each gadget. This makes it really easy
for the user to associate which configuration/metadata goes with which
gadget.
* Added `gadgets` method. This returns a list of from `jira` of all the
`gadgets` that are available to add to any dashboard.
* Added `add_gadget_to_dashboard` method. This allows the user to add
gadgets to a specified dashboard.
* Added the protected method `_get_internal_url`. This is very similar
to `get_url` or `get_latest` url, where `options` are updated to allow
for easy resolution of paths that are on the `internal` `jira` api.
* Updated the `_find_for_resource` type hint on `ids` because it is
possible that a resource requires more than `2` ids to resolve it's url.

jira/resources.py
-----------------
* Added the new resources `DashboardItemProperty`,
`DashboardItemPropertyKey`, and `Gadget` to the `__all__` list so they
are represented.
* Updated the typehint on the `find` method for the same reason as the
`_find_for_resource` method.
* Added a `gadgets` attribute to the `Dashboard` resource to house
`gadget` references.
* Added `DashbordItemPropertyKey` resource.
* Added `DashboardItemProperty` resource. The `update` and `delete`
methods are overridden here because it does not have a `self` attribute.
This is kind of in an in between space as far as being considered a
resource, but for ease of use as an interface, it makes sense for it to
be considered.
* Added `Gadget` resource. It too has overridden `update` and `delete`
methods for the aforementioned reasons.

jira/utils/__init__.py
----------------------
* Added `cloud` convenience decorator for client methods that make calls
that are only available on the `cloud` api. It checks the `client`
instance to see if it `_is_cloud`. If not, it logs a warning and returns
`None`. This was the convention seen on other endpoints on the `client`.
* Added `experimental` convenience decorator for client methods that
make calls that are experimental. It attempts to run the client method,
if a `JIRAError` is raised that has a response object, the response is
checked for a status code in `[404, 405]` indicating either the path no
longer accepts the HTTP verb or no longer exists, and then logs a
warning and returns `None`. Otherwise it reraises the error

^^ Regarding these convenience decorators, it makes it really apparent
to developers which calls are for the cloud and/or experimental.

* Added `remove_empty_attributes` convenience method. I found myself
having to remove empty attributes or add a lot of branching in order to
accommodate optional payload parameters or path parameters. This
function made that easier.

Finally I wrote tests to cover these changes. These tests worked for me
locally against my docker `jira server` instance. However, most of them
didn't run, because the test checks the environment to find out where it
is running.

For the cloud tests, I signed up for a trial account for `jira cloud`
and ran them against my instance. They all panned out okay and cleaned
up after themselves appropriately.

ISSUE-1836: Address linting

ISSUE-1836: Fix CI issue

ISSUE-1836: Fix `TypeError` in test

ISSUE-1836: Allow `dashboard` tests on cloud

ISSUE-1836: Shore Up Testing

Refactored the `utils` tests a bit to make them more a little more
straight-forward and hit all branches.

Added tests for `update` on `dashboard/gadget` resources.

ISSUE-1836: Adjust `set_dashboard_item_property`

Turns out if you use `set_dashboard_item_property` on an existing
dashboard gadget, you get back a `200` response. This makes sense as
you're not creating something new. To account for this, rather than
looking explicitly for a `201` response, we are now using the `ok`
attribute of the response. Updated the test accordingly.

ISSUE-1836: Fix `DashboardItemProperty` update method

* Found an idiosyncrasy with the `update` method on this resource. The
idea is to be able to update the item property value without clobbering
it (that's what `set_dashboard_item_property` is for). This change
ensures that happens, by only updating the resources current `value`
with the user provided input.
* Make method name align with parameters/ui
* Change name of `Gadget` to `DashboarGadget` for specificity and
clarity. Could be `gadget`'s of other kinds might exist in the future.
* Updated `gadgets` method name to `all_dashboard_gadgets`. More in line
with what it does, plus adds the `dashboard` context to the method.
studioj
studioj previously approved these changes Mar 18, 2024
Copy link
Contributor

@adehad adehad left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An excellent quality PR, still a few things that I think should be addressed before merging, but a promising start!

jira/client.py Outdated Show resolved Hide resolved
jira/client.py Outdated Show resolved Hide resolved
jira/client.py Outdated Show resolved Hide resolved
jira/client.py Show resolved Hide resolved
jira/client.py Outdated Show resolved Hide resolved
jira/utils/__init__.py Outdated Show resolved Hide resolved
jira/utils/__init__.py Outdated Show resolved Hide resolved
jira/utils/__init__.py Outdated Show resolved Hide resolved
tests/resources/test_dashboard.py Outdated Show resolved Hide resolved
tests/resources/test_dashboard.py Outdated Show resolved Hide resolved
@jpavlav
Copy link
Contributor Author

jpavlav commented Mar 19, 2024

Thanks for taking a look! I'll make another commit with some updates addressing your feedback.

* Created `NotJIRAInstanceError` exception. This is raised in the case
one of the convenience decorators utilized on a client method is
improperly applied to some other kind of object.
* Moved the `cloud/experimental` decorators into `client`. They are
technincally utilites for the `client` so I think this makes sense
logically. It also solves the issue of circular imports that came up
when attempting to do a type check on the instance that is passed into
these decorated functions.
* Updated `cloud` decorator name to `cloud_api` to make it clearer.
* Updated `experimental` to `experimental_atlassian_api`, to make it
apparent to a developer that the `experimental` aspect of the decorated
method is on the `atlassian` side of things, not on our side.
* Updated docstrings where `ResultList`'s are being returned to include
the kind of resource that is stored within the `ResultList`.
* Updated `copy_dashboard` and `create_dashboard` function signatures.
`edit_permissions` and `share_permissions` default values are no longer
`list`'s to avoid the issues with mutable default argument evaluation.
Now they are set to `None` by default and within the methods, they are
defaulted to `[]` in the case that they evaluate "falsey".
* Made `_get_internal_url` docstring more representative of what it
does.
* Ensured return types on the `update` methods of the
`DashboardItemProperty` resource and the `DashboardGadget` is correct.
* Added `only_run_on_cloud` marker to skip a test if it should only run
during cloud tests to `conftest`.
* Updated `test_dashboard` to utilize `only_run_on_cloud`.
* Moved tests of `cloud_api/experimental_atlassian_api` decorators into
the `test_client` file. Refactored those tests a bit.
Copy link
Contributor

@adehad adehad left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, there is one pending thing we need to look at here.

And that is the resource_class_map in the resources.py file:

r"dashboard/[^/]+$": Dashboard,

What we are looking at is a regex mapping of the URLs to the resource types.
From what I can see from the REST API reference we are dealing with:
https://developer.atlassian.com/cloud/jira/platform/rest/v2/api-group-dashboards/#api-rest-api-2-dashboard-get

# DashboardItemProperty
/rest/api/2/dashboard/{dashboardId}/items/{itemId}/properties
# Example regex pattern for the map:
# https://regex101.com/r/1Nw7U6/1
# r"dashboard/[^/]+/items/[^/]+/properties$"

# DashboardItemKey
/rest/api/2/dashboard/{dashboardId}/items/{itemId}/properties/{propertyKey}

# DashboardGadget
/rest/api/2/dashboard/{dashboardId}/gadget/{gadgetId}

Once this is in we can add to your existing tests an isinstance() check on the class type

jira/resources.py Show resolved Hide resolved
@adehad adehad linked an issue Mar 20, 2024 that may be closed by this pull request
@jpavlav jpavlav requested a review from adehad March 20, 2024 15:35
jpavlav added 2 commits March 20, 2024 11:14
* Added necessary path regex/class mapping to `resource_class_map`.
* Updated the `test_generic_resource.py` tests to include the resolution
of those new paths, their regex, to the appropriate class.
Included some `isinstance` assertions in the `Dashboard` tests per
@adehad's request.
@jpavlav
Copy link
Contributor Author

jpavlav commented Mar 20, 2024

@adehad I think I've got it now. I added the paths to the new resources. Added some more data points for the parameterized test_generic_resource that cover the new additions to that resource map dict. I also peppered the dashboard tests themselves with isinstance assertions where I thought it made sense. The cloud tests are still passing locally for me so figured 👍 . Let me know if there is anything else I need to address please 😄

Copy link
Contributor

@adehad adehad left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

excellent. I'll need to wait till later to actually run the cloud tests to merge (@studioj if you are able to confirm feel free to squash merge)

@adehad adehad merged commit fbcabe1 into pycontribs:main Mar 22, 2024
9 of 11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Flesh out Dashboard Endpoints
3 participants