def __init__(self): self.zabbix_conf = '/etc/zabbix/zabbix_server.conf' # pdb.set_trace() try: self.ydata = yaml.safe_load(argv[2]) subject=argv[1] event_id = self.ydata['event_id'] event_status = self.ydata['event_status'] log.debug('event_id:%s, event_status:%s subject: %s' % (event_id,event_status,subject)) # Establish MySQL connection self.mysql_setup() # Zendesk setup self.mycsr.execute("SELECT lower(macro),value FROM globalmacro WHERE macro like '%ZENDESK%';") self.zdp = dict([ (macro[10:-1], value) for (macro,value) in self.mycsr.fetchall() ]) self.zd = Zendesk(self.zdp['url'], self.zdp['email'], self.zdp['token'], api_version=2, use_api_token=True, client_args={ "disable_ssl_certificate_validation": True }) log.debug('email:%s, enduser:%s' % (self.zdp['email'],self.zdp['enduser'])) self.zd_user = self.get_zendesk_user(self.zdp['email']) self.zd_enduser = self.get_zendesk_user(self.zdp['enduser']) # If the status is 'OK' and we can find the ticket # matching our external_id we close the ticket if not self.update_zendesk_ticket(event_id,event_status,subject): self.create_zendesk_ticket(event_id,event_status,subject) self.db.close() exit(0) except Exception as e: log.error('48 Failed with: [%s]. Ciao!' % e) exit(1)
def __init__(self, url, username, password, token): self._username = username self._password = password self._url = url self._token = token self._zd = Zendesk(self._url, self._username, self._password, self._token, api_version=2) self._headers = {'content-type': 'application/json'}
def __init__(self): if not argv[1].startswith('support@'): log.debug('wrong argument 1: %s' % argv[1] ) exit(2) self.zabbix_conf = '/etc/zabbix/zabbix_server.conf' try: # Input data mangling self.ydata = yaml.load(argv[3]) log.debug(argv[3]) event_id = argv[2].split(':')[0] event_status = argv[2].split(':')[1].strip() log.debug('event_id:%s, event_status:%s' % (event_id,event_status)) # Establish MySQL connection self.mysql_setup() # Zendesk setup self.mycsr.execute("SELECT lower(macro),value FROM globalmacro WHERE macro like '%ZENDESK%';") self.zdp = dict([ (macro[10:-1], value) for (macro,value) in self.mycsr.fetchall() ]) self.zd = Zendesk(self.zdp['url'], self.zdp['email'], self.zdp['token'], api_version=2, use_api_token=True, client_args={ "disable_ssl_certificate_validation": True }) self.zd_user = self.get_zendesk_user(self.zdp['email']) self.zd_enduser = self.get_zendesk_user(self.zdp['enduser']) # If the status is 'OK' and we can find the ticket # matching our external_id we close the ticket if not self.update_zendesk_ticket(event_id,event_status): self.create_zendesk_ticket(event_id,event_status) self.db.close() exit(0) except Exception as e: log.error('Failed with: [%s]. Ciao!' % e) exit(1)
def __init__(self): if not argv[1].startswith('support@'): log.debug('wrong argument 1: %s' % argv[1]) exit(2) self.zabbix_conf = '/etc/zabbix/zabbix_server.conf' try: # Input data mangling self.ydata = yaml.load(argv[3]) log.debug(argv[3]) event_id = argv[2].split(':')[0] event_status = argv[2].split(':')[1].strip() log.debug('event_id:%s, event_status:%s' % (event_id, event_status)) # Establish MySQL connection self.mysql_setup() # Zendesk setup self.mycsr.execute( "SELECT lower(macro),value FROM globalmacro WHERE macro like '%ZENDESK%';" ) self.zdp = dict([(macro[10:-1], value) for (macro, value) in self.mycsr.fetchall()]) self.zd = Zendesk( self.zdp['url'], self.zdp['email'], self.zdp['token'], api_version=2, use_api_token=True, client_args={"disable_ssl_certificate_validation": True}) self.zd_user = self.get_zendesk_user(self.zdp['email']) self.zd_enduser = self.get_zendesk_user(self.zdp['enduser']) # If the status is 'OK' and we can find the ticket # matching our external_id we close the ticket if not self.update_zendesk_ticket(event_id, event_status): self.create_zendesk_ticket(event_id, event_status) self.db.close() exit(0) except Exception as e: log.error('Failed with: [%s]. Ciao!' % e) exit(1)
def get_zendesk(): """Instantiate and return a Zendesk client""" # Verify required Zendesk settings zendesk_url = settings.ZENDESK_URL zendesk_email = settings.ZENDESK_USER_EMAIL zendesk_password = settings.ZENDESK_USER_PASSWORD if not zendesk_url or not zendesk_email or not zendesk_password: log.error('Zendesk settings error: please set ZENDESK_URL, ' 'ZENDESK_USER_EMAIL and ZENDESK_USER_PASSWORD.') statsd.incr('questions.zendesk.settingserror') raise ZendeskSettingsError('Missing Zendesk settings.') return Zendesk(zendesk_url, zendesk_email, zendesk_password, api_version=2)
def undo_last_ticket_migration(): mapping_path = 'migration_data/Case/prod-mapping.json' mapping = json.load(open(mapping_path,'r', encoding='utf-8-sig')) ids = [str(value) for _,value in mapping.items()] batches=[] slices = round(len(ids)/100+0.5) slice_size = 100 for i in range(slices): batches.append(ids[i*slice_size:slice_size + i*slice_size]) zd_config = global_config['zendesk'] zd = Zendesk(**zd_config) for batch in batches: if len(batch) >0: path = f'/tickets/destroy_many.json?ids={",".join(batch)}' response = zd.delete(path,'job_status') print(response) os.remove(mapping_path) print('Scheduled for deleting')
def __init__(self, settings): # Set up a set of globals to pass to every template self.gs_globals = {} # GENOMICS STATUS MAJOR VERSION NUMBER # Bump this with any change that requires an update to documentation self.gs_globals['gs_version'] = '1.0'; # Get the latest git commit hash # This acts as a minor version number for small updates # It also forces javascript / CSS updates and solves caching problems try: self.gs_globals['git_commit'] = subprocess.check_output(['git', 'rev-parse', '--short=7', 'HEAD']).strip() self.gs_globals['git_commit_full'] = subprocess.check_output(['git', 'rev-parse', 'HEAD']).strip() except: self.gs_globals['git_commit'] = 'unknown' self.gs_globals['git_commit_full'] = 'unknown' handlers = [ ("/", MainHandler), ("/login", LoginHandler), ("/logout", LogoutHandler), ("/unauthorized", UnAuthorizedHandler), ("/api/v1", DataHandler), ("/api/v1/applications", ApplicationsDataHandler), ("/api/v1/application/([^/]*)$", ApplicationDataHandler), ("/api/v1/bioinfo_analysis", BioinfoAnalysisHandler), ("/api/v1/bioinfo_analysis/([^/]*)$", BioinfoAnalysisHandler), ("/api/v1/expected", BarcodeVsExpectedDataHandler), tornado.web.URLSpec("/api/v1/caliper_image/(?P<project>[^/]+)/(?P<sample>[^/]+)/(?P<step>[^/]+)", CaliperImageHandler, name="CaliperImageHandler"), ("/api/v1/charon_summary/([^/]*)$",CharonProjectHandler ), ("/api/v1/delivered_monthly", DeliveredMonthlyDataHandler), ("/api/v1/delivered_monthly.png", DeliveredMonthlyPlotHandler), ("/api/v1/delivered_quarterly", DeliveredQuarterlyDataHandler), ("/api/v1/delivered_quarterly.png", DeliveredQuarterlyPlotHandler), ("/api/v1/flowcells", FlowcellsDataHandler), ("/api/v1/flowcell_count/", FlowcellCountApiHandler), ("/api/v1/flowcell_info2/([^/]*)$", FlowcellsInfoDataHandler), ("/api/v1/flowcell_info/([^/]*)$", OldFlowcellsInfoDataHandler), ("/api/v1/flowcell_qc/([^/]*)$", FlowcellQCHandler), ("/api/v1/flowcell_demultiplex/([^/]*)$", FlowcellDemultiplexHandler), ("/api/v1/flowcell_q30/([^/]*)$", FlowcellQ30Handler), # ("/api/v1/flowcells/([^/]*)$", FlowcellDataHandler), ("/api/v1/flowcell_notes/([^/]*)$", FlowcellNotesDataHandler), ("/api/v1/flowcell_links/([^/]*)$", FlowcellLinksDataHandler), ("/api/v1/flowcell_search/([^/]*)$", FlowcellSearchHandler), ("/api/v1/flowcell_yield/([^/]*)$", DataFlowcellYieldHandler), ("/api/v1/generate_workset", GenerateWorksetHandler), ("/api/v1/instrument_cluster_density", InstrumentClusterDensityDataHandler), ("/api/v1/instrument_cluster_density.png", InstrumentClusterDensityPlotHandler), ("/api/v1/instrument_error_rates", InstrumentErrorrateDataHandler), ("/api/v1/instrument_error_rates.png", InstrumentErrorratePlotHandler), ("/api/v1/instrument_logs", DataInstrumentLogsHandler), ("/api/v1/instrument_logs/([^/]*)$", DataInstrumentLogsHandler), ("/api/v1/instrument_names",InstrumentNamesHandler ), ("/api/v1/instrument_unmatched", InstrumentUnmatchedDataHandler), ("/api/v1/instrument_unmatched.png", InstrumentUnmatchedPlotHandler), ("/api/v1/instrument_yield", InstrumentYieldDataHandler), ("/api/v1/instrument_yield.png", InstrumentYieldPlotHandler), ("/api/v1/internal_costs/([^/]*)", ProjectInternalCostsHandler), ("/api/v1/last_updated", UpdatedDocumentsDatahandler), ("/api/v1/last_psul", LastPSULRunHandler), ("/api/v1/load_workset_samples", WorksetSampleLoadHandler), ("/api/v1/plot/q30.png", Q30PlotHandler), ("/api/v1/plot/samples_per_lane.png", SamplesPerLanePlotHandler), ("/api/v1/plot/reads_per_lane.png", ReadsPerLanePlotHandler), ("/api/v1/plot/clusters_per_lane.png", ClustersPerLanePlotHandler), ("/api/v1/plot/barcodes_vs_expected([^/]*)$", BarcodeVsExpectedPlotHandler), ("/api/v1/samples_per_lane", SamplesPerLaneDataHandler), ("/api/v1/produced_monthly", ProducedMonthlyDataHandler), ("/api/v1/produced_monthly.png", ProducedMonthlyPlotHandler), ("/api/v1/produced_quarterly", ProducedQuarterlyDataHandler), ("/api/v1/produced_quarterly.png", ProducedQuarterlyPlotHandler), ("/api/v1/projects", ProjectsDataHandler), ("/api/v1/project/([^/]*)$", ProjectSamplesDataHandler), ("/api/v1/project/([^/]*)/tickets", ProjectTicketsDataHandler), ("/api/v1/projects_fields", ProjectsFieldsDataHandler), ("/api/v1/project_summary/([^/]*)$", ProjectDataHandler), ("/api/v1/project_summary_update/([^/]*)/([^/]*)$", ProjectSummaryUpdateHandler), ("/api/v1/project_search/([^/]*)$", ProjectsSearchHandler), ("/api/v1/presets", PresetsHandler), ("/api/v1/qc/([^/]*)$", SampleQCDataHandler), ("/api/v1/projectqc/([^/]*)$", ProjectQCDataHandler), ("/api/v1/reads_vs_quality", ReadsVsQDataHandler), ("/api/v1/rna_report/([^/]*$)", ProjectRNAMetaDataHandler), ("/api/v1/running_notes/([^/]*)$", RunningNotesDataHandler), ("/api/v1/links/([^/]*)$", LinksDataHandler), ("/api/v1/sample_info/([^/]*)$", SampleInfoDataHandler), ("/api/v1/sample_readcount/(\w+)?", SampleReadCountDataHandler), ("/api/v1/sample_run_counts/(\w+)?", SampleRunReadCountDataHandler), ("/api/v1/sample_alignment/([^/]*)$", SampleQCAlignmentDataHandler), ("/api/v1/sample_coverage/([^/]*)$", SampleQCCoverageDataHandler), ("/api/v1/sample_summary/([^/]*)$", SampleQCSummaryDataHandler), ("/api/v1/sample_insert_sizes/([^/]*)$", SampleQCInsertSizesDataHandler), ("/api/v1/samples/start/([^/]*)$", PagedQCDataHandler), ("/api/v1/samples/([^/]*)$", SampleRunDataHandler), ("/api/v1/stats",StatsAggregationHandler), ("/api/v1/stats/application_open_projects",ApplicationOpenProjectsHandler), ("/api/v1/stats/application_open_samples",ApplicationOpenSamplesHandler), ("/api/v1/stats/week_instr_yield",WeekInstrumentTypeYieldHandler), ("/api/v1/stats/year_application",YearApplicationsProjectHandler), ("/api/v1/stats/year_application_samples",YearApplicationsSamplesHandler), ("/api/v1/stats/year_affiliation_projects",YearAffiliationProjectsHandler), ("/api/v1/stats/year_deliverytime_projects",YearDeliverytimeProjectsHandler), ("/api/v1/stats/year_deliverytime_application",YearDeliverytimeApplicationHandler), ("/api/v1/deliveries/set_bioinfo_responsible$", DeliveriesPageHandler), ("/api/v1/suggestions", SuggestionBoxDataHandler), ("/api/v1/test/(\w+)?", TestDataHandler), ("/api/v1/phix_err_rate", PhixErrorRateDataHandler), ("/api/v1/worksets", WorksetsDataHandler), ("/api/v1/workset/([^/]*)$", WorksetDataHandler), ("/api/v1/workset_search/([^/]*)$", WorksetSearchHandler), ("/api/v1/workset_notes/([^/]*)$", WorksetNotesDataHandler), ("/api/v1/workset_links/([^/]*)$", WorksetLinksHandler), ("/api/v1/ws_pl_to_lims", WorksetPlacementSavingHandler), ("/applications", ApplicationsHandler), ("/application/([^/]*)$", ApplicationHandler), ("/barcode_vs_expected", ExpectedHandler), ("/bioinfo/(P[^/]*)$", BioinfoAnalysisHandler), ("/deliveries", DeliveriesPageHandler), ("/clusters_per_lane", ClustersPerLaneHandler), ("/flowcells", FlowcellsHandler), ("/flowcells/([^/]*)$", FlowcellHandler), ("/flowcells_plot", FlowcellPlotHandler), ("/flowcell_count_plot", FlowcellCountPlotHandler), ("/instrument_logs",InstrumentLogsHandler), ("/instrument_logs/([^/]*)$", InstrumentLogsHandler), ("/multiqc_report/([^/]*)$", MultiQCReportHandler), ("/nas_quotas", NASQuotasHandler), ("/q30", Q30Handler), ("/qc/([^/]*)$", SampleQCSummaryHandler), (r"/qc_reports/(.*)", SafeStaticFileHandler, {"path": 'qc_reports'}), ("/quotas", QuotasHandler), ("/phix_err_rate", PhixErrorRateHandler), ("/production", ProductionHandler), ("/production/cronjobs", ProductionCronjobsHandler), ("/project/([^/]*)$", ProjectSamplesHandler), ("/project/(P[^/]*)/([^/]*)$", ProjectSamplesHandler), ("/project_summary/([^/]*)$", ProjectSummaryHandler), ("/projects/([^/]*)$", ProjectsHandler), ("/proj_meta", ProjMetaCompareHandler), ("/reads_total/([^/]*)$", ReadsTotalHandler), ("/reads_vs_qv", ReadsVsQvhandler), ("/reads_per_lane", ReadsPerLaneHandler), ("/rec_ctrl_view/([^/]*)$", RecCtrlDataHandler), ("/samples_per_lane", SamplesPerLaneHandler), ("/samples/([^/]*)$", SampleRunHandler), ("/sequencing", SequencingStatsHandler), ("/suggestion_box", SuggestionBoxHandler), ("/worksets", WorksetsHandler), ("/workset/([^/]*)$", WorksetHandler), ("/workset_placement",WorksetPlacementHandler), (r'.*', BaseHandler) ] self.declared_handlers = handlers # Load templates self.loader = template.Loader("design") # Global connection to the database couch = Server(settings.get("couch_server", None)) if couch: self.analysis_db= couch["analysis"] self.application_categories_db = couch["application_categories"] self.bioinfo_db = couch["bioinfo_analysis"] self.cronjobs_db = couch["cronjobs"] self.flowcells_db = couch["flowcells"] self.gs_users_db = couch["gs_users"] self.instruments_db= couch["instruments"] self.instrument_logs_db = couch["instrument_logs"] self.projects_db = couch["projects"] self.samples_db = couch["samples"] self.server_status_db = couch['server_status'] self.suggestions_db = couch["suggestion_box"] self.worksets_db = couch["worksets"] self.x_flowcells_db = couch["x_flowcells"] else: print settings.get("couch_server", None) raise IOError("Cannot connect to couchdb"); # Load columns and presets from genstat-defaults user in StatusDB genstat_id = '' for u in self.gs_users_db.view('authorized/users'): if u.get('key') == 'genstat-defaults': genstat_id = u.get('value') # It's important to check that this user exists! if not genstat_id: raise RuntimeError("genstat-defaults user not found on {}, please " \ "make sure that the user is abailable with the " \ "corresponding defaults information.".format(settings.get("couch_server", None))) # We need to get this database as OrderedDict, so the pv_columns doesn't # mess up user = settings.get("username", None) password = settings.get("password", None) headers = {"Accept": "application/json", "Authorization": "Basic " + "{}:{}".format(user, password).encode('base64')[:-1]} decoder = json.JSONDecoder(object_pairs_hook=OrderedDict) user_url = "{}/gs_users/{}".format(settings.get("couch_server"), genstat_id) json_user = requests.get(user_url, headers=headers).content.rstrip() self.genstat_defaults = decoder.decode(json_user) # Load private instrument listing self.instrument_list = settings.get("instruments") # If settings states mode, no authentication is used self.test_mode = settings["Testing mode"] # google oauth key self.oauth_key = settings["google_oauth"]["key"] # ZenDesk self.zendesk_url = settings["zendesk"]["url"] self.zendesk_user = settings["zendesk"]["username"] self.zendesk_token = settings["zendesk"]["token"] self.zendesk = Zendesk(self.zendesk_url, use_api_token=True, zendesk_username=self.zendesk_user, zendesk_password=self.zendesk_token, api_version=2) # Trello self.trello_api_key = settings['trello']['api_key'] self.trello_api_secret = settings['trello']['api_secret'] self.trello_token = settings['trello']['token'] # Load password seed self.password_seed = settings.get("password_seed") # load logins for the genologics sftp self.genologics_login=settings['sftp']['login'] self.genologics_pw=settings['sftp']['password'] # Location of the psul log self.psul_log=settings.get("psul_log") # index page - to display quotas of uppmax projects self.uppmax_projects = settings.get('uppmax_projects') # to display instruments in the server status self.server_status = settings.get('server_status') # project summary - multiqc tab self.multiqc_path = settings.get('multiqc_path') # Setup the Tornado Application cookie_secret = base64.b64encode(uuid.uuid4().bytes + uuid.uuid4().bytes) settings["debug"]= True settings["static_path"]= "static" settings["cookie_secret"]= cookie_secret settings["login_url"]= "/login" if options['develop']: tornado.autoreload.watch("design/application.html") tornado.autoreload.watch("design/applications.html") tornado.autoreload.watch("design/barcode_vs_expected.html") tornado.autoreload.watch("design/base.html") tornado.autoreload.watch("design/bioinfo_tab.html") tornado.autoreload.watch("design/bioinfo_tab/run_lane_sample_view.html") tornado.autoreload.watch("design/bioinfo_tab/sample_run_lane_view.html") tornado.autoreload.watch("design/clusters_per_lane.html") tornado.autoreload.watch("design/cronjobs.html") tornado.autoreload.watch("design/deliveries.html") tornado.autoreload.watch("design/error_page.html") tornado.autoreload.watch("design/flowcell.html") tornado.autoreload.watch("design/flowcell_error.html") tornado.autoreload.watch("design/flowcell_samples.html") tornado.autoreload.watch("design/flowcells.html") tornado.autoreload.watch("design/index.html") tornado.autoreload.watch("design/instrument_logs.html") tornado.autoreload.watch("design/link_tab.html") tornado.autoreload.watch("design/nas_quotas.html") tornado.autoreload.watch("design/phix_err_rate.html") tornado.autoreload.watch("design/production.html") tornado.autoreload.watch("design/proj_meta_compare.html") tornado.autoreload.watch("design/project_samples.html") tornado.autoreload.watch("design/project_summary.html") tornado.autoreload.watch("design/projects.html") tornado.autoreload.watch("design/q30.html") tornado.autoreload.watch("design/reads_per_lane.html") tornado.autoreload.watch("design/reads_total.html") tornado.autoreload.watch("design/reads_vs_qv.html") tornado.autoreload.watch("design/rec_ctrl_view.html") tornado.autoreload.watch("design/running_notes_help.html") tornado.autoreload.watch("design/running_notes_tab.html") tornado.autoreload.watch("design/samples_per_lane.html") tornado.autoreload.watch("design/sequencing_stats.html") tornado.autoreload.watch("design/suggestion_box.html") tornado.autoreload.watch("design/unauthorized.html") tornado.autoreload.watch("design/uppmax_quotas.html") tornado.autoreload.watch("design/workset_placement.html") tornado.autoreload.watch("design/workset_samples.html") tornado.autoreload.watch("design/worksets.html") tornado.autoreload.watch("design/yield_plot.html") tornado.web.Application.__init__(self, handlers, **settings)
class z2z: def __init__(self): if not argv[1].startswith('support@'): log.debug('wrong argument 1: %s' % argv[1]) exit(2) self.zabbix_conf = '/etc/zabbix/zabbix_server.conf' try: # Input data mangling self.ydata = yaml.load(argv[3]) log.debug(argv[3]) event_id = argv[2].split(':')[0] event_status = argv[2].split(':')[1].strip() log.debug('event_id:%s, event_status:%s' % (event_id, event_status)) # Establish MySQL connection self.mysql_setup() # Zendesk setup self.mycsr.execute( "SELECT lower(macro),value FROM globalmacro WHERE macro like '%ZENDESK%';" ) self.zdp = dict([(macro[10:-1], value) for (macro, value) in self.mycsr.fetchall()]) self.zd = Zendesk( self.zdp['url'], self.zdp['email'], self.zdp['token'], api_version=2, use_api_token=True, client_args={"disable_ssl_certificate_validation": True}) self.zd_user = self.get_zendesk_user(self.zdp['email']) self.zd_enduser = self.get_zendesk_user(self.zdp['enduser']) # If the status is 'OK' and we can find the ticket # matching our external_id we close the ticket if not self.update_zendesk_ticket(event_id, event_status): self.create_zendesk_ticket(event_id, event_status) self.db.close() exit(0) except Exception as e: log.error('Failed with: [%s]. Ciao!' % e) exit(1) def create_zendesk_ticket(self, event_id, event_status): collaborators = self.zbx_evt_recipients(event_id) subject = self.ydata['trigger']['name'] #description = '%s\n\nZabbix severity: %s' % (self.ydata['desc'],self.ydata['trigger']['severity']) description = self.ydata['desc'].replace('"', '') priority = 'high' if self.ydata['trigger'][ 'severity'] == 'High' else 'normal' tkt_data = { 'ticket': { 'subject': subject, 'description': description, 'collaborators': collaborators, 'set_tags': ['test'], 'external_id': event_id, 'priority': priority, 'requester_id': self.zd_enduser['id'], 'submitter_id': self.zd_enduser['id'], 'organization_id': self.zd_enduser['organization_id'], } } ### Auto-close if status is "OK" and severity is "information" if event_status == 'OK' and self.ydata['trigger'][ 'severity'] == 'Information': tkt_data['ticket']['status'] = 'solved' tkt_data['ticket']['assignee_id'] = self.zd_user['id'] ### TODO: ADD TKT_ID as acknowledge comment tkt_url = self.zd.create_ticket(data=tkt_data) tkt_id = get_id_from_url(tkt_url) #tkt = self.zd.show_ticket(ticket_id=tkt_id) #log.debug('Created ticket with ID %s'%tkt_id) #log.debug(json.dumps(tkt,sort_keys=True,indent=2)) log.info('Created Zendesk ticket ID %s from Zabbix eventid %s' % (tkt_id, event_id)) #return tkt_id def update_zendesk_ticket(self, event_id, event_status): if event_status == 'OK': tkt = self.zd.list_all_tickets(external_id=event_id) log.debug(tkt) # json.dumps(tkt,sort_keys=True,indent=2) if tkt['count'] == 1: tkt_id = tkt['tickets'][0]['id'] desc = self.ydata['desc'].replace('"', '') #log.info('Update ticket %s' % tkt_id) if self.ydata['trigger']['severity'] == 'High': tkt_data = { 'ticket': { 'comment': { 'public': True, 'body': desc } } } log.info('Updating ticket %s from Zabbix event %s' % (tkt_id, event_id)) else: tkt_data = { 'ticket': { 'status': 'solved', 'assignee_id': self.zd_user['id'], 'comment': { 'public': True, 'author_id': self.zd_enduser['id'], 'body': '%s\n(Auto-closed by Zabbix)' % desc } } } log.info('Closing Zendesk ticket %s, event id: %s' % (tkt_id, event_id)) tkt_up = self.zd.update_ticket(ticket_id=tkt_id, data=tkt_data) #log.debug(json.dumps(tkt_up,sort_keys=True,indent=2)) return True return False def mysql_setup(self): try: # Get MySQL connection parameters from zabbix conf file with open(self.zabbix_conf) as f: my = dict(ln.lower()[2:].split('=') for ln in f.read().split('\n') if ln.startswith('DB')) self.db = MySQLdb.connect(my['host'], my['user'], my['password'], my['name']) self.mycsr = self.db.cursor() except IOError as e: log.error(e) return False except KeyError as e: log.error('Could not find %s' % e) return False else: return True def get_zendesk_user(self, email): cache_file = '/tmp/zendesk_user_%s' % email try: if time() - path.getmtime(cache_file) > 86400: remove(cache_file) log.debug('Cache file deleted') with open(cache_file, 'r') as f: data = yaml.load(f.read()) except: data = self.zd.search_user(query='email:%s' % email)['users'][0] with open(cache_file, 'w') as f: f.write(yaml.dump(data)) return data def zbx_evt_recipients(self, event_id): rows = ['*****@*****.**'] try: sql = """SELECT m.sendto mail FROM events e,functions f,items i, hosts_groups hg, groups g, users_groups uxg, usrgrp ug, media m WHERE e.eventid=%s AND e.object=0 AND e.source=0 AND e.objectid=f.triggerid AND f.itemid=i.itemid AND hg.hostid=i.hostid AND ug.usrgrpid=uxg.usrgrpid AND m.userid=uxg.userid AND LOWER(g.name)=LOWER(ug.name) AND hg.groupid=g.groupid;""" self.mycsr.execute(sql % event_id) rows.extend([r[0] for r in self.mycsr.fetchall()]) return rows except: return rows
from zendesk import Zendesk ################################################################ ## NEW CONNECTION CLIENT ################################################################ zendesk = Zendesk( zendesk_url='https://yourcompany.zendesk.com', zendesk_username='******', zendesk_password='******', use_api_token=True, api_version=2 ) # Are you getting an error such as... # "SSL routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed"? zendesk = Zendesk('https://yourcompany.zendesk.com', '*****@*****.**', 'passwd', client_args={ "disable_ssl_certificate_validation": True } ) ################################################################ ## TICKETS ################################################################ # List zendesk.list_tickets(view_id=1) # Must have a view defined # Create new_ticket = {
import os from config import ENV from salesforce import SalesForce from zendesk import Zendesk from migration import MigrationItem from log_helper import get_logger import helpers log = get_logger('Main') if __name__ == "__main__": migration_plan = json.load(open('migration_plan.json', 'r')) sf_config = migration_plan['salesforce'] sf = SalesForce(**sf_config) zd_config = migration_plan['zendesk'] zd = Zendesk(**zd_config) for item in migration_plan['migration_items']: if item['skip'] == False: if item['type'] == 'migration_object': migration_item = MigrationItem(sf, zd, mode=ENV, **item) if migration_item.skip == False: try: migration_item.migrate() except Exception as err: log.critical(err) raise err elif item['type'] == 'script': eval(item['script'], {"helpers": helpers, "env": ENV})
def get_zd_instance(): migration_plan = json.load(open('migration_plan.json', 'r')) zd_config = migration_plan['zendesk'] return Zendesk(**zd_config)
def config(argv=None): import os, sys, argparse # Declare a class for an argparse custom action. # Handles converting ascii input from argparse that may contain unicode # to a real unicode string. class UnicodeStore(argparse.Action): def __call__(self, parser, namespace, values, option_string=None): setattr(namespace, self.dest, values.decode('utf-8')) # Options precedence: # program state defaults, which are overridden by # ~/.zd.cfg [zdgrab] section options, which are overridden by # command line options, which are overridden by # -c CONFIG_FILE [zdgrab] section options, which are overridden by # ~/.zd.cfg [RUN_SECTION] section options, which are overridden by # -c CONFIG_FILE [RUN_SECTION] section options # # Program state, with defaults # state = { 'verbose': False, 'tickets': None, 'work_dir': os.path.join(os.path.expanduser('~'), 'zdgrab'), 'agent': 'me', 'url': None, 'mail': None, 'password': '******', 'is_token': False, } argp = argparse.ArgumentParser( description='Download attachments from Zendesk tickets.') argp.add_argument('-v', '--verbose', action='store_true', help='Verbose output') argp.add_argument( '-t', action=UnicodeStore, dest='tickets', help='Ticket(s) to grab attachments (default: all of your open tickets)' ) argp.add_argument('-c', action=UnicodeStore, dest='config_file', help='Configuration file (overrides ~/.zd.cfg)') argp.add_argument('-w', action=UnicodeStore, dest='work_dir', help="""Working directory in which to store attachments. (default: ~/zdgrab/)""") argp.add_argument('-a', action=UnicodeStore, dest='agent', help='Agent whose open tickets to search (default: me)') argp.add_argument('-u', action=UnicodeStore, dest='url', help='URL of Zendesk (e.g. https://example.zendesk.com)') argp.add_argument('-m', action=UnicodeStore, dest='mail', help='E-Mail address for Zendesk login') argp.add_argument('-p', action=UnicodeStore, dest='password', help='Password for Zendesk login', nargs='?', const=state['password']) argp.add_argument( '-i', '--is-token', action='store_true', dest='is_token', help='Is token? Specify if password supplied a Zendesk token') # Set argparse defaults with program defaults. # Skip password as it is argparse const, not argparse default argp.set_defaults(**dict( (k, v) for k, v in state.iteritems() if k != 'password')) # Read ~/.zd.cfg [zdgrab] section and update argparse defaults try: config_state(os.path.join(os.path.expanduser('~'), '.zd.cfg'), 'zd', state) # Password is OK now, because we either have one from the config file or # it is still None. argp.set_defaults(**dict((k, v) for k, v in state.iteritems())) except configparser.NoSectionError: # -c CONFIG_FILE did not have a [zdgrab] section. Skip it. pass # Parse the command line options if argv is None: argv = sys.argv args = argp.parse_args() # Update the program state with command line options for k in state.keys(): state[k] = getattr(args, k) # -c CONFIG_FILE given on command line read args.config_file [zdgrab], update state if args.config_file: if state['verbose']: print('Reading config file {}'.format(args.config_file)) try: config_state(args.config_file, 'zd', state) except configparser.NoSectionError: # -c CONFIG_FILE did not have a [zdgrab] section. Skip it. pass from zendesk import Zendesk if state['url'] and state['mail'] and state['password']: if state['verbose']: print( 'Configuring Zendesk with:\n' ' url: {}\n' ' mail: {}\n' ' is_token: {}\n' ' password: (hidden)\n'.format(state['url'], state['mail'], repr(state['is_token']))) zd = Zendesk(state['url'], zendesk_username=state['mail'], zendesk_password=state['password'], use_api_token=state['is_token'], api_version=2) else: msg = textwrap.dedent("""\ Error: Need Zendesk config to continue. Use -u, -m, -p options or a config file to provide the information. Config file (e.g. ~/.zd.cfg) should be something like: [zd] url = https://example.zendesk.com mail = [email protected] password = dneib393fwEF3ifbsEXAMPLEdhb93dw343 is_token = 1 agent = [email protected] """) print(msg) return 1 # Log the state if state['verbose']: print('Running with program state:') # Let's go around our ass to get to our elbow to hide the password here. for (k, v) in [(k, v) for k, v in state.iteritems() if k != 'password']: print(' {}: {}'.format(k, repr(v))) print(' password: (hidden)\n') # tickets=None means default to getting all of the attachments for this # user's open tickets. If tickets is given, try to split it into ints if state['tickets']: # User gave a list of tickets try: state['tickets'] = [int(i) for i in state['tickets'].split(',')] except ValueError: print('Error: Could not convert to integers: {}'.format( state['tickets'])) return 1 return zd, state
import re from zendesk import Zendesk def get_id_from_url(url): match = re.match(r".*/(?P<identifier>\d+)\.xml", url) if match and match.group('identifier'): return match.group('identifier') ################################################################ ## NEW CONNECTION CLIENT ################################################################ zendesk = Zendesk('https://yourcompany.zendesk.com', '*****@*****.**', 'passwd') ################################################################ ## TICKETS ################################################################ # List zendesk.list_tickets(view_id=1) # Must have a view defined # Create new_ticket = { 'ticket': { 'requester-name': 'Howard Schultz', 'requester-email': '*****@*****.**', 'subject': 'My Starbucks coffee is cold!', 'description': 'please reheat my coffee', 'set-tags': 'coffee drinks',
42730237: 'Tickets/LSM', 42730227: 'Tickets/WSM', 42771018: 'Tickets/Mobile', 42730217: 'Tickets/Insights', 44896573: 'Tickets/Browser', 42771028: 'Tickets/Platform', 42730207: 'Tickets/Other' } post_url = "https://platform-api.newrelic.com/platform/v1/metrics" guid = 'com.NewRelic.ZedScraper' #update bash_profile on server with this info zendesk = Zendesk(environ['ZENDESK_URL'], environ['ZENDESK_USERNAME'], environ['ZENDESK_APITOKEN'], use_api_token=True, api_version=2, client_args={"disable_ssl_certificate_validation": True}) platform_key = environ['NEW_RELIC_APIKEY'] view_blob = zendesk.count_many_views(ids=view_ids.keys()) counted_views = {} for view in view_blob['view_counts']: metric_name = "Component/test2/%s[Tickets]" % view_ids[view['view_id']] total_tickets = view['value'] data = { "agent": { "host": "db.zendesk.newrelic_tickets",
import re from zendesk import Zendesk def get_id_from_url(url): match = re.match(r".*/(?P<identifier>\d+)\.(json|xml)", url) if match and match.group('identifier'): return match.group('identifier') ################################################################ ## NEW CONNECTION CLIENT ################################################################ zendesk = Zendesk('https://yourcompany.zendesk.com', '*****@*****.**', 'passwd') ################################################################ ## TICKETS ################################################################ # List zendesk.list_tickets(view_id=1) # Must have a view defined # Create new_ticket = { 'ticket': { 'requester-name': 'Howard Schultz', 'requester-email': '*****@*****.**', 'subject':'My Starbucks coffee is cold!', 'description': 'please reheat my coffee', 'set-tags': 'coffee drinks', 'ticket-field-entries': { '@type': 'array',
def main(): arguments = docopt(__doc__) logger.setLevel(arguments['--level']) logger.debug(arguments) global cfg global run_open global open_cmd global zendesk # read in YAML configuration file if "~" in arguments['--config']: pattern = re.compile('~') arguments['--config'] = pattern.sub(os.path.expanduser("~"), arguments['--config']) if not os.path.exists(arguments['--config']): logger.error("Specified configuration file does not exist!") exit(1) with open(arguments['--config'], 'r') as ymlfile: cfg = yaml.load(ymlfile) # determine if run_open is defined and enabled try: run_open = cfg['downloader']['run_open'] if str(run_open).lower() == "true" or run_open == 1: run_open = True else: run_open = False except: run_open = False # determine if open_command is defined try: open_cmd = cfg['downloader']['open_command'] except: if run_open: logger.warning("'run_open' is set, but 'open_command' doesn't exist - disabling auto open. Please configure 'open_cmd' in .zendesk.yml.") run_open = False # check directory for downloads, expand '~' and append '/' if necessary if "~" in cfg['downloader']['directory']: pattern = re.compile('~') cfg['downloader']['directory'] = pattern.sub(os.path.expanduser("~"), cfg['downloader']['directory']) if not cfg['downloader']['directory'].endswith('/'): cfg['downloader']['directory'] += '/' logger.debug("download directory: {}".format(cfg['downloader']['directory'])) options = {} if 'extensions' in cfg['downloader']: options['extensions'] = cfg['downloader']['extensions'] if 'exclude' in cfg['downloader']: options['exclude'] = cfg['downloader']['exclude'] if 'rm_after_extract' in cfg['downloader']: options['rm_after_extract'] = cfg['downloader']['rm_after_extract'] zendesk = Zendesk(cfg['credentials']['username'], cfg['credentials']['password'], cfg['credentials']['url'], options=options) if '{0}'.format(arguments['--case']) == 'None': logger.info("No case specified, downloading attachments for all cases with updates in the last {0} hours".format(arguments['--recent'])) start_time = datetime.datetime.now() - datetime.timedelta(hours=int(arguments['--recent'])) updated_tickets = zendesk.getUpdatedTickets(start_time) logger.debug(updated_tickets) if not "error" in updated_tickets: for ticket in updated_tickets['ids']: processTicket(ticket) else: logger.error(updated_tickets['error']) else: ticket = arguments['--case'] processTicket(ticket)
42730227 : 'Tickets/WSM', 42771018 : 'Tickets/Mobile', 42730217 : 'Tickets/Insights', 44896573 : 'Tickets/Browser', 42771028 : 'Tickets/Platform', 42730207 : 'Tickets/Other' } post_url = "https://platform-api.newrelic.com/platform/v1/metrics" guid = 'com.NewRelic.ZedScraper' #update bash_profile on server with this info zendesk = Zendesk( environ['ZENDESK_URL'], environ['ZENDESK_USERNAME'], environ['ZENDESK_APITOKEN'], use_api_token=True, api_version=2, client_args= {"disable_ssl_certificate_validation": True} ) platform_key = environ['NEW_RELIC_APIKEY'] view_blob = zendesk.count_many_views(ids=view_ids.keys()) counted_views = {} for view in view_blob['view_counts']: metric_name = "Component/test2/%s[Tickets]" % view_ids[view['view_id']] total_tickets = view['value'] data = {
from zendesk import Zendesk ################################################################ ## NEW CONNECTION CLIENT ################################################################ zendesk = Zendesk('https://yourcompany.zendesk.com', '*****@*****.**', 'passwd') # Are you getting an error such as... # "SSL routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed"? zendesk = Zendesk('https://yourcompany.zendesk.com', '*****@*****.**', 'passwd', client_args={ "disable_ssl_certificate_validation": True } ) ################################################################ ## TICKETS ################################################################ # List zendesk.list_tickets(view_id=1) # Must have a view defined # Create new_ticket = { 'ticket': { _ 'requester_name': 'Howard Schultz', 'requester_email': '*****@*****.**', 'subject':'My Starbucks coffee is cold!', 'description': 'please reheat my coffee', 'set_tags': 'coffee drinks',
class z2z: def __init__(self): if not argv[1].startswith('support@'): log.debug('wrong argument 1: %s' % argv[1] ) exit(2) self.zabbix_conf = '/etc/zabbix/zabbix_server.conf' try: # Input data mangling self.ydata = yaml.load(argv[3]) log.debug(argv[3]) event_id = argv[2].split(':')[0] event_status = argv[2].split(':')[1].strip() log.debug('event_id:%s, event_status:%s' % (event_id,event_status)) # Establish MySQL connection self.mysql_setup() # Zendesk setup self.mycsr.execute("SELECT lower(macro),value FROM globalmacro WHERE macro like '%ZENDESK%';") self.zdp = dict([ (macro[10:-1], value) for (macro,value) in self.mycsr.fetchall() ]) self.zd = Zendesk(self.zdp['url'], self.zdp['email'], self.zdp['token'], api_version=2, use_api_token=True, client_args={ "disable_ssl_certificate_validation": True }) self.zd_user = self.get_zendesk_user(self.zdp['email']) self.zd_enduser = self.get_zendesk_user(self.zdp['enduser']) # If the status is 'OK' and we can find the ticket # matching our external_id we close the ticket if not self.update_zendesk_ticket(event_id,event_status): self.create_zendesk_ticket(event_id,event_status) self.db.close() exit(0) except Exception as e: log.error('Failed with: [%s]. Ciao!' % e) exit(1) def create_zendesk_ticket(self,event_id,event_status): collaborators = self.zbx_evt_recipients(event_id) subject = self.ydata['trigger']['name'] #description = '%s\n\nZabbix severity: %s' % (self.ydata['desc'],self.ydata['trigger']['severity']) description = self.ydata['desc'].replace('"','') priority = 'high' if self.ydata['trigger']['severity'] == 'High' else 'normal' tkt_data = { 'ticket': { 'subject': subject, 'description': description, 'collaborators': collaborators, 'set_tags': ['test'], 'external_id': event_id, 'priority': priority, 'requester_id': self.zd_enduser['id'], 'submitter_id': self.zd_enduser['id'], 'organization_id': self.zd_enduser['organization_id'], }} ### Auto-close if status is "OK" and severity is "information" if event_status == 'OK' and self.ydata['trigger']['severity'] == 'Information': tkt_data['ticket']['status'] = 'solved' tkt_data['ticket']['assignee_id'] = self.zd_user['id'] ### TODO: ADD TKT_ID as acknowledge comment tkt_url = self.zd.create_ticket(data=tkt_data) tkt_id = get_id_from_url(tkt_url) #tkt = self.zd.show_ticket(ticket_id=tkt_id) #log.debug('Created ticket with ID %s'%tkt_id) #log.debug(json.dumps(tkt,sort_keys=True,indent=2)) log.info('Created Zendesk ticket ID %s from Zabbix eventid %s' % (tkt_id,event_id)) #return tkt_id def update_zendesk_ticket(self,event_id,event_status): if event_status == 'OK': tkt = self.zd.list_all_tickets(external_id=event_id) log.debug(tkt) # json.dumps(tkt,sort_keys=True,indent=2) if tkt['count']==1: tkt_id = tkt['tickets'][0]['id'] desc = self.ydata['desc'].replace('"','') #log.info('Update ticket %s' % tkt_id) if self.ydata['trigger']['severity'] == 'High': tkt_data = {'ticket':{ 'comment':{'public':True, 'body': desc} }} log.info('Updating ticket %s from Zabbix event %s' % (tkt_id,event_id)) else: tkt_data = {'ticket':{ 'status': 'solved', 'assignee_id': self.zd_user['id'], 'comment':{ 'public':True, 'author_id': self.zd_enduser['id'], 'body': '%s\n(Auto-closed by Zabbix)'%desc } }} log.info('Closing Zendesk ticket %s, event id: %s' % (tkt_id,event_id)) tkt_up = self.zd.update_ticket(ticket_id=tkt_id,data=tkt_data) #log.debug(json.dumps(tkt_up,sort_keys=True,indent=2)) return True return False def mysql_setup(self): try: # Get MySQL connection parameters from zabbix conf file with open(self.zabbix_conf) as f: my = dict( ln.lower()[2:].split('=') for ln in f.read().split('\n') if ln.startswith('DB') ) self.db = MySQLdb.connect(my['host'], my['user'], my['password'], my['name']) self.mycsr = self.db.cursor() except IOError as e: log.error(e) return False except KeyError as e: log.error('Could not find %s'%e) return False else: return True def get_zendesk_user(self,email): cache_file = '/tmp/zendesk_user_%s' % email try: if time()-path.getmtime(cache_file) > 86400: remove(cache_file) log.debug('Cache file deleted') with open(cache_file, 'r') as f: data = yaml.load(f.read()) except: data = self.zd.search_user(query='email:%s'%email)['users'][0] with open(cache_file, 'w') as f: f.write(yaml.dump(data) ) return data def zbx_evt_recipients(self,event_id): rows = ['*****@*****.**'] try: sql = """SELECT m.sendto mail FROM events e,functions f,items i, hosts_groups hg, groups g, users_groups uxg, usrgrp ug, media m WHERE e.eventid=%s AND e.object=0 AND e.source=0 AND e.objectid=f.triggerid AND f.itemid=i.itemid AND hg.hostid=i.hostid AND ug.usrgrpid=uxg.usrgrpid AND m.userid=uxg.userid AND LOWER(g.name)=LOWER(ug.name) AND hg.groupid=g.groupid;""" self.mycsr.execute(sql % event_id) rows.extend([ r[0] for r in self.mycsr.fetchall() ]) return rows except: return rows
class ZendeskTask(object): _username = "" _url = "" _password = "" _token = "" _headers = {} def __init__(self, url, username, password, token): self._username = username self._password = password self._url = url self._token = token self._zd = Zendesk(self._url, self._username, self._password, self._token, api_version=2) self._headers = {'content-type': 'application/json'} def search_by_email(self, email): return self._zd.search_user(query=email) def get_all_organizations(self): zen_orgs = [] runLoop = True page = 1 while runLoop: results = self._zd.list_organizations(page=page) if int(results['count']) > 0: for org in results['organizations']: zen_orgs.append(org) page+=1 if results['next_page'] is None: runLoop = False return zen_orgs def update_organization(self, orgId, data): return self._zd.update_organization(organization_id=orgId, data=data) def update_organization_v2(self, orgId, data): # Use requests module instead username = self._username + '/token' url = '{0}/api/v2/organizations/{1}.json'.format(self._url, orgId) r = requests.put(url, auth=(username, self._password), headers=self._headers, data=json.dumps(data)) return r.content def create_organization(self, data): return self._zd.create_organization(data=data) def create_organization_v2(self, data): # Use requests module instead username = self._username + '/token' url = '{0}/api/v2/organizations.json'.format(self._url) r = requests.post(url, auth=(username, self._password), headers=self._headers, data=json.dumps(data)) return r.content def update_user(self, userId, data): return self._zd.update_user(user_id=userId, data=data) def list_organization_fields(self): r = self._zd.list_organization_fields() return { "count" : r['count'], "fields" : r['organization_fields'] } def search_organization_by_name(self, name): return self._zd.autocomplete_organizations(name=name) def get_existing_organization_field_options(self, field_id): return self._zd.show_organization_field(field_id=field_id)['organization_field']['custom_field_options'] def add_organization_field_dropdown(self, field_id, field_name='Vict0r Duan', field_value='test1'): # Dropdown fields need all options to be added existing_fields = self.get_existing_organization_field_options(field_id=field_id) existing_fields.append({ 'name' : field_name, 'value' : field_value }) data = { "organization_field" : { "custom_field_options" : [{"name": "test", "value": "test2"}] }} try: print data print self._zd.update_organization_fields(field_id=field_id, data=data) except Exception, err: print err return self.get_existing_organization_field_options(field_id=field_id) return self.get_existing_organization_field_options(field_id=field_id)
def main(argv=None): import sys, tempfile, argparse # Log to stdout import logging logging.basicConfig() # Declare a class for an argparse custom action. # Handles converting ascii input from argparse that may contain unicode # to a real unicode string. class UnicodeStore(argparse.Action): def __call__(self, parser, namespace, values, option_string=None): setattr(namespace, self.dest, values.decode('utf-8')) # Options precedence: # program state defaults, which are overridden by # ~/.zdf2pdf.cfg [zdf2pdf] section options, which are overridden by # command line options, which are overridden by # -c CONFIG_FILE [zdf2pdf] section options, which are overridden by # ~/.zdf2pdf.cfg [RUN_SECTION] section options, which are overridden by # -c CONFIG_FILE [RUN_SECTION] section options # # Program state, with defaults # state = { 'verbose': False, 'json_file': None, 'categories': None, 'forums': None, 'topics': None, 'run_section': None, 'list_zdf': 'forums', 'style_file': None, 'output_file': 'PCLOADLETTER.pdf', 'title': None, 'title_class': None, 'author': None, 'date': None, 'copyright': None, 'toc': False, 'toc_class': None, 'toc_title': 'Table of Contents', 'pre_width': None, 'strip_empty': False, 'header': None, 'footer': None, 'category_sections': False, 'forum_sections': False, 'topics_heading': None, 'work_dir': tempfile.gettempdir(), 'delete': False, 'url': None, 'mail': None, 'password': '******', 'is_token': False, } argp = argparse.ArgumentParser( description='Make a PDF from Zendesk forums or entries.') argp.add_argument('-v', '--verbose', action='store_true', help='Verbose output') argp.add_argument('--json-file', action=UnicodeStore, dest='json_file', help='Zendesk entries JSON file to convert to PDF') argp.add_argument( '--categories', action=UnicodeStore, dest='categories', help='Comma separated Category IDs to download and convert to PDF') argp.add_argument( '--forums', action=UnicodeStore, dest='forums', help='Comma separated Forum IDs to download and convert to PDF') argp.add_argument( '--topics', action=UnicodeStore, dest='topics', help='Comma separated Topic (Entry) IDs to download and convert to PDF' ) argp.add_argument('-r', action=UnicodeStore, dest='run_section', help='Run pre-configured section in configuration file') argp.add_argument( '-l', action=UnicodeStore, dest='list_zdf', help="""List a forum's entries by ID and title. If no forum ID is supplied, list forums by ID and title organized by category""", nargs='?', const=state['list_zdf'], metavar='FORUM_TO_LIST') argp.add_argument('-c', action=UnicodeStore, dest='config_file', help='Configuration file (overrides ~/.zdf2pdf.cfg)') argp.add_argument('-s', action=UnicodeStore, dest='style_file', help='Style file (CSS) to <link>') argp.add_argument('-o', action=UnicodeStore, dest='output_file', help='Output filename (default: PCLOADLETTER.pdf)', default=state['output_file']) argp.add_argument('-t', action=UnicodeStore, dest='title', help='Title to be added to the beginning of the PDF') argp.add_argument( '-a', action=UnicodeStore, dest='author', help='Author line to be added to the beginning of the PDF') argp.add_argument('--date', action=UnicodeStore, dest='date', help='Date line to be added to the beginning of the PDF') argp.add_argument( '--copyright', action=UnicodeStore, dest='copyright', help='Copyright line to be added to the beginning of the PDF') argp.add_argument('--title-class', action=UnicodeStore, dest='title_class', help='CSS class to be added to title page div') argp.add_argument('--toc', action='store_true', dest='toc', help="Generate a Table of Contents (default: false)") argp.add_argument('--toc-title', action=UnicodeStore, dest='toc_title', help="ToC title (default: Table of Contents)") argp.add_argument('--toc-class', action=UnicodeStore, dest='toc_class', help='CSS class to be added to ToC div') argp.add_argument('--pre-width', action=UnicodeStore, dest='pre_width', help='Width to wrap contents of <pre></pre> tags.') argp.add_argument('--strip-empty', action='store_true', dest='strip_empty', help='Strip empty tags. (default: false)') argp.add_argument('--header', action=UnicodeStore, dest='header', help='HTML header to add to the PDF (see docs)') argp.add_argument('--footer', action=UnicodeStore, dest='footer', help='HTML footer to add to the PDF (see docs)') argp.add_argument('--category-sections', action='store_true', dest='category_sections', help='Make categories sections (default: false)') argp.add_argument('--forum-sections', action='store_true', dest='category_sections', help='Make categories sections (default: false)') argp.add_argument( '--topics-heading', action=UnicodeStore, dest='topics_heading', help='Heading to put at start of topics retrieved individually') argp.add_argument( '-w', action=UnicodeStore, dest='work_dir', help="""Working directory in which to store JSON output and images (default: temp dir)""") argp.add_argument('-d', '--delete', action='store_true', dest='delete', help="""Delete working directory at program exit (default: do not delete)""") argp.add_argument('-u', action=UnicodeStore, dest='url', help='URL of Zendesk (e.g. https://example.zendesk.com)') argp.add_argument('-m', action=UnicodeStore, dest='mail', help='E-Mail address for Zendesk login') argp.add_argument('-p', action=UnicodeStore, dest='password', help='Password for Zendesk login', nargs='?', const=state['password']) argp.add_argument( '-i', '--is-token', action='store_true', dest='is_token', help='Is token? Specify if password supplied a Zendesk token') # Set argparse defaults with program defaults. # Skip password and list_zdf as they are argparse const, not argparse default argp.set_defaults(**dict((k, v) for k, v in state.iteritems() if k != 'password' and k != 'list_zdf')) # Read ~/.zdf2pdf.cfg [zdf2pdf] section and update argparse defaults try: config_state( os.path.expanduser('~') + '/.zdf2pdf.cfg', 'zdf2pdf', state) # Skip list_zdf because it is not a config file value, not an argparse # const value, and we don't want to lose it by overwriting it with None. # Password is OK now, because we either have one from the config file or # it is still None. argp.set_defaults(**dict( (k, v) for k, v in state.iteritems() if k != 'list_zdf')) except configparser.NoSectionError: # -c CONFIG_FILE did not have a [zdf2pdf] section. Skip it. pass # Parse the command line options if argv is None: argv = sys.argv args = argp.parse_args() # Update the program state with command line options for k in state.keys(): state[k] = getattr(args, k) # -c CONFIG_FILE given on command line read args.config_file [zdf2pdf], update state if args.config_file: if state['verbose']: print('Reading config file {}'.format(args.config_file)) try: config_state(args.config_file, 'zdf2pdf', state) except configparser.NoSectionError: # -c CONFIG_FILE did not have a [zdf2pdf] section. Skip it. pass # -r RUN_SECTION given if args.run_section: if state['verbose']: print('Searching for {} in ~/.zdf2pdf'.format(args.run_section)) section_found = False try: config_state( os.path.expanduser('~') + '/.zdf2pdf.cfg', args.run_section, state) section_found = True if state['verbose']: print('Found {} in ~/.zdf2pdf'.format(args.run_section)) except configparser.NoSectionError: # ~/.zdf2pdf.cfg did not have this section. Hope it's found later. pass # -c CONFIG_FILE and -r RUN_SECTION given if args.config_file: if state['verbose']: print('Searching for {} in {}'.format(args.run_section, args.config_file)) try: config_state(args.config_file, args.run_section, state) section_found = True if state['verbose']: print('Found {} in {}'.format(args.run_section, args.config_file)) except configparser.NoSectionError: # CONFIG_FILE did not have this section. pass # If the section wasn't found, print an error and exit if not section_found: print('Error: Run section {} was not found'.format( args.run_section)) return 1 if state['categories'] or state['topics'] or state['forums'] or state[ 'list_zdf']: from zendesk import Zendesk if state['url'] and state['mail'] and state['password']: if state['verbose']: print( 'Configuring Zendesk with:\n' 'url: {}\n' 'mail: {}\n' 'password: (hidden)\n' 'is_token: {}\n'.format(state['url'], state['mail'], repr(state['is_token']))) zd = Zendesk(state['url'], zendesk_username=state['mail'], zendesk_password=state['password'], use_api_token=state['is_token']) else: msg = textwrap.dedent("""\ Error: Need Zendesk config for requested operation. Use -u, -m, -p options or a config file to provide the information. Config file (e.g. ~/.zdf2pdf.cfg) should be something like: [zdf2pdf] url = https://example.zendesk.com mail = [email protected] password = dneib393fwEF3ifbsEXAMPLEdhb93dw343 is_token = 1 """) print(msg) return 1 # All config options are in, state is set. # Handle any last minute type checking or setting if state['pre_width']: try: state['pre_width'] = int(state['pre_width']) except TypeError: print('Could not convert pre_width {} to integer'.format( repr(state['pre_width']))) return 1 # Log the state if state['verbose']: print('Running with program state:') for k, v in state.iteritems(): print('{} {}'.format(k, repr(v))) if state['list_zdf'] == 'forums': # List available zendesk forums with their IDs and titles and exit. # Listing is formatted like for following: # 12345 Category 1 name # 09876 Forum 1 name # 54321 Forum 2 name # 67890 Category 2 name if state['verbose']: print('Listing all forums') categories = zd.list_categories() for cat in categories['categories']: print('{} {}'.format(cat['id'], cat['name'])) forums = zd.list_category_forums(category_id=cat['id'])['forums'] for forum in forums: print(' {} {}'.format(forum['id'], forum['name'])) return 0 elif state['list_zdf']: if state['verbose']: print('Listing all entries in forum {}'.format(state['list_zdf'])) # List a zendesk forum's entries with their IDs and titles and exit try: forum_id = int(state['list_zdf']) except ValueError: print('Error: Could not convert to integer: {}'.format( state['list_zdf'])) return 1 entries = zd.list_topics(forum_id=state['list_zdf']) for entry in entries['topics']: print('{} {}'.format(entry['id'], entry['title'])) return 0 # Use an entries file on disk if state['json_file']: if state['verbose']: print('Reading entries from {}'.format(state['json_file'])) # Get the entries off disk with open(state['json_file'], 'r') as infile: entries = json.loads(infile.read()) else: entries = [] # Get the entries from one or more zendesk categories if state['categories']: try: cat_ids = [int(i) for i in state['categories'].split(',')] except ValueError: print('Error: Could not convert to integers: {}'.format( state['forums'])) return 1 for cat_id in cat_ids: if state['verbose']: print('Obtaining entries from category {}'.format(cat_id)) cat_entries = [] forums = zd.list_category_forums(category_id=cat_id)['forums'] for forum in forums: if state['verbose']: print('Obtaining entries from forum {}'.format( forum['id'])) # topics = [{entry}, {entry}, ..., {entry}] topics = zd.list_topics(forum_id=forum['id'])['topics'] if state['forum_sections']: cat_entries.append({ 'section': forum['name'], 'id': forum['id'], 'body': forum['description'], 'topics': topics }) # result: # cat_entries = [ # <previous forums,> # { # 'section':'FORUM TITLE', # 'topics': # [ # {entry}, # ... # {entry} # ] # } # ] else: cat_entries += topics # result: # cat_entries = [ # {entry}, # ... # {entry} # ] if state['category_sections']: cat = zd.show_category(category_id=cat_id) entries.append({ 'section': cat['name'], 'id': cat_id, 'body': cat['description'], 'topics': cat_entries }) #od[zd.show_category(category_id=cat_id)['name']] = #entries.append([zd.show_category(category_id=cat_id)['name']] = # result if forum sections: # entries = [ # <previous categories,> # { # 'section': 'CATEGORY TITLE', # 'id': 'CATEGORY ID', # 'body': 'CATEGORY DESCRIPTION', # 'topics': # [ # { # 'section':'FORUM TITLE', # 'topics': # [ # {entry}, # ... # {entry} # ] # } # ] # } # ] # # result if not forum sections: # entries = [ # <previous categories,> # { # 'section':'CATEGORY TITLE', # 'topics': # [ # {entry}, # ... # {entry} # ] # } # ] else: entries += cat_entries # result: whatever cat_entries was (forums as sections or not) # is concatenated onto the end of entries # Get the entries from one or more zendesk forums if state['forums']: try: forum_ids = [int(i) for i in state['forums'].split(',')] for forum_id in forum_ids: if state['verbose']: print('Obtaining entries from forum {}'.format(forum_id)) topics = zd.list_topics(forum_id=forum_id)['topics'] # see above for description of what entries looks like if state['forum_sections']: forum = zd.show_forum(forum_id=forum_id)['forum'] entries.append({ 'section': forum['name'], 'id': forum['id'], 'body': forum['description'], 'topics': topics }) else: entries += topics except ValueError: print('Error: Could not convert to integers: {}'.format( state['forums'])) return 1 # Get individual entries from zendesk if state['topics']: topic_ids = state['topics'].replace(' ', '') topics = zd.show_multiple_topics(topic_ids)['topics'] if state['entries_heading']: entries.append({ 'section': state['entries_heading'], 'topics': topics }) else: entries += topics if len(entries) == 0: # Didn't get entries from any inputs. print("Error: Did not receive any entries.") return 1 zdf2pdf(entries, state) if state['delete']: shutil.rmtree(state['work_dir']) return 0
def main(argv=None): import sys, tempfile, argparse # Log to stdout import logging logging.basicConfig() # Declare a class for an argparse custom action. # Handles converting ascii input from argparse that may contain unicode # to a real unicode string. class UnicodeStore(argparse.Action): def __call__(self, parser, namespace, values, option_string=None): setattr(namespace, self.dest, values.decode('utf-8')) # Options precedence: # program state defaults, which are overridden by # ~/.zdf2pdf.cfg [zdf2pdf] section options, which are overridden by # command line options, which are overridden by # -c CONFIG_FILE [zdf2pdf] section options, which are overridden by # ~/.zdf2pdf.cfg [RUN_SECTION] section options, which are overridden by # -c CONFIG_FILE [RUN_SECTION] section options # # Program state, with defaults # state = { 'verbose': False, 'json_file': None, 'categories': None, 'forums': None, 'topics': None, 'run_section': None, 'list_zdf': 'forums', 'style_file': None, 'output_file': 'PCLOADLETTER.pdf', 'title': None, 'title_class': None, 'author': None, 'date': None, 'copyright': None, 'toc': False, 'toc_class': None, 'toc_title': 'Table of Contents', 'pre_width': None, 'strip_empty': False, 'header': None, 'footer': None, 'category_sections': False, 'forum_sections': False, 'topics_heading': None, 'work_dir': tempfile.gettempdir(), 'delete': False, 'url': None, 'mail': None, 'password': '******', 'is_token': False, } argp = argparse.ArgumentParser( description='Make a PDF from Zendesk forums or entries.') argp.add_argument('-v', '--verbose', action='store_true', help='Verbose output') argp.add_argument('--json-file', action=UnicodeStore, dest='json_file', help='Zendesk entries JSON file to convert to PDF') argp.add_argument('--categories', action=UnicodeStore, dest='categories', help='Comma separated Category IDs to download and convert to PDF') argp.add_argument('--forums', action=UnicodeStore, dest='forums', help='Comma separated Forum IDs to download and convert to PDF') argp.add_argument('--topics', action=UnicodeStore, dest='topics', help='Comma separated Topic (Entry) IDs to download and convert to PDF') argp.add_argument('-r', action=UnicodeStore, dest='run_section', help='Run pre-configured section in configuration file') argp.add_argument('-l', action=UnicodeStore, dest='list_zdf', help="""List a forum's entries by ID and title. If no forum ID is supplied, list forums by ID and title organized by category""", nargs='?', const=state['list_zdf'], metavar='FORUM_TO_LIST') argp.add_argument('-c', action=UnicodeStore, dest='config_file', help='Configuration file (overrides ~/.zdf2pdf.cfg)') argp.add_argument('-s', action=UnicodeStore, dest='style_file', help='Style file (CSS) to <link>') argp.add_argument('-o', action=UnicodeStore, dest='output_file', help='Output filename (default: PCLOADLETTER.pdf)', default=state['output_file']) argp.add_argument('-t', action=UnicodeStore, dest='title', help='Title to be added to the beginning of the PDF') argp.add_argument('-a', action=UnicodeStore, dest='author', help='Author line to be added to the beginning of the PDF') argp.add_argument('--date', action=UnicodeStore, dest='date', help='Date line to be added to the beginning of the PDF') argp.add_argument('--copyright', action=UnicodeStore, dest='copyright', help='Copyright line to be added to the beginning of the PDF') argp.add_argument('--title-class', action=UnicodeStore, dest='title_class', help='CSS class to be added to title page div') argp.add_argument('--toc', action='store_true', dest='toc', help="Generate a Table of Contents (default: false)") argp.add_argument('--toc-title', action=UnicodeStore, dest='toc_title', help="ToC title (default: Table of Contents)") argp.add_argument('--toc-class', action=UnicodeStore, dest='toc_class', help='CSS class to be added to ToC div') argp.add_argument('--pre-width', action=UnicodeStore, dest='pre_width', help='Width to wrap contents of <pre></pre> tags.') argp.add_argument('--strip-empty', action='store_true', dest='strip_empty', help='Strip empty tags. (default: false)') argp.add_argument('--header', action=UnicodeStore, dest='header', help='HTML header to add to the PDF (see docs)') argp.add_argument('--footer', action=UnicodeStore, dest='footer', help='HTML footer to add to the PDF (see docs)') argp.add_argument('--category-sections', action='store_true', dest='category_sections', help='Make categories sections (default: false)') argp.add_argument('--forum-sections', action='store_true', dest='category_sections', help='Make categories sections (default: false)') argp.add_argument('--topics-heading', action=UnicodeStore, dest='topics_heading', help='Heading to put at start of topics retrieved individually') argp.add_argument('-w', action=UnicodeStore, dest='work_dir', help="""Working directory in which to store JSON output and images (default: temp dir)""") argp.add_argument('-d', '--delete', action='store_true', dest='delete', help="""Delete working directory at program exit (default: do not delete)""") argp.add_argument('-u', action=UnicodeStore, dest='url', help='URL of Zendesk (e.g. https://example.zendesk.com)') argp.add_argument('-m', action=UnicodeStore, dest='mail', help='E-Mail address for Zendesk login') argp.add_argument('-p', action=UnicodeStore, dest='password', help='Password for Zendesk login', nargs='?', const=state['password']) argp.add_argument('-i', '--is-token', action='store_true', dest='is_token', help='Is token? Specify if password supplied a Zendesk token') # Set argparse defaults with program defaults. # Skip password and list_zdf as they are argparse const, not argparse default argp.set_defaults(**dict((k, v) for k, v in state.iteritems() if k != 'password' and k != 'list_zdf')) # Read ~/.zdf2pdf.cfg [zdf2pdf] section and update argparse defaults try: config_state(os.path.expanduser('~') + '/.zdf2pdf.cfg', 'zdf2pdf', state) # Skip list_zdf because it is not a config file value, not an argparse # const value, and we don't want to lose it by overwriting it with None. # Password is OK now, because we either have one from the config file or # it is still None. argp.set_defaults(**dict((k, v) for k, v in state.iteritems() if k != 'list_zdf')) except configparser.NoSectionError: # -c CONFIG_FILE did not have a [zdf2pdf] section. Skip it. pass # Parse the command line options if argv is None: argv = sys.argv args = argp.parse_args() # Update the program state with command line options for k in state.keys(): state[k] = getattr(args, k) # -c CONFIG_FILE given on command line read args.config_file [zdf2pdf], update state if args.config_file: if state['verbose']: print('Reading config file {}'.format(args.config_file)) try: config_state(args.config_file, 'zdf2pdf', state) except configparser.NoSectionError: # -c CONFIG_FILE did not have a [zdf2pdf] section. Skip it. pass # -r RUN_SECTION given if args.run_section: if state['verbose']: print('Searching for {} in ~/.zdf2pdf'.format(args.run_section)) section_found = False try: config_state(os.path.expanduser('~') + '/.zdf2pdf.cfg', args.run_section, state) section_found = True if state['verbose']: print('Found {} in ~/.zdf2pdf'.format(args.run_section)) except configparser.NoSectionError: # ~/.zdf2pdf.cfg did not have this section. Hope it's found later. pass # -c CONFIG_FILE and -r RUN_SECTION given if args.config_file: if state['verbose']: print('Searching for {} in {}'.format(args.run_section, args.config_file)) try: config_state(args.config_file, args.run_section, state) section_found = True if state['verbose']: print('Found {} in {}'.format(args.run_section, args.config_file)) except configparser.NoSectionError: # CONFIG_FILE did not have this section. pass # If the section wasn't found, print an error and exit if not section_found: print('Error: Run section {} was not found'.format(args.run_section)) return 1 if state['categories'] or state['topics'] or state['forums'] or state['list_zdf']: from zendesk import Zendesk if state['url'] and state['mail'] and state['password']: if state['verbose']: print('Configuring Zendesk with:\n' 'url: {}\n' 'mail: {}\n' 'password: (hidden)\n' 'is_token: {}\n'.format( state['url'], state['mail'], repr(state['is_token']) )) zd = Zendesk(state['url'], zendesk_username = state['mail'], zendesk_password = state['password'], use_api_token = state['is_token']) else: msg = textwrap.dedent("""\ Error: Need Zendesk config for requested operation. Use -u, -m, -p options or a config file to provide the information. Config file (e.g. ~/.zdf2pdf.cfg) should be something like: [zdf2pdf] url = https://example.zendesk.com mail = [email protected] password = dneib393fwEF3ifbsEXAMPLEdhb93dw343 is_token = 1 """) print(msg) return 1 # All config options are in, state is set. # Handle any last minute type checking or setting if state['pre_width']: try: state['pre_width'] = int(state['pre_width']) except TypeError: print('Could not convert pre_width {} to integer'.format( repr(state['pre_width']) )) return 1 # Log the state if state['verbose']: print('Running with program state:') for k, v in state.iteritems(): print('{} {}'.format(k, repr(v))) if state['list_zdf'] == 'forums': # List available zendesk forums with their IDs and titles and exit. # Listing is formatted like for following: # 12345 Category 1 name # 09876 Forum 1 name # 54321 Forum 2 name # 67890 Category 2 name if state['verbose']: print('Listing all forums') categories = zd.list_categories() for cat in categories['categories']: print('{} {}'.format(cat['id'], cat['name'])) forums = zd.list_category_forums(category_id=cat['id'])['forums'] for forum in forums: print(' {} {}'.format(forum['id'], forum['name'])) return 0 elif state['list_zdf']: if state['verbose']: print('Listing all entries in forum {}'.format(state['list_zdf'])) # List a zendesk forum's entries with their IDs and titles and exit try: forum_id = int(state['list_zdf']) except ValueError: print('Error: Could not convert to integer: {}'.format(state['list_zdf'])) return 1 entries = zd.list_topics(forum_id=state['list_zdf']) for entry in entries['topics']: print('{} {}'.format(entry['id'], entry['title'])) return 0 # Use an entries file on disk if state['json_file']: if state['verbose']: print('Reading entries from {}'.format(state['json_file'])) # Get the entries off disk with open(state['json_file'], 'r') as infile: entries = json.loads(infile.read()) else: entries = [] # Get the entries from one or more zendesk categories if state['categories']: try: cat_ids = [int(i) for i in state['categories'].split(',')] except ValueError: print('Error: Could not convert to integers: {}'.format(state['forums'])) return 1 for cat_id in cat_ids: if state['verbose']: print('Obtaining entries from category {}'.format(cat_id)) cat_entries = [] forums = zd.list_category_forums(category_id=cat_id)['forums'] for forum in forums: if state['verbose']: print('Obtaining entries from forum {}'.format(forum['id'])) # topics = [{entry}, {entry}, ..., {entry}] topics = zd.list_topics(forum_id=forum['id'])['topics'] if state['forum_sections']: cat_entries.append({'section': forum['name'], 'id': forum['id'], 'body': forum['description'], 'topics': topics}) # result: # cat_entries = [ # <previous forums,> # { # 'section':'FORUM TITLE', # 'topics': # [ # {entry}, # ... # {entry} # ] # } # ] else: cat_entries += topics # result: # cat_entries = [ # {entry}, # ... # {entry} # ] if state['category_sections']: cat = zd.show_category(category_id=cat_id) entries.append({'section': cat['name'], 'id': cat_id, 'body': cat['description'], 'topics': cat_entries}) #od[zd.show_category(category_id=cat_id)['name']] = #entries.append([zd.show_category(category_id=cat_id)['name']] = # result if forum sections: # entries = [ # <previous categories,> # { # 'section': 'CATEGORY TITLE', # 'id': 'CATEGORY ID', # 'body': 'CATEGORY DESCRIPTION', # 'topics': # [ # { # 'section':'FORUM TITLE', # 'topics': # [ # {entry}, # ... # {entry} # ] # } # ] # } # ] # # result if not forum sections: # entries = [ # <previous categories,> # { # 'section':'CATEGORY TITLE', # 'topics': # [ # {entry}, # ... # {entry} # ] # } # ] else: entries += cat_entries # result: whatever cat_entries was (forums as sections or not) # is concatenated onto the end of entries # Get the entries from one or more zendesk forums if state['forums']: try: forum_ids = [int(i) for i in state['forums'].split(',')] for forum_id in forum_ids: if state['verbose']: print('Obtaining entries from forum {}'.format(forum_id)) topics = zd.list_topics(forum_id=forum_id)['topics'] # see above for description of what entries looks like if state['forum_sections']: forum = zd.show_forum(forum_id=forum_id)['forum'] entries.append({'section': forum['name'], 'id': forum['id'], 'body': forum['description'], 'topics':topics}) else: entries += topics except ValueError: print('Error: Could not convert to integers: {}'.format(state['forums'])) return 1 # Get individual entries from zendesk if state['topics']: topic_ids = state['topics'].replace(' ', '') topics = zd.show_multiple_topics(topic_ids)['topics'] if state['entries_heading']: entries.append({'section':state['entries_heading'], 'topics':topics}) else: entries += topics if len(entries) == 0: # Didn't get entries from any inputs. print("Error: Did not receive any entries.") return 1 zdf2pdf(entries, state) if state['delete']: shutil.rmtree(state['work_dir']) return 0
import requests as r import json as j from trello import Trello from zendesk import Zendesk trl = Trello() zd = Zendesk() def create_ticket_card(id): tk = zd.get_ticket(id) ticket_name = f"{id} - {tk['ticket']['subject']}" ticket_desc = tk['ticket']['description'] response = trl.create_card(ticket_name, ticket_desc) if response == r.codes.ok: print(f'Card do ticket {id} criado com sucesso.')
from zendesk import Zendesk, get_id_from_url import httplib2 import simplejson ################################################################ ## NEW CONNECTION CLIENT ################################################################ zendesk = Zendesk('https://23andme.zendesk.com', '*****@*****.**', 'lC3mXMSIU3aF9z8bsLXZEqNktxz3Q4kbwcBgehN3') ################################################################ ## TICKETS ################################################################ # List zendesk.list_tickets(view_id=1) # Must have a view defined # # Create # new_ticket = { # 'ticket': { # 'requester_name': 'Howard Schultz', # 'requester_email': '*****@*****.**', # 'subject':'My Starbucks coffee is cold!', # 'description': 'please reheat my coffee', # 'set_tags': 'coffee drinks', # 'ticket_field_entries': [ # { # 'ticket_field_id': 1, # 'value': 'venti' # }, # { # 'ticket_field_id': 2,