diff --git a/.envs/sample.env b/.envs/sample.env index 10bb6c1c34..3380015d29 100644 --- a/.envs/sample.env +++ b/.envs/sample.env @@ -78,16 +78,15 @@ S3_POLICY_DOCUMENT_TEMPLATE_BASE64=e30= S3_PERMISSIONS_BOUNDARY_ARN=my-arn S3_ROLE_PREFIX=my-prefix -ZENDESK_EMAIL=test@test.com -# ZENDESK_SUBDOMAIN just requires the subdomain part not the full url -# i.e. for subdomain.zendesk.com the value should be subdomain -ZENDESK_SUBDOMAIN=subdomain -ZENDESK_TOKEN=abcd - -# ZENDESK_SERVICE_FIELD_ID is a numeric value for a custom field within zendesk which is -# set to the ZENDESK_SERVIE_FIELD_VALUE when requesting access to datasets zendesk.py -ZENDESK_SERVICE_FIELD_ID=numeric_field_id -ZENDESK_SERVICE_FIELD_VALUE=field_value +# HELP_DESK_SERVICE_FIELD_ID is a numeric value for a custom field within the help desk which is +# set to the HELPDESK_SERVICE_FIELD_VALUE when requesting access to datasets help_desk.py +HELP_DESK_EMAIL=test@test.com +HELP_DESK_SERVICE_FIELD_ID=numeric_field_id +HELP_DESK_SERVICE_FIELD_VALUE=field_value + +# helpdesk abstraction +HELP_DESK_INTERFACE=helpdesk_client.interfaces.HelpDeskStubbed +HELP_DESK_CREDS=xxx NOTIFY_API_KEY=notify-token FERNET_EMAIL_TOKEN_KEY=generate-using-fernet-generate-key diff --git a/.envs/test.env b/.envs/test.env index f21ff488d1..759cdb4be8 100644 --- a/.envs/test.env +++ b/.envs/test.env @@ -54,11 +54,9 @@ AWS_ECR_ENDPOINT_URL=http://api.ecr.my-region-1.amazonaws.com:8008/ PROMETHEUS_DOMAIN=some.domain.com METRICS_SERVICE_DISCOVERY_BASIC_AUTH_USER=user METRICS_SERVICE_DISCOVERY_BASIC_AUTH_PASSWORD=password -ZENDESK_EMAIL=test@test.com -ZENDESK_SUBDOMAIN=subdomain -ZENDESK_TOKEN=abcd -ZENDESK_SERVICE_FIELD_ID=654321 -ZENDESK_SERVICE_FIELD_VALUE=field_value +HELP_DESK_EMAIL=test@test.com +HELP_DESK_SERVICE_FIELD_ID=654321 +HELP_DESK_SERVICE_FIELD_VALUE=field_value S3_ASSUME_ROLE_POLICY_DOCUMENT_BASE64=e30= S3_POLICY_NAME=my-policy S3_POLICY_DOCUMENT_TEMPLATE_BASE64=e30= @@ -67,6 +65,9 @@ S3_ROLE_PREFIX=my-prefix ALLOWED_HOSTS='dataworkspace.test' +HELP_DESK_INTERFACE=helpdesk_client.interfaces.HelpDeskStubbed + + UPLOADS_BUCKET=an-upload-bucket MIRROR_REMOTE_ROOT=http://127.0.0.1:8006/some-remote-folder/ diff --git a/Makefile b/Makefile index 0dde95418a..75eb23c6e3 100644 --- a/Makefile +++ b/Makefile @@ -37,8 +37,7 @@ docker-test-unit: docker-build .PHONY: docker-test-integration docker-test-integration: docker-build - docker-compose -f docker-compose-test.yml -p data-workspace-test run data-workspace-test pytest test/ - + docker-compose -f docker-compose-test.yml -p data-workspace-test run -e DJANGO_SETTINGS_MODULE=dataworkspace.settings.integration_tests data-workspace-test pytest test/ .PHONY: docker-test docker-test: docker-test-integration docker-test-unit diff --git a/dataworkspace/dataworkspace/apps/applications/views.py b/dataworkspace/dataworkspace/apps/applications/views.py index 148763c055..d83b246f0e 100644 --- a/dataworkspace/dataworkspace/apps/applications/views.py +++ b/dataworkspace/dataworkspace/apps/applications/views.py @@ -83,7 +83,7 @@ from dataworkspace.apps.eventlog.models import EventLog from dataworkspace.apps.eventlog.utils import log_event from dataworkspace.notify import decrypt_token, send_email -from dataworkspace.zendesk import update_zendesk_ticket +from dataworkspace.help_desk import update_helpdesk_ticket TOOL_LOADING_MESSAGES = [ { @@ -726,7 +726,7 @@ def error(email_address_error): return error(f"{user.get_full_name()} already has access") if email_address == token_data.get("email", "").strip().lower(): - update_zendesk_ticket( + update_helpdesk_ticket( token_data["ticket"], comment=f"Access granted by {request.user.email}", status="solved", diff --git a/dataworkspace/dataworkspace/apps/core/views.py b/dataworkspace/dataworkspace/apps/core/views.py index 7567604cc3..47c68a5e2b 100644 --- a/dataworkspace/dataworkspace/apps/core/views.py +++ b/dataworkspace/dataworkspace/apps/core/views.py @@ -43,7 +43,7 @@ ) from dataworkspace.apps.eventlog.models import EventLog from dataworkspace.apps.eventlog.utils import log_event -from dataworkspace.zendesk import create_support_request +from dataworkspace.help_desk import create_support_request logger = logging.getLogger("app") @@ -101,7 +101,7 @@ class SupportView(FormView): form_class = SupportForm template_name = "core/support.html" - ZENDESK_TAGS = {"data-request": "data_request"} + HELP_DESK_TAGS = {"data-request": "data_request"} def get_context_data(self, **kwargs): ctx = super().get_context_data() @@ -122,7 +122,7 @@ def form_valid(self, form): if cleaned["support_type"] == form.SupportTypes.TECH_SUPPORT: return HttpResponseRedirect(f'{reverse("technical-support")}?email={cleaned["email"]}') - tag = self.ZENDESK_TAGS.get(self.request.GET.get("tag")) + tag = self.HELP_DESK_TAGS.get(self.request.GET.get("tag")) ticket_id = create_support_request( self.request.user, cleaned["email"], cleaned["message"], tag=tag ) diff --git a/dataworkspace/dataworkspace/apps/request_access/migrations/0005_rename_zendesk_reference_number_accessrequest.py b/dataworkspace/dataworkspace/apps/request_access/migrations/0005_rename_zendesk_reference_number_accessrequest.py new file mode 100644 index 0000000000..be1b8bee03 --- /dev/null +++ b/dataworkspace/dataworkspace/apps/request_access/migrations/0005_rename_zendesk_reference_number_accessrequest.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.15 on 2022-09-26 17:38 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("request_access", "0004_alter_accessrequest_training_screenshot"), + ] + + operations = [ + migrations.RenameField( + model_name="accessrequest", + old_name="zendesk_reference_number", + new_name="help_desk_reference_number", + ), + ] diff --git a/dataworkspace/dataworkspace/apps/request_access/models.py b/dataworkspace/dataworkspace/apps/request_access/models.py index 318b94aa43..d0b263f8b9 100644 --- a/dataworkspace/dataworkspace/apps/request_access/models.py +++ b/dataworkspace/dataworkspace/apps/request_access/models.py @@ -31,10 +31,10 @@ class AccessRequest(TimeStampedModel): spss_and_stata = models.BooleanField(default=False, blank=True) line_manager_email_address = models.CharField(max_length=256, null=True) reason_for_spss_and_stata = models.TextField(null=True) - zendesk_reference_number = models.CharField(max_length=256, null=True) + help_desk_reference_number = models.CharField(max_length=256, null=True) def __str__(self): - return f"{self.requester} - Zendesk reference number: {self.zendesk_reference_number}" + return f"{self.requester} - Help desk reference number: {self.help_desk_reference_number}" @property def journey(self): diff --git a/dataworkspace/dataworkspace/apps/request_access/views.py b/dataworkspace/dataworkspace/apps/request_access/views.py index 2538052d8e..6712ea7b43 100644 --- a/dataworkspace/dataworkspace/apps/request_access/views.py +++ b/dataworkspace/dataworkspace/apps/request_access/views.py @@ -18,7 +18,7 @@ ) from dataworkspace.apps.request_access import models -from dataworkspace import zendesk +from dataworkspace import help_desk class DatasetAccessRequest(CreateView): @@ -173,7 +173,7 @@ class AccessRequestConfirmationPage(RequestAccessMixin, DetailView): def get(self, request, *args, **kwargs): access_request = self.get_object() - if not access_request.zendesk_reference_number: + if not access_request.help_desk_reference_number: catalogue_item = ( find_dataset(access_request.catalogue_item_id, self.request.user) if access_request.catalogue_item_id @@ -184,15 +184,15 @@ def get(self, request, *args, **kwargs): isinstance(catalogue_item, VisualisationCatalogueItem) and catalogue_item.visualisation_template is not None ): - access_request.zendesk_reference_number = ( - zendesk.notify_visualisation_access_request( + access_request.help_desk_reference_number = ( + help_desk.notify_visualisation_access_request( request, access_request, catalogue_item, ) ) else: - access_request.zendesk_reference_number = zendesk.create_zendesk_ticket( + access_request.help_desk_reference_number = help_desk.create_help_desk_ticket( request, access_request, catalogue_item, @@ -205,7 +205,7 @@ def get(self, request, *args, **kwargs): EventLog.TYPE_DATASET_ACCESS_REQUEST, catalogue_item, extra={ - "ticket_reference": access_request.zendesk_reference_number, + "ticket_reference": access_request.help_desk_reference_number, }, ) else: @@ -213,7 +213,7 @@ def get(self, request, *args, **kwargs): request.user, EventLog.TYPE_TOOLS_ACCESS_REQUEST, extra={ - "ticket_reference": access_request.zendesk_reference_number, + "ticket_reference": access_request.help_desk_reference_number, }, ) return super().get(request, *args, **kwargs) diff --git a/dataworkspace/dataworkspace/apps/request_data/migrations/0005_rename_zendesk_ticket_id_datarequest_help_desk.py b/dataworkspace/dataworkspace/apps/request_data/migrations/0005_rename_zendesk_ticket_id_datarequest_help_desk.py new file mode 100644 index 0000000000..a0fc84a8d1 --- /dev/null +++ b/dataworkspace/dataworkspace/apps/request_data/migrations/0005_rename_zendesk_ticket_id_datarequest_help_desk.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.15 on 2022-09-26 17:38 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("request_data", "0004_datarequest_data_licence"), + ] + + operations = [ + migrations.RenameField( + model_name="datarequest", + old_name="zendesk_ticket_id", + new_name="help_desk_ticket_id", + ), + ] diff --git a/dataworkspace/dataworkspace/apps/request_data/models.py b/dataworkspace/dataworkspace/apps/request_data/models.py index 1d1a0a95e2..d2f12a9519 100644 --- a/dataworkspace/dataworkspace/apps/request_data/models.py +++ b/dataworkspace/dataworkspace/apps/request_data/models.py @@ -47,4 +47,4 @@ class DataRequest(TimeStampedModel): choices=DataRequestStatus.choices, default=DataRequestStatus.draft, ) - zendesk_ticket_id = models.CharField(max_length=256) + help_desk_ticket_id = models.CharField(max_length=256) diff --git a/dataworkspace/dataworkspace/apps/request_data/views.py b/dataworkspace/dataworkspace/apps/request_data/views.py index 0a8f56d6a2..a7bf496689 100644 --- a/dataworkspace/dataworkspace/apps/request_data/views.py +++ b/dataworkspace/dataworkspace/apps/request_data/views.py @@ -17,7 +17,7 @@ RoleType, DataRequestStatus, ) -from dataworkspace.zendesk import create_support_request # pylint: disable=import-error +from dataworkspace.help_desk import create_support_request # pylint: disable=import-error class RequestData(TemplateView): @@ -249,11 +249,11 @@ def post(self, request, *args, **kwargs): # If they've hacked the URL, some required fields might be blank. try: - obj.clean_fields(exclude=["zendesk_ticket_id"]) + obj.clean_fields(exclude=["help_desk_ticket_id"]) except ValidationError: return HttpResponseBadRequest() - zendesk_message = f""" + help_desk_message = f""" A request for a new dataset on Data Workspace has been submitted. Here are the details: # Request details @@ -288,16 +288,21 @@ def post(self, request, *args, **kwargs): {obj.requester.email} """ + from django.conf import settings + + print("settings:::", flush=True) + print(settings.HELP_DESK_INTERFACE, flush=True) + ticket_id = create_support_request( obj.requester, obj.requester.email, - zendesk_message, + help_desk_message, tag="request-for-data", subject="Request for new dataset on Data Workspace", ) obj.status = DataRequestStatus.submitted - obj.zendesk_ticket_id = ticket_id + obj.help_desk_ticket_id = ticket_id obj.save() return HttpResponseRedirect( diff --git a/dataworkspace/dataworkspace/context_processors.py b/dataworkspace/dataworkspace/context_processors.py index cd0da65be7..aaa143e3b3 100644 --- a/dataworkspace/dataworkspace/context_processors.py +++ b/dataworkspace/dataworkspace/context_processors.py @@ -31,7 +31,7 @@ def common(request): "NOTIFY_ON_MASTER_DATASET_CHANGE_FLAG": settings.NOTIFY_ON_MASTER_DATASET_CHANGE_FLAG, "NOTIFY_ON_DATACUT_CHANGE_FLAG": settings.NOTIFY_ON_DATACUT_CHANGE_FLAG, "NOTIFY_ON_REFERENCE_DATASET_CHANGE_FLAG": settings.NOTIFY_ON_REFERENCE_DATASET_CHANGE_FLAG, - "ZENDESK_EMAIL": settings.ZENDESK_EMAIL, + "HELP_DESK_EMAIL": settings.HELP_DESK_EMAIL, "TEAMS_DATA_WORKSPACE_COMMUNITY_URL": settings.TEAMS_DATA_WORKSPACE_COMMUNITY_URL, "DATA_WORKSPACE_ROADMAP_URL": settings.DATA_WORKSPACE_ROADMAP_URL, "SSO_USER_ID": request.META.get("HTTP_SSO_PROFILE_USER_ID", ""), diff --git a/dataworkspace/dataworkspace/zendesk.py b/dataworkspace/dataworkspace/help_desk.py similarity index 73% rename from dataworkspace/dataworkspace/zendesk.py rename to dataworkspace/dataworkspace/help_desk.py index 06bea9f231..08e671c922 100644 --- a/dataworkspace/dataworkspace/zendesk.py +++ b/dataworkspace/dataworkspace/help_desk.py @@ -3,15 +3,21 @@ from django.conf import settings from django.urls import reverse -from zenpy import Zenpy -from zenpy.lib.api_objects import Ticket, User, Comment, CustomField + +from helpdesk_client import get_helpdesk_interface +from helpdesk_client.interfaces import ( + HelpDeskComment, + HelpDeskCustomField, + HelpDeskTicket, + HelpDeskUser, +) from dataworkspace.notify import generate_token, send_email logger = logging.getLogger("app") -zendesk_service_field_id = settings.ZENDESK_SERVICE_FIELD_ID -zendesk_service_field_value = settings.ZENDESK_SERVICE_FIELD_VALUE +help_desk_service_field_id = settings.HELP_DESK_SERVICE_FIELD_ID +help_desk_service_field_value = settings.HELP_DESK_SERVICE_FIELD_VALUE def get_username(user): @@ -72,13 +78,12 @@ def build_private_comment_text(catalogue_item, approval_url): return private_comment -def create_zendesk_ticket(request, access_request, catalogue_item=None): - client = Zenpy( - subdomain=settings.ZENDESK_SUBDOMAIN, - email=settings.ZENDESK_EMAIL, - token=settings.ZENDESK_TOKEN, - ) +# configure and instantiate the client +helpdesk_interface = get_helpdesk_interface(settings.HELP_DESK_INTERFACE) +helpdesk = helpdesk_interface(credentials=settings.HELP_DESK_CREDS) + +def create_help_desk_ticket(request, access_request, catalogue_item=None): access_request_url = request.build_absolute_uri( reverse("admin:request_access_accessrequest_change", args=(access_request.id,)) ) @@ -99,39 +104,31 @@ def create_zendesk_ticket(request, access_request, catalogue_item=None): subject = f"Access Request for {catalogue_item if catalogue_item else username}" - ticket_audit = client.tickets.create( - Ticket( - subject=subject, - description=ticket_description, - requester=User(email=access_request.requester.email, name=username), - custom_fields=[ - CustomField(id=zendesk_service_field_id, value=zendesk_service_field_value) - ], - ) + helpdesk_ticket = HelpDeskTicket( + subject=subject, + description=ticket_description, + user=HelpDeskUser(full_name=username, email=access_request.requester.email), + custom_fields=[ + HelpDeskCustomField(id=help_desk_service_field_id, value=help_desk_service_field_value) + ], + comment=HelpDeskComment(body=private_comment, public=False), ) - ticket_audit.ticket.comment = Comment(body=private_comment, public=False) - client.tickets.update(ticket_audit.ticket) + ticket_audit = helpdesk.create_ticket(helpdesk_ticket) - return ticket_audit.ticket.id + return ticket_audit.id -def update_zendesk_ticket(ticket_id, comment=None, status=None): - client = Zenpy( - subdomain=settings.ZENDESK_SUBDOMAIN, - email=settings.ZENDESK_EMAIL, - token=settings.ZENDESK_TOKEN, - ) - - ticket = client.tickets(id=ticket_id) - +def update_helpdesk_ticket(ticket_id, comment=None, status=None): if comment: - ticket.comment = Comment(body=comment, public=False) + helpdesk.helpdesk.add_comment( + ticket_id=ticket_id, comment=HelpDeskComment(body=comment, public=False) + ) if status: + ticket = helpdesk.get_ticket(ticket_id=ticket_id) ticket.status = status - - client.tickets.update(ticket) + ticket = helpdesk.update_ticket(ticket=ticket) return ticket @@ -155,7 +152,7 @@ def notify_visualisation_access_request(request, access_request, dataset): Secondary contact: {dataset.secondary_enquiries_contact.email if dataset.secondary_enquiries_contact else 'Not set'} -If access has not been granted to the requestor within 5 working days, this will trigger an update to this Zendesk ticket to resolve the request. +If access has not been granted to the requestor within 5 working days, this will trigger an update to this help desk ticket to resolve the request. """ ticket_reference = create_support_request( @@ -200,20 +197,21 @@ def notify_visualisation_access_request(request, access_request, dataset): def create_support_request(user, email, message, tag=None, subject=None): - client = Zenpy( - subdomain=settings.ZENDESK_SUBDOMAIN, - email=settings.ZENDESK_EMAIL, - token=settings.ZENDESK_TOKEN, - ) - ticket_audit = client.tickets.create( - Ticket( + ticket_audit = helpdesk.create_ticket( + HelpDeskTicket( subject=subject or "Data Workspace Support Request", description=message, - requester=User(email=email, name=user.get_full_name()), - tags=[tag] if tag else None, + user=HelpDeskUser( + full_name=user.get_full_name(), + email=email, + ), custom_fields=[ - CustomField(id=zendesk_service_field_id, value=zendesk_service_field_value) + HelpDeskCustomField( + id=help_desk_service_field_id, value=help_desk_service_field_value + ) ], + tags=[tag] if tag else None, ) ) - return ticket_audit.ticket.id + + return ticket_audit.id diff --git a/dataworkspace/dataworkspace/settings/base.py b/dataworkspace/dataworkspace/settings/base.py index f0aa98f365..886c41fe89 100644 --- a/dataworkspace/dataworkspace/settings/base.py +++ b/dataworkspace/dataworkspace/settings/base.py @@ -282,14 +282,13 @@ def aws_fargate_private_ip(): f"ws://{APPLICATION_ROOT_DOMAIN.split(':')[0]}:3000", ] +HELP_DESK_EMAIL = env.get("HELP_DESK_INTERFACE", "") +HELP_DESK_SERVICE_FIELD_ID = env.get("HELP_DESK_SERVICE_FIELD_ID", "") +HELP_DESK_SERVICE_FIELD_VALUE = env.get("HELP_DESK_SERVICE_FIELD_VALUE", "") -ZENDESK_EMAIL = env["ZENDESK_EMAIL"] -ZENDESK_SUBDOMAIN = env["ZENDESK_SUBDOMAIN"] -ZENDESK_TOKEN = env["ZENDESK_TOKEN"] - -ZENDESK_SERVICE_FIELD_ID = env["ZENDESK_SERVICE_FIELD_ID"] -ZENDESK_SERVICE_FIELD_VALUE = env["ZENDESK_SERVICE_FIELD_VALUE"] - +# helpdesk abstraction +HELP_DESK_INTERFACE = env.get("HELP_DESK_INTERFACE", "") +HELP_DESK_CREDS = env.get("HELP_DESK_CREDS", "") NOTIFY_API_KEY = env["NOTIFY_API_KEY"] FERNET_EMAIL_TOKEN_KEY = env["FERNET_EMAIL_TOKEN_KEY"] diff --git a/dataworkspace/dataworkspace/settings/integration_tests.py b/dataworkspace/dataworkspace/settings/integration_tests.py index 3a07468615..728b4107b5 100644 --- a/dataworkspace/dataworkspace/settings/integration_tests.py +++ b/dataworkspace/dataworkspace/settings/integration_tests.py @@ -2,7 +2,15 @@ from .base import * # noqa -DEBUG = True +from helpdesk_client.interfaces import ( + HelpDeskBase, + HelpDeskComment, + HelpDeskUser, + HelpDeskTicket, +) + +import requests + LOGGING = { "version": 1, @@ -19,3 +27,46 @@ "celery": {"handlers": ["dev"], "level": "INFO", "propagate": False}, }, } + + +class HelpDeskTest(HelpDeskBase): + def __init__(self, *args, **kwargs) -> None: + self._ticket_id = 1234567890987654321 + + def get_or_create_user(self, user: HelpDeskUser) -> HelpDeskUser: + raise NotImplementedError + + def create_ticket(self, ticket: HelpDeskTicket) -> HelpDeskTicket: + # Call help desk test server with ticket details + ticket.id = self._ticket_id + + requests.post( + "http://dataworkspace.test:8006/api/v2/tickets.json", + json={ + "ticket": { + "id": ticket.id, + "subject": ticket.subject, + "description": ticket.description, + "status": "new", + "requester_id": 1, + "submitter_id": 1, + }, + }, + ) + + return ticket + + def get_ticket(self, ticket_id: int) -> HelpDeskTicket: + raise NotImplementedError + + def close_ticket(self, ticket_id: int) -> HelpDeskTicket: + raise NotImplementedError + + def add_comment(self, ticket_id: int, comment: HelpDeskComment) -> HelpDeskTicket: + raise NotImplementedError + + def update_ticket(self, ticket: HelpDeskTicket) -> HelpDeskTicket: + raise NotImplementedError + + +HELP_DESK_INTERFACE = "dataworkspace.settings.integration_tests.HelpDeskTest" diff --git a/dataworkspace/dataworkspace/templates/datasets/subscriptions/unsubscribe_confirm.html b/dataworkspace/dataworkspace/templates/datasets/subscriptions/unsubscribe_confirm.html index 835ca7ffdf..a457fbfbdf 100644 --- a/dataworkspace/dataworkspace/templates/datasets/subscriptions/unsubscribe_confirm.html +++ b/dataworkspace/dataworkspace/templates/datasets/subscriptions/unsubscribe_confirm.html @@ -59,7 +59,7 @@
If you have any questions, contact {{ ZENDESK_EMAIL }}. + href="mailto:{{ HELP_DESK_EMAIL }}">{{ HELP_DESK_EMAIL }}.
diff --git a/dataworkspace/dataworkspace/templates/request_access/confirmation-page.html b/dataworkspace/dataworkspace/templates/request_access/confirmation-page.html index 81d50c451a..e67169c0c7 100644 --- a/dataworkspace/dataworkspace/templates/request_access/confirmation-page.html +++ b/dataworkspace/dataworkspace/templates/request_access/confirmation-page.html @@ -19,7 +19,7 @@