Skip to content
This repository has been archived by the owner on Jan 29, 2020. It is now read-only.

Commit

Permalink
Merge pull request #4 from godaddy/issue1
Browse files Browse the repository at this point in the history
API Changes and CO Timestamp support
Fixes #2
  • Loading branch information
tarkatronic authored Jan 29, 2018
2 parents 6b7b805 + 4c69e29 commit b2e4732
Show file tree
Hide file tree
Showing 6 changed files with 284 additions and 70 deletions.
9 changes: 3 additions & 6 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ Configuration
* ``SNOW_ASSIGNMENT_GROUP`` (Optional) - The group to which the tickets should be assigned.
If this is not provided, each call to create the tickets should be provided with an `assignment_group` argument.
See the API documentation for more details
* ``SNOW_DEFAULT_CHANGE_TYPE`` (Optional) - Default Change Request Type. If not provided,
`standard` will considered as the default type.

Usage
=====
Expand All @@ -44,14 +46,9 @@ Creation

* ``title`` - The title of the change request
* ``description`` - The description of the change request
* ``co_type`` (Optional) - One of the following:

* ``Automated`` (Default)
* ``Manual``
* ``Emergency``

* ``assignment_group`` - The group to which the change request is to be assigned.
This is **optional** if ``SNOW_ASSIGNMENT_GROUP`` django settings is available, else, it is **mandatory**
* ``payload`` (Optional) - The payload for creating the Change Request.

**Returns**

Expand Down
73 changes: 51 additions & 22 deletions django_snow/helpers/snow_request_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import pysnow
from django.conf import settings
from django.utils import timezone
from requests.exceptions import HTTPError

from ..models import ChangeRequest
from .exceptions import ChangeRequestException
Expand All @@ -17,31 +19,40 @@ class ChangeRequestHandler:

group_guid_dict = {}

# Service Now table name
CHANGE_REQUEST_TABLE_NAME = 'change_request'
USER_GROUP_TABLE_NAME = 'sys_user_group'
# Service Now table REST endpoints
CHANGE_REQUEST_TABLE_PATH = '/table/change_request'
USER_GROUP_TABLE_PATH = '/table/sys_user_group'

def __init__(self):
self._client = None
self.snow_instance = settings.SNOW_INSTANCE
self.snow_api_user = settings.SNOW_API_USER
self.snow_api_pass = settings.SNOW_API_PASS
self.snow_assignment_group = settings.SNOW_ASSIGNMENT_GROUP
self.snow_assignment_group = getattr(settings, 'SNOW_ASSIGNMENT_GROUP', None)
self.snow_default_cr_type = getattr(settings, 'SNOW_DEFAULT_CHANGE_TYPE', 'standard')

def create_change_request(self, title, description, co_type='Automated', assignment_group=None):
def create_change_request(self, title, description, assignment_group=None, payload=None):
"""
Create a change request with the given payload.
"""
client = self._get_client()
assignment_group_guid = self._get_snow_group_guid(assignment_group or self.snow_assignment_group)
result = client.insert(
table=self.CHANGE_REQUEST_TABLE_NAME,
payload={
'short_description': title,
'state': ChangeRequest.TICKET_STATE_OPEN,
'description': description,
'type': co_type,
'assignment_group': assignment_group_guid
}
)

change_requests = client.resource(api_path=self.CHANGE_REQUEST_TABLE_PATH)
payload = payload or {}
payload['short_description'] = title
payload['description'] = description

if 'type' not in payload:
payload['type'] = self.snow_default_cr_type
if 'assignment_group' not in payload:
payload['assignment_group'] = self.get_snow_group_guid(assignment_group or self.snow_assignment_group)

try:
result = change_requests.create(payload=payload)
except HTTPError as e:
logger.error('Could not create change request due to %s', e.response.text)
raise ChangeRequestException('Could not create change request due to %s.' % e.response.text)

# This piece of code is for legacy SNow instances. (probably Geneva and before it)
if 'error' in result:
logger.error('Could not create change request due to %s', result['error'])
raise ChangeRequestException('Could not create change request due to %s' % result['error'])
Expand All @@ -61,6 +72,7 @@ def close_change_request(self, change_request):
"""Mark the change request as completed."""

payload = {'state': ChangeRequest.TICKET_STATE_COMPLETE}
change_request.closed_time = timezone.now()
self.update_change_request(change_request, payload)

