Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge pull request #279 from anasouma/TicketingSupport

Ticketing support
  • Loading branch information...
commit d8e4d7d025af86b9f29987c7cb2500c4260d8a47 2 parents 4e2ed44 + 11538ad
@briancline briancline authored
View
275 SoftLayer/CLI/modules/ticket.py
@@ -0,0 +1,275 @@
+"""
+usage: sl ticket [<command>] [<args>...] [options]
+
+Manages account tickets
+
+The available commands are:
+ create Create a new ticket
+ detail Output details about an ticket
+ list List tickets
+ update Update an existing ticket
+ subjects List the subject IDs that can be used for ticket creation
+ summary Give summary info about tickets
+"""
+# :license: MIT, see LICENSE for more details.
+
+import textwrap
+import tempfile
+import os
+from subprocess import call
+
+from SoftLayer import TicketManager
+from SoftLayer.CLI import (CLIRunnable, Table, resolve_id, NestedDict,
+ KeyValueTable)
+
+TEMPLATE_MSG = "***** SoftLayer Ticket Content ******"
+
+
+def wrap_string(input_str):
+ # utility method to wrap the content of the ticket,
+ # as it can make the output messy
+ return textwrap.wrap(input_str, 80)
+
+
+def get_ticket_results(mgr, ticket_id, update_count=1):
+ """ Get output about a ticket
+
+ :param integer id: the ticket ID
+ :param integer update_count: number of entries to retrieve from ticket
+ :returns: a KeyValue table containing the details of the ticket
+
+ """
+ result = mgr.get_ticket(ticket_id)
+ result = NestedDict(result)
+
+ t = KeyValueTable(['Name', 'Value'])
+ t.align['Name'] = 'r'
+ t.align['Value'] = 'l'
+
+ t.add_row(['id', result['id']])
+ t.add_row(['title', result['title']])
+ if result['assignedUser']:
+ t.add_row(['assignedUser',
+ "%s %s" % (result['assignedUser']['firstName'],
+ result['assignedUser']['lastName'])])
+ t.add_row(['createDate', result['createDate']])
+ t.add_row(['lastEditDate', result['lastEditDate']])
+
+ totalUpdates = len(result['updates'])
+ count = min(totalUpdates, update_count)
+ for index in range(0, count):
+ i = totalUpdates - index
+ update = wrap_string(result['updates'][i - 1]['entry'])
+ t.add_row(['Update %s' % (i), update])
+
+ return t
+
+
+def open_editor(beg_msg, ending_msg=None):
+ """
+
+ :param beg_msg: generic msg to be appended at the end of the file
+ :param ending_msg: placeholder msg to append at the end of the file,
+ like filesystem info, etc, not being used now
+ :returns: the content the user has entered
+
+ """
+
+ # Let's get the default EDITOR of the environment,
+ # use nano if none is specified
+ editor = os.environ.get('EDITOR', 'nano')
+
+ with tempfile.NamedTemporaryFile(suffix=".tmp") as tfile:
+ # populate the file with the baked messages
+ tfile.write("\n")
+ tfile.write(beg_msg)
+ if ending_msg:
+ tfile.write("\n")
+ tfile.write(ending_msg)
+ # flush the file and open it for editing
+ tfile.flush()
+ call([editor, tfile.name])
+ tfile.seek(0)
+ data = tfile.read()
+ return data
+
+ return
+
+
+class ListTickets(CLIRunnable):
+ """
+usage: sl ticket list [--open | --closed]
+
+List tickets
+
+Options:
+ --open display only open tickets
+ --closed display only closed tickets display all if none specified
+
+"""
+ action = 'list'
+
+ def execute(self, args):
+ ticket_mgr = TicketManager(self.client)
+
+ tickets = ticket_mgr.list_tickets(
+ open_status=args.get('--open'),
+ closed_status=args.get('--closed'))
+
+ t = Table(['id', 'assigned user', 'title',
+ 'creation date', 'last edit date'])
+
+ for ticket in tickets:
+ if ticket['assignedUser']:
+ t.add_row([
+ ticket['id'],
+ "%s %s" % (ticket['assignedUser']['firstName'],
+ ticket['assignedUser']['lastName']),
+ wrap_string(ticket['title']),
+ ticket['createDate'],
+ ticket['lastEditDate']
+ ])
+ else:
+ t.add_row([
+ ticket['id'],
+ 'N/A',
+ wrap_string(ticket['title']),
+ ticket['createDate'],
+ ticket['lastEditDate']
+ ])
+
+ return t
+
+
+class ListSubjectsTickets(CLIRunnable):
+ """
+usage: sl ticket subjects
+
+List Subject IDs for ticket creation
+
+"""
+ action = 'subjects'
+
+ def execute(self, args):
+ ticket_mgr = TicketManager(self.client)
+
+ t = Table(['id', 'subject'])
+ for subject in ticket_mgr.list_subjects():
+ t.add_row([
+ subject['id'],
+ subject['name']
+ ])
+ return t
+
+
+class UpdateTicket(CLIRunnable):
+ """
+usage: sl ticket update <identifier> [options]
+
+Updates a certain ticket
+
+Options:
+ --body=BODY The entry that will be appended to the ticket
+
+"""
+ action = 'update'
+ options = ['--body']
+
+ def execute(self, args):
+ mgr = TicketManager(self.client)
+
+ ticket_id = resolve_id(
+ mgr.resolve_ids, args.get('<identifier>'), 'ticket')
+
+ body = args.get('--body')
+ if body is None:
+ body = open_editor(beg_msg=TEMPLATE_MSG)
+
+ mgr.update_ticket(ticket_id=ticket_id, body=body)
+ return "Ticket Updated!"
+
+
+class TicketsSummary(CLIRunnable):
+ """
+usage: sl ticket summary
+
+Give summary info about tickets
+
+"""
+ action = 'summary'
+
+ def execute(self, args):
+ account = self.client['Account']
+ mask = ('mask[openTicketCount, closedTicketCount, '
+ 'openBillingTicketCount, openOtherTicketCount, '
+ 'openSalesTicketCount, openSupportTicketCount, '
+ 'openAccountingTicketCount]')
+ accountObject = account.getObject(mask=mask)
+ t = Table(['Status', 'count'])
+
+ nested = Table(['Type', 'count'])
+ nested.add_row(['Accounting',
+ accountObject['openAccountingTicketCount']])
+ nested.add_row(['Billing', accountObject['openBillingTicketCount']])
+ nested.add_row(['Sales', accountObject['openSalesTicketCount']])
+ nested.add_row(['Support', accountObject['openSupportTicketCount']])
+ nested.add_row(['Other', accountObject['openOtherTicketCount']])
+ nested.add_row(['Total', accountObject['openTicketCount']])
+ t.add_row(['Open', nested])
+ t.add_row(['Closed', accountObject['closedTicketCount']])
+
+ return t
+
+
+class TicketDetails(CLIRunnable):
+ """
+usage: sl ticket detail <identifier> [options]
+
+Get details for a ticket
+
+Options:
+ --updateCount=X Show X count of updates [default: 1]
+"""
+ action = 'detail'
+
+ def execute(self, args):
+ mgr = TicketManager(self.client)
+
+ ticket_id = resolve_id(
+ mgr.resolve_ids, args.get('<identifier>'), 'ticket')
+
+ count = args.get('--updateCount')
+ return get_ticket_results(mgr, ticket_id, int(count))
+
+
+class CreateTicket(CLIRunnable):
+ """
+usage: sl ticket create --title=TITLE --subject=ID [options]
+
+Create a support ticket.
+
+Required:
+ --title=TITLE The title of the ticket
+ --subject=ID The id of the subject to use for the ticket,
+ issue 'sl ticket subjects' to get the list
+
+Optional:
+ --body=BODY the body text to attach to the ticket,
+ an editor will be opened if body is not provided
+"""
+ action = 'create'
+ required_params = ['--title, --subject']
+
+ def execute(self, args):
+ mgr = TicketManager(self.client)
+ if args.get('--title') is "":
+ return 'Please provide a valid title'
+ body = args.get('--body')
+ if body is None:
+ body = open_editor(beg_msg=TEMPLATE_MSG)
+
+ createdTicket = mgr.create_ticket(
+ title=args.get('--title'),
+ body=body,
+ subject=args.get('--subject'))
+ return get_ticket_results(mgr, createdTicket['id'], 1)
View
3  SoftLayer/managers/__init__.py
@@ -17,7 +17,8 @@
from SoftLayer.managers.network import NetworkManager
from SoftLayer.managers.sshkey import SshKeyManager
from SoftLayer.managers.ssl import SSLManager
+from SoftLayer.managers.ticket import TicketManager
__all__ = ['CCIManager', 'DNSManager', 'FirewallManager', 'HardwareManager',
'ImageManager', 'MessagingManager', 'MetadataManager',
- 'NetworkManager', 'SshKeyManager', 'SSLManager']
+ 'NetworkManager', 'SshKeyManager', 'SSLManager', 'TicketManager']
View
85 SoftLayer/managers/ticket.py
@@ -0,0 +1,85 @@
+"""
+ SoftLayer.ticket
+ ~~~~~~~~~~~~~~~
+ Ticket Manager/helpers
+
+ :license: MIT, see LICENSE for more details.
+"""
+
+from SoftLayer.utils import IdentifierMixin
+
+
+class TicketManager(IdentifierMixin, object):
+ """
+ Manages account Tickets
+
+ :param SoftLayer.API.Client client: an API client instance
+ """
+
+ def __init__(self, client):
+ self.client = client
+ self.account = self.client['Account']
+ self.ticket = self.client['Ticket']
+
+ def list_tickets(self, open_status=True, closed_status=True):
+ """ List all tickets
+
+ :param boolean open_status: include open tickets
+ :param boolean closed_status: include closed tickets
+ """
+ mask = ('mask[id, title, assignedUser[firstName, lastName],'
+ 'createDate,lastEditDate,accountId]')
+
+ call = 'getTickets'
+ if not all([open_status, closed_status]):
+ if open_status:
+ call = 'getOpenTickets'
+ elif closed_status:
+ call = 'getClosedTickets'
+
+ func = getattr(self.account, call)
+ return func(mask=mask)
+
+ def list_subjects(self):
+ """ List all tickets"""
+ return self.client['Ticket_Subject'].getAllObjects()
+
+ def get_ticket(self, ticket_id):
+ """ Get details about a ticket
+
+ :param integer id: the ticket ID
+ :returns: A dictionary containing a large amount of information about
+ the specified ticket.
+
+ """
+ mask = ('mask[id, title, assignedUser[firstName, lastName],'
+ 'createDate,lastEditDate,updates[entry],updateCount]')
+ return self.ticket.getObject(id=ticket_id, mask=mask)
+
+ def create_ticket(self, title=None, body=None, subject=None):
+ """ Create a new ticket
+
+ :param string title: title for the new ticket
+ :param string body: body for the new ticket
+ :param integer subject: id of the subject to be assigned to the ticket
+ """
+
+ currentUser = self.account.getCurrentUser()
+ new_ticket = {
+ 'subjectId': subject,
+ 'contents': body,
+ 'assignedUserId': currentUser['id'],
+ 'title': title,
+ }
+ created_ticket = self.ticket.createStandardTicket(new_ticket, body)
+ return created_ticket
+
+ def update_ticket(self, ticket_id=None, body=None):
+ """ Update a ticket
+
+ :param integer ticket_id: the id of the ticket to update
+ :param string body: entry to update in the ticket
+ """
+
+ ticket = self.ticket.getObject(id=ticket_id)
+ return self.ticket.edit(ticket, body, id=ticket_id)
View
54 SoftLayer/tests/fixtures/Account.py
@@ -201,3 +201,57 @@
'id': 1234}]
getExpiredSecurityCertificates = getSecurityCertificates
getValidSecurityCertificates = getSecurityCertificates
+
+getTickets = [
+ {
+ "accountId": 1234,
+ "assignedUserId": 12345,
+ "createDate": "2013-08-01T14:14:04-07:00",
+ "id": 100,
+ "lastEditDate": "2013-08-01T14:16:47-07:00",
+ "lastEditType": "AUTO",
+ "modifyDate": "2013-08-01T14:16:47-07:00",
+ "status": {
+ "id": 1002,
+ "name": "Closed"
+ },
+ "statusId": 1002,
+ "title": "Cloud Instance Cancellation - 08/01/13"
+ },
+ {
+ "accountId": 1234,
+ "assignedUserId": 12345,
+ "createDate": "2013-08-01T14:14:04-07:00",
+ "id": 101,
+ "lastEditDate": "2013-08-01T14:16:47-07:00",
+ "lastEditType": "AUTO",
+ "modifyDate": "2013-08-01T14:16:47-07:00",
+ "status": {
+ "id": 1002,
+ "name": "Closed"
+ },
+ "statusId": 1002,
+ "title": "Cloud Instance Cancellation - 08/01/13"
+ },
+ {
+ "accountId": 1234,
+ "assignedUserId": 12345,
+ "createDate": "2014-03-03T09:44:01-08:00",
+ "id": 102,
+ "lastEditDate": "2013-08-01T14:16:47-07:00",
+ "lastEditType": "AUTO",
+ "modifyDate": "2014-03-03T09:44:03-08:00",
+ "status": {
+ "id": 1001,
+ "name": "Open"
+ },
+ "statusId": 1001,
+ "title": "Cloud Instance Cancellation - 08/01/13"
+ }]
+
+getOpenTickets = [ticket for ticket in getTickets
+ if ticket['statusId'] == 1001]
+getClosedTickets = [ticket for ticket in getTickets
+ if ticket['statusId'] == 1002]
+
+getCurrentUser = {"id": 12345}
View
24 SoftLayer/tests/fixtures/Ticket.py
@@ -1 +1,25 @@
createCancelServerTicket = {'id': 1234, 'title': 'Server Cancellation Request'}
+getObject = {
+ "accountId": 1234,
+ "assignedUserId": 12345,
+ "createDate": "2013-08-01T14:14:04-07:00",
+ "id": 100,
+ "lastEditDate": "2013-08-01T14:16:47-07:00",
+ "lastEditType": "AUTO",
+ "modifyDate": "2013-08-01T14:16:47-07:00",
+ "status": {
+ "id": 1002,
+ "name": "Closed"
+ },
+ "statusId": 1002,
+ "title": "Cloud Instance Cancellation - 08/01/13"
+}
+
+createStandardTicket = {
+ "assignedUserId": 12345,
+ "id": 100,
+ "contents": "body",
+ "subjectId": 1004,
+ "title": "Cloud Instance Cancellation - 08/01/13"
+}
+edit = True
View
22 SoftLayer/tests/fixtures/Ticket_Subject.py
@@ -0,0 +1,22 @@
+getAllObjects = [
+ {
+ "id": 1001,
+ "name": "Accounting Request"
+ },
+ {
+ "id": 1002,
+ "name": "Sales Request"
+ },
+ {
+ "id": 1003,
+ "name": "Reboots and Console Access"
+ },
+ {
+ "id": 1004,
+ "name": "DNS Request"
+ },
+ {
+ "id": 1005,
+ "name": "Hardware Issue"
+ }
+]
View
92 SoftLayer/tests/managers/ticket_tests.py
@@ -0,0 +1,92 @@
+"""
+ SoftLayer.tests.managers.ticket_tests
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ :license: MIT, see LICENSE for more details.
+"""
+from SoftLayer import TicketManager
+from SoftLayer.tests import unittest, FixtureClient
+from SoftLayer.tests.fixtures import Ticket
+from mock import ANY, call
+
+
+class TicketTests(unittest.TestCase):
+
+ def setUp(self):
+ self.client = FixtureClient()
+ self.ticket = TicketManager(self.client)
+
+ def test_list_tickets(self):
+ mcall = call(mask=ANY)
+ service = self.client['Account']
+
+ list_expected_ids = [100, 101, 102]
+ open_expected_ids = [102]
+ closed_expected_ids = [100, 101]
+
+ results = self.ticket.list_tickets()
+ service.getTickets.assert_has_calls(mcall)
+ for result in results:
+ self.assertIn(result['id'], list_expected_ids)
+
+ results = self.ticket.list_tickets(open_status=True, closed_status=True)
+ service.getTickets.assert_has_calls(mcall)
+ for result in results:
+ self.assertIn(result['id'], list_expected_ids)
+
+ results = self.ticket.list_tickets(open_status=True, closed_status=False)
+ for result in results:
+ self.assertIn(result['id'], open_expected_ids)
+
+ results = self.ticket.list_tickets(open_status=False, closed_status=True)
+ for result in results:
+ self.assertIn(result['id'], closed_expected_ids)
+
+ def test_list_subjects(self):
+ list_expected_ids = [1001, 1002, 1003, 1004, 1005]
+
+ results = self.ticket.list_subjects()
+ for result in results:
+ self.assertIn(result['id'], list_expected_ids)
+
+ def test_get_instance(self):
+ result = self.ticket.get_ticket(100)
+ self.client['Ticket'].getObject.assert_called_once_with(
+ id=100, mask=ANY)
+ self.assertEqual(Ticket.getObject, result)
+
+ def test_create_ticket(self):
+ self.ticket.create_ticket(
+ title="Cloud Instance Cancellation - 08/01/13",
+ body="body",
+ subject=1004)
+ self.client['Ticket'].createStandardTicket.assert_called_once_with(
+ {"assignedUserId": 12345,
+ "contents": "body",
+ "subjectId": 1004,
+ "title": "Cloud Instance Cancellation - 08/01/13"}, "body")
+
+ def test_update_ticket(self):
+ # Test editing user data
+ service = self.client['Ticket']
+
+ # test a full update
+ self.ticket.update_ticket(100, body='Update1')
+ service.edit.assert_called_once_with(
+ {
+ "accountId": 1234,
+ "assignedUserId": 12345,
+ "createDate": "2013-08-01T14:14:04-07:00",
+ "id": 100,
+ "lastEditDate": "2013-08-01T14:16:47-07:00",
+ "lastEditType": "AUTO",
+ "modifyDate": "2013-08-01T14:16:47-07:00",
+ "status": {
+ "id": 1002,
+ "name": "Closed"
+ },
+ "statusId": 1002,
+ "title": "Cloud Instance Cancellation - 08/01/13"
+ },
+ 'Update1',
+ id=100)
Please sign in to comment.
Something went wrong with that request. Please try again.