diff --git a/README.md b/README.md index 7df346f..216f60c 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ Install via: pip install teamscale-client # Development +We suggest the [PyCharm](https://www.jetbrains.com/pycharm/) IDE for development in this project. We are happy to add additional service calls to the client. Please make sure you include a test, if you add a service call. To run them use: python setup.py test diff --git a/examples/cleanupStaleDashboards.py b/examples/cleanupStaleDashboards.py new file mode 100644 index 0000000..6ec3489 --- /dev/null +++ b/examples/cleanupStaleDashboards.py @@ -0,0 +1,99 @@ +# + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import json +import requests + +import datetime +import os +import sys +import re + +from teamscale_client import TeamscaleClient + + +TEAMSCALE_URL = "http://localhost:8080" + +USERNAME = "build" +ACCESS_TOKEN = "ide-access-token" + + +def confirm(prompt=None, resp=False): + """prompts for yes or no response from the user. Returns True for yes and + False for no. + + 'resp' should be set to the default value assumed by the caller when + user simply types ENTER. + + >>> confirm(prompt='Create Directory?', resp=True) + Create Directory? [y]|n: + True + >>> confirm(prompt='Create Directory?', resp=False) + Create Directory? [n]|y: + False + >>> confirm(prompt='Create Directory?', resp=False) + Create Directory? [n]|y: y + True + + """ + + if prompt is None: + prompt = 'Confirm' + + if resp: + prompt = '%s [%s]|%s: ' % (prompt, 'y', 'n') + else: + prompt = '%s [%s]|%s: ' % (prompt, 'n', 'y') + + while True: + ans = input(prompt) + if not ans: + return resp + if ans not in ['y', 'Y', 'n', 'N']: + print('please enter y or n.') + continue + if ans == 'y' or ans == 'Y': + return True + if ans == 'n' or ans == 'N': + return False + + +def determine_stale_dashboards(active_projects, client): + """Determines dashboards that have no reference to active projects. + + Args: + active_projects: Set of active projects + client: TeamscaleClient + + Returns: + Set of dashboard ids that do not refer to any active project. + """ + stale_dashboards=[] + for dashboard in client.get_all_dashboard_details(): + dashboard_name = '%s/%s' % (dashboard['owner'], dashboard['name']) + referenced_projects = set(re.findall('"project": "([a-zA-Z0-9_]+)"', dashboard['descriptorJSON'])) + if active_projects.isdisjoint(referenced_projects): + print("Dashboard %s only references projects %s (none of which is active anymore)" + % (dashboard_name, referenced_projects)) + stale_dashboards.append(dashboard_name) + return stale_dashboards + + +def main(): + client = TeamscaleClient(TEAMSCALE_URL, USERNAME, ACCESS_TOKEN, "") + active_projects = set([x.id for x in client.get_projects()]) + print('active project IDs:', active_projects) + + dashboards_to_be_removed = determine_stale_dashboards(active_projects, client) + + for dashboard in dashboards_to_be_removed: + if confirm('really delete dashboard %s ?'%dashboard): + print(client.delete_dashboard(dashboard)) + + +if __name__ == '__main__': + main() diff --git a/teamscale_client/client.py b/teamscale_client/client.py index e490eed..71bab6b 100644 --- a/teamscale_client/client.py +++ b/teamscale_client/client.py @@ -721,3 +721,26 @@ def get_architectures(self): if response.status_code != 200: raise ServiceError("ERROR: GET {url}: {r.status_code}:{r.text}".format(url=service_url, r=response)) return [architecture_overview['uniformPath'] for architecture_overview in response.json()] + + def get_all_dashboard_details(self): + """Returns all dashboards with detail info. + + Returns: + list of dashboard objects + """ + service_url = self.get_global_service_url("dashboards") + + response = self.get(service_url, parameters={'detail':True}) + + if response.status_code != 200: + raise ServiceError("ERROR: GET {url}: {r.status_code}:{r.text}".format(url=service_url, r=response)) + return json.loads(response.content) + + def delete_dashboard(self, dashboard_name): + """Deletes the dashboard with the given name + + Returns: + response + """ + service_url = self.get_global_service_url("dashboards") + return self.delete(service_url+ dashboard_name) diff --git a/tests/teamscale_client_test.py b/tests/teamscale_client_test.py index 57a6fef..47f54f9 100644 --- a/tests/teamscale_client_test.py +++ b/tests/teamscale_client_test.py @@ -214,4 +214,20 @@ def test_add_issue_metric(): responses.add(responses.PUT, get_project_service_mock('issue-metrics'), body='{"message": "success"}', status=200) get_client().add_issue_metric("example/foo", "instate(status=YELLOW) > 2d") - assert "YELLOW" in responses.calls[1].request.body.decode() \ No newline at end of file + assert "YELLOW" in responses.calls[1].request.body.decode() + +@responses.activate +def test_get_all_dashboard_details(): + """Tests query of all dashboard details""" + responses.add(responses.GET, get_global_service_mock('dashboards'), + body='[{"name":"new dashboard","owner":"gib","descriptorJSON":"{\\"widgets\\":[{\\"widget-id\\":\\"commit-chart\\",\\"position\\":{\\"x\\":0,\\"y\\":0,\\"width\\":12,\\"height\\":6},\\"Title\\":\\"Commit Chart\\",\\"Path\\":{\\"project\\":\\"myProject\\",\\"path\\":\\"\\",\\"hiddenInWidgetTitle\\":false},\\"Trend\\":{\\"type\\":\\"timespan\\",\\"value\\":0},\\"Zooming\\":true,\\"Hide Y-Axes\\":false,\\"Baselines\\":true,\\"Action menu\\":false,\\"Min. commits per developer\\":0,\\"Order\\":\\"by time\\"}]}","author":"gib","comment":"","canRead":false,"canWrite":false}]', status=200) + dashboards = get_client().get_all_dashboard_details() + assert dashboards[0]['name'] == 'new dashboard' + +@responses.activate +def test_delete_dashboard(): + """Test deletion of dashboards""" + responses.add(responses.DELETE, get_global_service_mock('dashboards'), + body=SUCCESS, status=200) + resp = get_client().delete_dashboard("some dashboard name") + assert resp.text == SUCCESS