def close_change_request_with_error(self, change_request, payload):
Expand All @@ -76,6 +88,7 @@ def close_change_request_with_error(self, change_request, payload):
:type payload: dict
"""
payload['state'] = ChangeRequest.TICKET_STATE_COMPLETE_WITH_ERRORS
change_request.closed_time = timezone.now()
self.update_change_request(change_request, payload)

def update_change_request(self, change_request, payload):
Expand All @@ -94,14 +107,23 @@ def update_change_request(self, change_request, payload):
client = self._get_client()

# Get the record and update it
record = client.query(table=self.CHANGE_REQUEST_TABLE_NAME, query={'sys_id': change_request.sys_id.hex})
change_requests = client.resource(api_path=self.CHANGE_REQUEST_TABLE_PATH)

result = record.update(payload=payload)
try:
result = change_requests.update(query={'sys_id': change_request.sys_id.hex}, payload=payload)
except HTTPError as e:
logger.error('Could not update change request due to %s', e.response.text)
raise ChangeRequestException('Could not update change request due to %s' % e.response.text)

# This piece of code is for legacy SNow instances. (probably Geneva and before it)
if 'error' in result:
logger.error('Could not update change request due to %s', result['error'])
raise ChangeRequestException('Could not update change request due to %s' % result['error'])

change_request.state = result['state']
change_request.title = result['short_description']
change_request.description = result['description']
change_request.assignment_group_guid = result['assignment_group']['value']
change_request.save()

return result
Expand All @@ -113,15 +135,22 @@ def _get_client(self):
)
return self._client

def _get_snow_group_guid(self, group_name):
def get_snow_group_guid(self, group_name):
"""
Get the SNow Group's GUID from the Group Name
"""

if group_name not in self.group_guid_dict:
client = self._get_client()
query = client.query(table=self.USER_GROUP_TABLE_NAME, query={'name': group_name})
result = query.get_one()
user_groups = client.resource(api_path=self.USER_GROUP_TABLE_PATH)
response = user_groups.get(query={'name': group_name})
result = response.one()
self.group_guid_dict[group_name] = result['sys_id']

return self.group_guid_dict[group_name]

def clear_group_guid_cache(self):
"""
Clear the SNow Group Name - GUID cache.
"""
self.group_guid_dict.clear()
28 changes: 28 additions & 0 deletions django_snow/migrations/0002_changemgmt_add_createtime_closetime.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.5 on 2017-12-21 22:57
from __future__ import unicode_literals

from django.db import migrations, models
import django.utils.timezone


class Migration(migrations.Migration):

dependencies = [
('django_snow', '0001_initial'),
]

# TODO state field must be altered to include the latest changes (once finalized).
operations = [
migrations.AddField(
model_name='changerequest',
name='closed_time',
field=models.DateTimeField(help_text='Timestamp when the Change Request was closed', null=True),
),
migrations.AddField(
model_name='changerequest',
name='created_time',
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now, help_text='Timestamp when the Change Request was created'),
preserve_default=False,
),
]
20 changes: 18 additions & 2 deletions django_snow/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ class ChangeRequest(models.Model):
SNow Change Request Model Class.
"""

# TODO: Review the states to be included by default. Some are used only in legacy instances (Geneva and before),
# and some are used only in later instances.
# https://docs.servicenow.com/bundle/kingston-it-service-management/page/product/change-management/task/state-model-activate-tasks.html

# The state of the Change Request
TICKET_STATE_OPEN = '1'
TICKET_STATE_IN_PROGRESS = '2'
Expand Down Expand Up @@ -47,9 +51,21 @@ class ChangeRequest(models.Model):
)

state = models.CharField(
max_length=max([len(x[0]) for x in TICKET_STATE_CHOICES]),
max_length=3, # TODO: Review this decision.
choices=TICKET_STATE_CHOICES,
help_text='The current state the the change order is in.'
help_text='The current state the change order is in.'
)

# The time at which the Change Request was created.
created_time = models.DateTimeField(
auto_now_add=True,
help_text='Timestamp when the Change Request was created'
)

# The time at which the Change Request was closed.
closed_time = models.DateTimeField(
null=True,
help_text='Timestamp when the Change Request was closed'
)

def __str__(self):
Expand Down
5 changes: 4 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@
download_url='https://github.com/godaddy/django-snow/archive/master.tar.gz',
install_requires=[
'Django>=1.8',
'pysnow'
'pysnow>=0.6.4',
],
tests_require=[
'six',
],

classifiers=[
Expand Down
Loading

0 comments on commit b2e4732

Please sign in to comment.