├── .npmrc ├── .streerc ├── .rubocop.yml ├── .prettierrc.cjs ├── .template-lintrc.cjs ├── stylelint.config.mjs ├── eslint.config.mjs ├── translator.yml ├── Gemfile ├── spec ├── system │ └── core_features_spec.rb ├── jobs │ ├── onceoff │ │ └── migrate_zendesk_autogenerate_categories_site_settings_spec.rb │ └── regular │ │ └── zendesk_job_spec.rb ├── integration │ ├── topic_extensions_spec.rb │ └── zendesk_plugin_spec.rb ├── lib │ └── discourse_zendesk_plugin │ │ └── helper_spec.rb └── requests │ └── sync_controller_spec.rb ├── .github └── workflows │ └── discourse-plugin.yml ├── config ├── locales │ ├── client.en_GB.yml │ ├── client.zh_TW.yml │ ├── client.bg.yml │ ├── client.da.yml │ ├── client.gl.yml │ ├── client.sq.yml │ ├── client.ug.yml │ ├── client.ur.yml │ ├── client.be.yml │ ├── client.cs.yml │ ├── client.el.yml │ ├── client.et.yml │ ├── client.hr.yml │ ├── client.hy.yml │ ├── client.id.yml │ ├── client.lt.yml │ ├── client.nb_NO.yml │ ├── client.ro.yml │ ├── client.sk.yml │ ├── client.sl.yml │ ├── client.sw.yml │ ├── client.te.yml │ ├── client.th.yml │ ├── client.uk.yml │ ├── client.fa_IR.yml │ ├── client.hu.yml │ ├── client.lv.yml │ ├── client.pt.yml │ ├── client.sr.yml │ ├── client.ca.yml │ ├── client.vi.yml │ ├── client.bs_BA.yml │ ├── client.ko.yml │ ├── client.sv.yml │ ├── client.zh_CN.yml │ ├── client.ja.yml │ ├── client.en.yml │ ├── client.he.yml │ ├── client.pl_PL.yml │ ├── client.ar.yml │ ├── client.ru.yml │ ├── client.tr_TR.yml │ ├── client.nl.yml │ ├── client.pt_BR.yml │ ├── client.fi.yml │ ├── client.es.yml │ ├── client.it.yml │ ├── client.de.yml │ └── client.fr.yml └── settings.yml ├── .discourse-compatibility ├── README.md ├── db └── migrate │ ├── 20200707122325_remove_zendesk_sync_site_setting.rb │ ├── 20230113110559_rename_enable_categories_site_settings.rb │ └── 20200917041108_disable_zendesk_plugin_by_default.rb ├── package.json ├── lib └── discourse_zendesk_plugin │ ├── post_extension.rb │ ├── engine.rb │ ├── topic_extension.rb │ └── helper.rb ├── app ├── jobs │ ├── onceoff │ │ └── migrate_zendesk_autogenerate_categories_site_settings.rb │ └── regular │ │ └── zendesk_job.rb └── controllers │ └── discourse_zendesk_plugin │ ├── issues_controller.rb │ └── sync_controller.rb ├── LICENSE ├── .gitignore ├── assets └── javascripts │ └── discourse-zendesk-plugin │ └── connectors │ └── after-topic-footer-buttons │ └── zendesk-topic-actions.gjs ├── plugin.rb └── Gemfile.lock /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict = true 2 | auto-install-peers = false 3 | -------------------------------------------------------------------------------- /.streerc: -------------------------------------------------------------------------------- 1 | --print-width=100 2 | --plugins=plugin/trailing_comma 3 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | inherit_gem: 2 | rubocop-discourse: stree-compat.yml 3 | -------------------------------------------------------------------------------- /.prettierrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = require("@discourse/lint-configs/prettier"); 2 | -------------------------------------------------------------------------------- /.template-lintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = require("@discourse/lint-configs/template-lint"); 2 | -------------------------------------------------------------------------------- /stylelint.config.mjs: -------------------------------------------------------------------------------- 1 | export default { 2 | extends: ["@discourse/lint-configs/stylelint"], 3 | }; 4 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import DiscourseRecommended from "@discourse/lint-configs/eslint"; 2 | 3 | export default [...DiscourseRecommended]; 4 | -------------------------------------------------------------------------------- /translator.yml: -------------------------------------------------------------------------------- 1 | # Configuration file for discourse-translator-bot 2 | 3 | files: 4 | - source_path: config/locales/client.en.yml 5 | destination_path: client.yml 6 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | 5 | group :development do 6 | gem "rubocop-discourse" 7 | gem "syntax_tree" 8 | end 9 | -------------------------------------------------------------------------------- /spec/system/core_features_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe "Core features", type: :system do 4 | before { enable_current_plugin } 5 | 6 | it_behaves_like "having working core features" 7 | end 8 | -------------------------------------------------------------------------------- /.github/workflows/discourse-plugin.yml: -------------------------------------------------------------------------------- 1 | name: Discourse Plugin 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | jobs: 10 | ci: 11 | uses: discourse/.github/.github/workflows/discourse-plugin.yml@v1 12 | -------------------------------------------------------------------------------- /config/locales/client.en_GB.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | en_GB: 8 | -------------------------------------------------------------------------------- /config/locales/client.zh_TW.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | zh_TW: 8 | js: 9 | zendesk: 10 | general_error: 發生錯誤。 11 | -------------------------------------------------------------------------------- /config/locales/client.bg.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | bg: 8 | js: 9 | zendesk: 10 | general_error: Нещо се обърка. 11 | -------------------------------------------------------------------------------- /config/locales/client.da.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | da: 8 | js: 9 | zendesk: 10 | general_error: Noget gik galt. 11 | -------------------------------------------------------------------------------- /config/locales/client.gl.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | gl: 8 | js: 9 | zendesk: 10 | general_error: Algo foi mal. 11 | -------------------------------------------------------------------------------- /config/locales/client.sq.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | sq: 8 | js: 9 | zendesk: 10 | general_error: Diçka shkoi keq. 11 | -------------------------------------------------------------------------------- /config/locales/client.ug.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | ug: 8 | js: 9 | zendesk: 10 | general_error: چاتاق چىقتى. 11 | -------------------------------------------------------------------------------- /config/locales/client.ur.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | ur: 8 | js: 9 | zendesk: 10 | general_error: کچھ غلط ہو گیا۔ 11 | -------------------------------------------------------------------------------- /.discourse-compatibility: -------------------------------------------------------------------------------- 1 | < 3.5.0.beta5-dev: e9e5eaf547376028c62b7bb15c3dbc7b0033e79b 2 | < 3.5.0.beta1-dev: 0d24cfa4aeedc224cb35749341b6188095701c5f 3 | < 3.4.0.beta1-dev: 61680042bd47918964bc70c879936c69241950d8 4 | < 3.3.0.beta1-dev: 9d107d791aaad17f3d99eea98a170efe5e23fe22 5 | 3.1.999: 8f97601fcaad5d3d39e3358b0f7e88a759154e0f 6 | -------------------------------------------------------------------------------- /config/locales/client.be.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | be: 8 | js: 9 | zendesk: 10 | general_error: Нешта пайшло не так. 11 | -------------------------------------------------------------------------------- /config/locales/client.cs.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | cs: 8 | js: 9 | zendesk: 10 | general_error: Něco se pokazilo. 11 | -------------------------------------------------------------------------------- /config/locales/client.el.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | el: 8 | js: 9 | zendesk: 10 | general_error: Κάτι δεν πήγε καλά. 11 | -------------------------------------------------------------------------------- /config/locales/client.et.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | et: 8 | js: 9 | zendesk: 10 | general_error: Miskit läks nihu. 11 | -------------------------------------------------------------------------------- /config/locales/client.hr.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | hr: 8 | js: 9 | zendesk: 10 | general_error: Nešto je pošlo krivo. 11 | -------------------------------------------------------------------------------- /config/locales/client.hy.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | hy: 8 | js: 9 | zendesk: 10 | general_error: 'Ինչ-որ բան այն չէ:' 11 | -------------------------------------------------------------------------------- /config/locales/client.id.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | id: 8 | js: 9 | zendesk: 10 | general_error: Terjadi kesalahan 11 | -------------------------------------------------------------------------------- /config/locales/client.lt.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | lt: 8 | js: 9 | zendesk: 10 | general_error: Kažkas blogai nutiko. 11 | -------------------------------------------------------------------------------- /config/locales/client.nb_NO.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | nb_NO: 8 | js: 9 | zendesk: 10 | general_error: Noe gikk galt. 11 | -------------------------------------------------------------------------------- /config/locales/client.ro.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | ro: 8 | js: 9 | zendesk: 10 | general_error: Ceva nu a funcționat. 11 | -------------------------------------------------------------------------------- /config/locales/client.sk.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | sk: 8 | js: 9 | zendesk: 10 | general_error: Niečo sa pokazilo. 11 | -------------------------------------------------------------------------------- /config/locales/client.sl.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | sl: 8 | js: 9 | zendesk: 10 | general_error: Nekaj je šlo narobe. 11 | -------------------------------------------------------------------------------- /config/locales/client.sw.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | sw: 8 | js: 9 | zendesk: 10 | general_error: Kitu kimeenda vibaya. 11 | -------------------------------------------------------------------------------- /config/locales/client.te.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | te: 8 | js: 9 | zendesk: 10 | general_error: ఏదో తేడా జరిగింది. 11 | -------------------------------------------------------------------------------- /config/locales/client.th.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | th: 8 | js: 9 | zendesk: 10 | general_error: มีบางอย่างผิดปกติ 11 | -------------------------------------------------------------------------------- /config/locales/client.uk.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | uk: 8 | js: 9 | zendesk: 10 | general_error: Щось пішло не так. 11 | -------------------------------------------------------------------------------- /config/locales/client.fa_IR.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | fa_IR: 8 | js: 9 | zendesk: 10 | general_error: اشتباهی روی داد. 11 | -------------------------------------------------------------------------------- /config/locales/client.hu.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | hu: 8 | js: 9 | zendesk: 10 | general_error: Valami rosszul sült el. 11 | -------------------------------------------------------------------------------- /config/locales/client.lv.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | lv: 8 | js: 9 | zendesk: 10 | general_error: Kaut kas nogāja greizi. 11 | -------------------------------------------------------------------------------- /config/locales/client.pt.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | pt: 8 | js: 9 | zendesk: 10 | general_error: Correu algo de errado. 11 | -------------------------------------------------------------------------------- /config/locales/client.sr.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | sr: 8 | js: 9 | zendesk: 10 | general_error: Nešto nije bilo u redu. 11 | -------------------------------------------------------------------------------- /config/locales/client.ca.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | ca: 8 | js: 9 | zendesk: 10 | general_error: Alguna cosa ha anat malament 11 | -------------------------------------------------------------------------------- /config/locales/client.vi.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | vi: 8 | js: 9 | zendesk: 10 | general_error: Có một lỗi gì đó đang xảy ra 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Discourse Zendesk Plugin 2 | 3 | > [!IMPORTANT] 4 | > This plugin has now been bundled into Discourse core. See: https://meta.discourse.org/t/bundling-more-popular-plugins-with-discourse-core/373574 5 | 6 | Zendesk for Discourse 7 | 8 | For more information, please see: https://meta.discourse.org/t/discourse-zendesk-plugin/68005 9 | -------------------------------------------------------------------------------- /config/locales/client.bs_BA.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | bs_BA: 8 | js: 9 | zendesk: 10 | general_error: Nešto je krenulo pogrešno. 11 | -------------------------------------------------------------------------------- /db/migrate/20200707122325_remove_zendesk_sync_site_setting.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class RemoveZendeskSyncSiteSetting < ActiveRecord::Migration[6.0] 4 | def up 5 | execute "DELETE FROM site_settings WHERE name = 'zendesk_sync_enabled'" 6 | execute "DELETE FROM site_settings WHERE name = 'zendesk_signature_regex'" 7 | end 8 | 9 | def down 10 | raise ActiveRecord::IrreversibleMigration 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "devDependencies": { 4 | "@discourse/lint-configs": "2.23.0", 5 | "ember-template-lint": "7.7.0", 6 | "eslint": "9.28.0", 7 | "prettier": "3.5.3", 8 | "stylelint": "16.20.0" 9 | }, 10 | "engines": { 11 | "node": ">= 22", 12 | "npm": "please-use-pnpm", 13 | "yarn": "please-use-pnpm", 14 | "pnpm": "9.x" 15 | }, 16 | "packageManager": "pnpm@9.15.5" 17 | } 18 | -------------------------------------------------------------------------------- /lib/discourse_zendesk_plugin/post_extension.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module DiscourseZendeskPlugin 4 | module PostExtension 5 | def self.prepended(base) 6 | base.after_commit :generate_zendesk_ticket, on: [:create] 7 | end 8 | 9 | private 10 | 11 | def generate_zendesk_ticket 12 | return unless SiteSetting.zendesk_enabled? 13 | return unless DiscourseZendeskPlugin::Helper.autogeneration_category?(topic.category_id) 14 | Jobs.enqueue_in(5.seconds, :zendesk_job, post_id: id) 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/discourse_zendesk_plugin/engine.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module DiscourseZendeskPlugin 4 | class Engine < ::Rails::Engine 5 | engine_name "discourse-zendesk-plugin" 6 | isolate_namespace DiscourseZendeskPlugin 7 | 8 | config.after_initialize do 9 | Discourse::Application.routes.append do 10 | post "/zendesk-plugin/issues" => "discourse_zendesk_plugin/issues#create", 11 | :constraints => StaffConstraint.new 12 | put "/zendesk-plugin/sync" => "discourse_zendesk_plugin/sync#webhook" 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /db/migrate/20230113110559_rename_enable_categories_site_settings.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class RenameEnableCategoriesSiteSettings < ActiveRecord::Migration[7.0] 4 | def setting(old, new) 5 | execute "UPDATE site_settings SET name='#{new}' where name='#{old}'" 6 | end 7 | 8 | def up 9 | setting :zendesk_enable_all_categories, :zendesk_autogenerate_all_categories 10 | setting :zendesk_enabled_categories, :zendesk_autogenerate_categories 11 | end 12 | 13 | def down 14 | setting :zendesk_autogenerate_all_categories, :zendesk_enable_all_categories 15 | setting :zendesk_autogenerate_categories, :zendesk_enabled_categories 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /app/jobs/onceoff/migrate_zendesk_autogenerate_categories_site_settings.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jobs 4 | class MigrateZendeskAutogenerateCategoriesSiteSettings < ::Jobs::Onceoff 5 | def execute_onceoff(_) 6 | site_setting = 7 | SiteSetting.where( 8 | name: "zendesk_autogenerate_categories", 9 | data_type: SiteSettings::TypeSupervisor.types[:list], 10 | ) 11 | 12 | return unless site_setting.exists? 13 | 14 | site_setting = site_setting.first 15 | 16 | site_setting.update!( 17 | data_type: SiteSettings::TypeSupervisor.types[:category_list], 18 | value: Category.where(name: site_setting.value.split("|")).pluck(:id).join("|"), 19 | ) 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /app/controllers/discourse_zendesk_plugin/issues_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module DiscourseZendeskPlugin 4 | class IssuesController < ApplicationController 5 | include ::DiscourseZendeskPlugin::Helper 6 | 7 | requires_plugin ::DiscourseZendeskPlugin::PLUGIN_NAME 8 | 9 | def create 10 | topic = Topic.find(params[:topic_id]) 11 | 12 | if topic.custom_fields[::DiscourseZendeskPlugin::ZENDESK_ID_FIELD].blank? 13 | create_ticket(topic.first_post) 14 | end 15 | 16 | topic_view = ::TopicView.new(topic.id, current_user) 17 | topic_view_serializer = 18 | ::TopicViewSerializer.new(topic_view, scope: topic_view.guardian, root: false) 19 | render_json_dump topic_view_serializer 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /config/settings.yml: -------------------------------------------------------------------------------- 1 | discourse_zendesk: 2 | zendesk_url: 3 | default: https://your-url.zendesk.com/api/v2 4 | client: true 5 | zendesk_enabled: 6 | default: false 7 | client: true 8 | zendesk_jobs_api_token: 9 | default: "" 10 | secret: true 11 | zendesk_jobs_email: 12 | default: "" 13 | zendesk_autogenerate_all_categories: 14 | default: false 15 | zendesk_autogenerate_categories: 16 | type: category_list 17 | client: false 18 | default: "" 19 | zendesk_job_push_all_posts: 20 | default: true 21 | zendesk_job_push_only_author_posts: 22 | default: false 23 | sync_comments_from_zendesk: 24 | default: false 25 | zendesk_incoming_webhook_token: 26 | default: "" 27 | zendesk_tags: 28 | type: list 29 | client: false 30 | default: "" 31 | -------------------------------------------------------------------------------- /db/migrate/20200917041108_disable_zendesk_plugin_by_default.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class DisableZendeskPluginByDefault < ActiveRecord::Migration[6.0] 4 | def change 5 | # enable Zendesk plugin for sites that have configured Zendesk API token 6 | zendesk_email = 7 | DB.query_single("SELECT value FROM site_settings WHERE name = 'zendesk_jobs_email'").first 8 | zendesk_api_token = 9 | DB.query_single("SELECT value FROM site_settings WHERE name = 'zendesk_jobs_api_token'").first 10 | DB.exec(<<~SQL) if zendesk_email.present? && zendesk_api_token.present? 11 | INSERT INTO site_settings (name, data_type, value, created_at, updated_at) 12 | VALUES ('zendesk_enabled', 5, 't', now(), now()) 13 | ON CONFLICT (name) 14 | DO NOTHING 15 | SQL 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/jobs/onceoff/migrate_zendesk_autogenerate_categories_site_settings_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rails_helper" 4 | 5 | RSpec.describe Jobs::MigrateZendeskAutogenerateCategoriesSiteSettings do 6 | it "should migrate the site settings correctly" do 7 | category = Fabricate(:category) 8 | category_2 = Fabricate(:category) 9 | 10 | site_setting = 11 | SiteSetting.create!( 12 | name: "zendesk_autogenerate_categories", 13 | data_type: SiteSettings::TypeSupervisor.types[:list], 14 | value: "#{category.name}|#{category_2.name}|some random name", 15 | ) 16 | 17 | described_class.new.execute_onceoff({}) 18 | 19 | expect(site_setting.reload.data_type).to eq(SiteSettings::TypeSupervisor.types[:category_list]) 20 | 21 | expect(site_setting.value.split("|").map(&:to_i)).to contain_exactly(category.id, category_2.id) 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/discourse_zendesk_plugin/topic_extension.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module DiscourseZendeskPlugin 4 | module TopicExtension 5 | def self.prepended(base) 6 | base.after_update :publish_to_zendesk 7 | end 8 | 9 | private 10 | 11 | def publish_to_zendesk 12 | return if saved_changes[:category_id].blank? 13 | 14 | old_category = Category.find_by(id: saved_changes[:category_id].first) 15 | new_category = Category.find_by(id: saved_changes[:category_id].last) 16 | 17 | old_cat_enabled = DiscourseZendeskPlugin::Helper.autogeneration_category?(old_category&.id) 18 | new_cat_enabled = DiscourseZendeskPlugin::Helper.autogeneration_category?(new_category&.id) 19 | 20 | # Do nothing if neither old or new category are enabled 21 | return nil if !old_cat_enabled && !new_cat_enabled 22 | 23 | # Do nothing if both categories are enabled 24 | return nil if old_cat_enabled && new_cat_enabled 25 | 26 | # enqueue job in future since after commit does not maintain changes hash 27 | Jobs.enqueue_in(5.seconds, :zendesk_job, topic_id: id) 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Civilized Discourse Construction Kit, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /spec/integration/topic_extensions_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rails_helper" 4 | 5 | describe "TopicExtensions" do 6 | before { freeze_time } 7 | 8 | fab!(:user_1) { Fabricate(:user, admin: true) } 9 | fab!(:topic_1) { Fabricate(:topic, user: user_1) } 10 | 11 | context "when an enabled category is set on the topic" do 12 | fab!(:category_1) { Fabricate(:category) } 13 | 14 | before { SiteSetting.zendesk_autogenerate_categories = "#{category_1.id}" } 15 | 16 | it "queues a sync job" do 17 | expect_enqueued_with( 18 | job: :zendesk_job, 19 | args: { 20 | topic_id: topic_1.id, 21 | }, 22 | at: Time.zone.now + 5.seconds, 23 | ) do 24 | topic_1.category = category_1 25 | topic_1.save! 26 | end 27 | end 28 | 29 | context "when the category is removed" do 30 | before do 31 | topic_1.category = category_1 32 | topic_1.save! 33 | end 34 | 35 | it "queues a sync job" do 36 | expect_enqueued_with( 37 | job: :zendesk_job, 38 | args: { 39 | topic_id: topic_1.id, 40 | }, 41 | at: Time.zone.now + 5.seconds, 42 | ) do 43 | topic_1.category = nil 44 | topic_1.save! 45 | end 46 | end 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | /.config 4 | /coverage/ 5 | /InstalledFiles 6 | /pkg/ 7 | /spec/reports/ 8 | /spec/examples.txt 9 | /test/tmp/ 10 | /test/version_tmp/ 11 | /tmp/ 12 | /gems/ 13 | # Used by dotenv library to load environment variables. 14 | # .env 15 | 16 | ## Specific to RubyMotion: 17 | .dat* 18 | .repl_history 19 | build/ 20 | *.bridgesupport 21 | build-iPhoneOS/ 22 | build-iPhoneSimulator/ 23 | 24 | ## Specific to RubyMotion (use of CocoaPods): 25 | # 26 | # We recommend against adding the Pods directory to your .gitignore. However 27 | # you should judge for yourself, the pros and cons are mentioned at: 28 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 29 | # 30 | # vendor/Pods/ 31 | 32 | ## Documentation cache and generated files: 33 | /.yardoc/ 34 | /_yardoc/ 35 | /doc/ 36 | /rdoc/ 37 | 38 | ## Environment normalization: 39 | /.bundle/ 40 | /vendor/bundle 41 | /lib/bundler/man/ 42 | 43 | # for a library or gem, you might want to ignore these files since the code is 44 | # intended to run in multiple environments; otherwise, check them in: 45 | # Gemfile.lock 46 | # .ruby-version 47 | # .ruby-gemset 48 | 49 | # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: 50 | .rvmrc 51 | node_modules 52 | yarn-error.log 53 | .rubocop-https---raw-githubusercontent-com-discourse-* 54 | -------------------------------------------------------------------------------- /config/locales/client.ko.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | ko: 8 | js: 9 | topic: 10 | create_zendesk_issue: "Zendesk 티켓 만들기" 11 | view_zendesk_issue: "Zendesk에서 보기" 12 | zendesk: 13 | credentials_not_setup: Zendesk 자격 증명을 설정하지 않았습니다. 14 | url_not_set: Zendesk URL이 설정되지 않았습니다. 15 | general_error: 문제가 발생했습니다. 16 | site_settings: 17 | zendesk_url: 'Zendesk URL. 예: https://YOUR_SUBDOMAIN.zendesk.com/api/v2' 18 | zendesk_enabled: Zendesk 플러그인 활성화 19 | zendesk_autogenerate_all_categories: 모든 카테고리에 대한 티켓을 자동으로 생성합니다. 활성화된 경우 `zendesk_autogenerate_categories`는 무시됩니다. 20 | zendesk_autogenerate_categories: Zendesk 티켓을 자동으로 생성하는 카테고리 목록입니다. 21 | zendesk_jobs_api_token: Zendesk 인증을 위한 API 토큰 22 | zendesk_jobs_email: Zendesk 인증에 사용할 이메일 23 | zendesk_job_push_all_posts: 토픽의 모든 게시물을 Zendesk 댓글로 푸시할까요? 비활성화하면 첫 번째 게시물만 Zendesk로 푸시됩니다. 24 | zendesk_job_push_only_author_posts: 원본 주제 작성자의 게시물만 zendesk로 푸시합니다. `zendesk_job_push_all_posts`가 활성화된 경우에만 적용됩니다. 25 | sync_comments_from_zendesk: Zendesk에서 Discourse로 댓글을 자동 게시합니다. zendesk_autogenerate_categories`의 주제에만 적용됩니다. 26 | zendesk_incoming_webhook_token: Zendesk 발신 웹훅에서 페이로드로 전송된 토큰입니다. `sync_comments_from_zendesk`가 활성화된 경우에만 필요합니다. 27 | zendesk_tags: 각 티켓에 추가할 태그 목록 28 | -------------------------------------------------------------------------------- /config/locales/client.sv.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | sv: 8 | js: 9 | topic: 10 | create_zendesk_issue: "Skapa Zendesk-ärende" 11 | view_zendesk_issue: "Visa på Zendesk" 12 | zendesk: 13 | credentials_not_setup: Du har inte konfigurerat dina Zendesk-autentiseringsuppgifter. 14 | url_not_set: Din Zendesk-URL har för tillfället inte ställts in. 15 | general_error: Något gick fel. 16 | site_settings: 17 | zendesk_url: Din Zendesk-URL, t.ex. https://YOUR_SUBDOMAIN.zendesk.com/api/v2 18 | zendesk_enabled: Aktivera Zendesk-tillägget 19 | zendesk_jobs_api_token: API-token för Zendesk-autentisering 20 | zendesk_jobs_email: E-postadress som ska användas för Zendesk-autentisering 21 | zendesk_job_push_all_posts: Gör alla inlägg i ett ämne till Zendesk-kommentarer? Om detta inaktiveras kommer endast det första inlägget att flyttas till Zendesk. 22 | zendesk_job_push_only_author_posts: Flytta bara inlägg av den ursprungliga ämnesförfattaren till Zendesk. Gäller endast om `zendesk_job_push_all_posts` har aktiverats 23 | zendesk_incoming_webhook_token: Token som skickas i nyttolasten från Zendesks utgående webhook. Krävs endast om `sync_comments_from_zendesk` har aktiverats. 24 | zendesk_tags: Lista över taggar att lägga till i varje ärende 25 | -------------------------------------------------------------------------------- /config/locales/client.zh_CN.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | zh_CN: 8 | admin_js: 9 | admin: 10 | site_settings: 11 | categories: 12 | discourse_zendesk: "Discourse Zendesk" 13 | js: 14 | topic: 15 | create_zendesk_issue: "创建 Zendesk 工单" 16 | view_zendesk_issue: "在 Zendesk 上查看" 17 | zendesk: 18 | credentials_not_setup: 您尚未设置 Zendesk 凭据。 19 | url_not_set: 您的 Zendesk URL 当前未设置。 20 | general_error: 出了点问题。 21 | site_settings: 22 | zendesk_url: 您的 Zendesk URL,例如 https://YOUR_SUBDOMAIN.zendesk.com/api/v2 23 | zendesk_enabled: 启用 Zendesk 插件 24 | zendesk_autogenerate_all_categories: 自动为所有类别创建工单。如果启用,则忽略 `zendesk_autogenerate_categories`。 25 | zendesk_autogenerate_categories: 将自动创建 Zendesk 工单的类别列表。 26 | zendesk_jobs_api_token: Zendesk 身份验证的 API 令牌 27 | zendesk_jobs_email: Zendesk 身份验证的电子邮件 28 | zendesk_job_push_all_posts: 将话题中的所有帖子作为 Zendesk 评论推送?如果禁用,则只有第一个帖子将被推送到 Zendesk。 29 | zendesk_job_push_only_author_posts: 仅将原始话题作者的帖子推送到 Zendesk。仅在启用 `zendesk_job_push_all_posts` 时适用 30 | sync_comments_from_zendesk: 将 Zendesk 的评论自动发布到 Discourse。仅适用于 `zendesk_autogenerate_categories` 中的话题。 31 | zendesk_incoming_webhook_token: Zendesk 传出网络钩子在有效负载中发送的令牌。仅在启用 `sync_comments_from_zendesk` 时才需要。 32 | zendesk_tags: 添加到每张工单的标签列表 33 | -------------------------------------------------------------------------------- /config/locales/client.ja.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | ja: 8 | admin_js: 9 | admin: 10 | site_settings: 11 | categories: 12 | discourse_zendesk: "Discourse Zendesk" 13 | js: 14 | topic: 15 | create_zendesk_issue: "Zendesk チケットを作成" 16 | view_zendesk_issue: "Zendesk で表示" 17 | zendesk: 18 | credentials_not_setup: Zendesk の資格情報がセットアップされていません。 19 | url_not_set: あなたの Zendesk URL は現在設定されていません。 20 | general_error: エラーが発生しました。 21 | site_settings: 22 | zendesk_url: 'あなたの Zendesk URL。例: https://YOUR_SUBDOMAIN.zendesk.com/api/v2' 23 | zendesk_enabled: Zendesk プラグインを有効にする 24 | zendesk_autogenerate_all_categories: すべてのカテゴリのチケットを自動的に作成します。有効にすると、`zendesk_autogenerate_categories` は無視されます。 25 | zendesk_autogenerate_categories: Zendesk チケットが自動生成されるカテゴリのリスト。 26 | zendesk_jobs_api_token: Zendesk 認証用の API トークン 27 | zendesk_jobs_email: Zendesk 認証用のメールアドレス 28 | zendesk_job_push_all_posts: トピック内のすべての投稿を Zendesk コメントとしてプッシュしますか?無効にすると、最初の投稿のみが Zendesk にプッシュされます。 29 | zendesk_job_push_only_author_posts: 元のトピック作成者による投稿のみを Zendesk にプッシュしますか?`zendesk_job_push_all_posts` が有効である場合にのみ適用されます。 30 | sync_comments_from_zendesk: Zendesk から Discourse にコメントを自動投稿します。`zendesk_autogenerate_categories` のトピックのみに適用されます。 31 | zendesk_incoming_webhook_token: Zendesk の発信 Webhook からペイロードで送信されるトークン。`sync_comments_from_zendesk` が有効である場合にのみ必要です。 32 | zendesk_tags: 各チケットに追加されるタグのリスト 33 | -------------------------------------------------------------------------------- /config/locales/client.en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | admin_js: 3 | admin: 4 | site_settings: 5 | categories: 6 | discourse_zendesk: "Discourse Zendesk" 7 | js: 8 | topic: 9 | create_zendesk_issue: "Create Zendesk Ticket" 10 | view_zendesk_issue: "View on Zendesk" 11 | zendesk: 12 | credentials_not_setup: You haven't setup your Zendesk credentials. 13 | url_not_set: Your Zendesk URL is not currently set. 14 | general_error: Something went wrong. 15 | site_settings: 16 | zendesk_url: Your Zendesk URL. e.g. https://YOUR_SUBDOMAIN.zendesk.com/api/v2 17 | zendesk_enabled: Enable Zendesk plugin 18 | zendesk_autogenerate_all_categories: Automatically create tickets for all categories. If enabled, `zendesk_autogenerate_categories` is ignored. 19 | zendesk_autogenerate_categories: List of categories that will automatically create Zendesk tickets. 20 | zendesk_jobs_api_token: API token for Zendesk authentication 21 | zendesk_jobs_email: Email to use for Zendesk authentication 22 | zendesk_job_push_all_posts: Push all posts in a topic as Zendesk comments? If disabled only first post will be pushed to Zendesk. 23 | zendesk_job_push_only_author_posts: Only push posts by the original topic author to zendesk. Only applies if `zendesk_job_push_all_posts` is enabled 24 | sync_comments_from_zendesk: Autopost comments from Zendesk to Discourse. Only applies for topics in `zendesk_autogenerate_categories`. 25 | zendesk_incoming_webhook_token: Token sent in the payload from the Zendesk outgoing webhook. Required only if `sync_comments_from_zendesk` is enabled. 26 | zendesk_tags: List of tags to add to each ticket 27 | -------------------------------------------------------------------------------- /app/jobs/regular/zendesk_job.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jobs 4 | class ZendeskJob < ::Jobs::Base 5 | sidekiq_options backtrace: true 6 | include ::DiscourseZendeskPlugin::Helper 7 | 8 | def execute(args) 9 | return unless SiteSetting.zendesk_enabled? 10 | return if SiteSetting.zendesk_jobs_email.blank? || SiteSetting.zendesk_jobs_api_token.blank? 11 | 12 | if args[:post_id].present? 13 | push_post!(args[:post_id]) 14 | elsif args[:topic_id].present? 15 | push_topic!(args[:topic_id]) 16 | end 17 | end 18 | 19 | private 20 | 21 | def push_topic!(topic_id) 22 | topic = Topic.find_by(id: topic_id) 23 | return if !DiscourseZendeskPlugin::Helper.autogeneration_category?(topic.category_id) 24 | if topic.present? && 25 | DiscourseZendeskPlugin::Helper.autogeneration_category?(topic.category_id) 26 | topic.post_ids.each { |post_id| push_post!(post_id) } 27 | end 28 | end 29 | 30 | def push_post!(post_id) 31 | post = Post.find_by(id: post_id) 32 | return if !post || post.user_id < 1 33 | return if post.custom_fields[::DiscourseZendeskPlugin::ZENDESK_ID_FIELD].present? 34 | return if !DiscourseZendeskPlugin::Helper.autogeneration_category?(post.topic.category_id) 35 | return if !SiteSetting.zendesk_job_push_all_posts? && post.post_number > 1 36 | 37 | ticket_id = post.topic.custom_fields[::DiscourseZendeskPlugin::ZENDESK_ID_FIELD] 38 | if ticket_id.present? 39 | add_comment(post, ticket_id) if comment_eligible_for_sync?(post) 40 | else 41 | create_ticket(post) 42 | end 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /config/locales/client.he.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | he: 8 | admin_js: 9 | admin: 10 | site_settings: 11 | categories: 12 | discourse_zendesk: "Discourse Zendesk" 13 | js: 14 | topic: 15 | create_zendesk_issue: "יצירת כרטיס ב־Zendesk" 16 | view_zendesk_issue: "הצגה ב־Zendesk" 17 | zendesk: 18 | credentials_not_setup: לא הגדרת את פרטי הגישה שלך ל־Zendeks. 19 | url_not_set: כתובת ה־Zendesk שלך לא מוגדרת כרגע. 20 | general_error: משהו השתבש. 21 | site_settings: 22 | zendesk_url: 'כתובת ה־Zendesk שלך. למשל: https://YOUR_SUBDOMAIN.zendesk.com/api/v2' 23 | zendesk_enabled: הפעלת תוסף Zendesk 24 | zendesk_autogenerate_all_categories: ליצור אוטומטית בקשות לכל הקטגוריות. אם האפשרות פעיל, אז `zendesk_autogenerate_categories` לא תקף. 25 | zendesk_autogenerate_categories: רשימת קטגוריות שייצרו אוטומטית בקשות ב־Zendesk. 26 | zendesk_jobs_api_token: אסימון API לאימות Zendesk 27 | zendesk_jobs_email: דוא״ל לשימוש לאימות Zendesk 28 | zendesk_job_push_all_posts: לדחוף את כל הפוסטים בנושא כהערות ל־Zendesk? אם האפשרות מושבתת רק הפוסט הראשון שלך יידחף ל־Zendesk. 29 | zendesk_job_push_only_author_posts: לדחוף רק פוסטים מהיוצר המקורי ל־Zendesk. תקף רק אם `zendesk_job_push_all_posts` פעיל. 30 | sync_comments_from_zendesk: לפרסם תגובות מ־Zendesk ל־Discourse באופן אוטומטי. תקף רק לנושאים תחת `zendesk_autogenerate_categories`. 31 | zendesk_incoming_webhook_token: אסימון נשלח במטען מההתלייה היוצאת מ־Zendesk. נחוץ רק אם `sync_comments_from_zendesk` פעיל. 32 | zendesk_tags: רשימת תגיות להוספה לכל כרטיס 33 | -------------------------------------------------------------------------------- /config/locales/client.pl_PL.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | pl_PL: 8 | js: 9 | topic: 10 | create_zendesk_issue: "Utwórz zgłoszenie Zendesk" 11 | view_zendesk_issue: "Zobacz w Zendesk" 12 | zendesk: 13 | credentials_not_setup: Nie skonfigurowałeś swoich danych uwierzytelniających Zendesk. 14 | url_not_set: Twój adres URL Zendesk nie jest obecnie ustawiony. 15 | general_error: Coś poszło nie tak. 16 | site_settings: 17 | zendesk_url: Twój adres URL Zendesk. np. https://TWOJA_SUBDOMENA.zendesk.com/api/v2 18 | zendesk_enabled: Włącz wtyczkę Zendesk 19 | zendesk_autogenerate_all_categories: Automatycznie twórz zgłoszenia dla wszystkich kategorii. Jeśli włączone, `zendesk_autogenerate_categories` jest ignorowane. 20 | zendesk_autogenerate_categories: Lista kategorii, które będą automatycznie tworzyć zgłoszenia Zendesk. 21 | zendesk_jobs_api_token: Token API do uwierzytelniania Zendesk 22 | zendesk_jobs_email: E-mail używany do uwierzytelniania Zendesk 23 | zendesk_job_push_all_posts: Przesłać wszystkie posty w temacie jako komentarze Zendesk? Jeśli wyłączone, tylko pierwszy post zostanie wysłany do Zendesk. 24 | zendesk_job_push_only_author_posts: Wysyłaj do zendesk tylko posty oryginalnego autora tematu. Dotyczy tylko sytuacji, gdy włączona jest opcja `zendesk_job_push_all_posts`. 25 | sync_comments_from_zendesk: Autopostuj komentarze z Zendesk do Discourse. Dotyczy tylko tematów w `zendesk_autogenerate_categories`. 26 | zendesk_incoming_webhook_token: Token wysłany w zapytaniu z wychodzącego webhooka Zendesk. Wymagane tylko wtedy, gdy włączone jest `sync_comments_from_zendesk`. 27 | zendesk_tags: Lista tagów do dodania do każdego zgłoszenia 28 | -------------------------------------------------------------------------------- /spec/lib/discourse_zendesk_plugin/helper_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rails_helper" 4 | 5 | describe DiscourseZendeskPlugin::Helper do 6 | subject(:dummy) { Class.new { extend DiscourseZendeskPlugin::Helper } } 7 | 8 | it "Instantiates" do 9 | expect(dummy).to be_present 10 | end 11 | 12 | describe "comment_eligible_for_sync?" do 13 | subject(:eligible) { dummy.comment_eligible_for_sync?(post) } 14 | 15 | let!(:topic_user) { Fabricate(:user) } 16 | let!(:other_user) { Fabricate(:user) } 17 | let(:post_user) { topic_user } 18 | let!(:topic) { Fabricate(:topic, user: topic_user) } 19 | let!(:post) { Fabricate(:post, topic: topic, user: post_user) } 20 | let(:zendesk_job_push_only_author_posts) { true } 21 | 22 | before { SiteSetting.zendesk_job_push_only_author_posts = zendesk_job_push_only_author_posts } 23 | 24 | context "with zendesk_job_push_only_author_posts disabled" do 25 | let(:zendesk_job_push_only_author_posts) { false } 26 | 27 | context "with same author" do 28 | it "should be true" do 29 | expect(eligible).to be_truthy 30 | end 31 | end 32 | 33 | context "with different author" do 34 | let(:post_user) { other_user } 35 | it "should be true" do 36 | expect(eligible).to be_truthy 37 | end 38 | end 39 | end 40 | 41 | context "with zendesk_job_push_only_author_posts enabled" do 42 | let(:zendesk_job_push_only_author_posts) { true } 43 | 44 | context "with same author" do 45 | it "should be true" do 46 | expect(eligible).to be_truthy 47 | end 48 | end 49 | 50 | context "with different author" do 51 | let(:post_user) { other_user } 52 | it "should be false" do 53 | expect(eligible).to be_falsey 54 | end 55 | end 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /config/locales/client.ar.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | ar: 8 | admin_js: 9 | admin: 10 | site_settings: 11 | categories: 12 | discourse_zendesk: "Discourse Zendesk" 13 | js: 14 | topic: 15 | create_zendesk_issue: "إنشاء تذكرة Zendesk" 16 | view_zendesk_issue: "العرض على Zendesk" 17 | zendesk: 18 | credentials_not_setup: لم تقم بإعداد بيانات اعتماد Zendesk الخاصة بك. 19 | url_not_set: لم يتم تحديد عنوان URL لـ Zendesk حاليًا. 20 | general_error: حدث خطأ ما. 21 | site_settings: 22 | zendesk_url: 'عنوان URL لـ Zendesk. على سبيل المثال: https://YOUR_SUBDOMAIN.zendesk.com/api/v2' 23 | zendesk_enabled: تفعيل المكوِّن الإضافي Zendesk 24 | zendesk_autogenerate_all_categories: إنشاء تذاكر تلقائيًا لجميع الفئات. يتم تجاهل `zendesk_enabled_categories` في حال التفعيل. 25 | zendesk_autogenerate_categories: قائمة بالفئات التي تُنشئ تذاكر Zendesk تلقائيًا. 26 | zendesk_jobs_api_token: الرمز المميَّز لواجهة برمجة التطبيقات لمصادقة Zendesk 27 | zendesk_jobs_email: البريد الإلكتروني الذي سيتم استخدامه في مصادقة Zendesk 28 | zendesk_job_push_all_posts: هل تريد إرسال جميع المنشورات في موضوع كتعليقات Zendesk؟ إذا تم إيقاف هذا الخيار، فسيتم إرسال المنشور الأول فقط إلى Zendesk. 29 | zendesk_job_push_only_author_posts: إرسال المنشورات بواسطة المؤلف الأصلي فقط إلى Zendesk. ينطبق ذلك فقط في حال تفعيل `zendesk_job_push_all_posts` 30 | sync_comments_from_zendesk: نشر التعليقات تلقائيًا من Zendesk إلى Discourse. ينطبق ذلك فقط للموضوعات في `zendesk_autogenerate_categories`. 31 | zendesk_incoming_webhook_token: الرمز المميَّز المرسل في الحمولة من خطاف ويب Zendesk الصادر. مطلوب فقط إذا كان `sync_comments_from_zendesk` مفعلًا. 32 | zendesk_tags: قائمة الوسوم التي سيتم إضافتها إلى كل تذكرة 33 | -------------------------------------------------------------------------------- /config/locales/client.ru.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | ru: 8 | admin_js: 9 | admin: 10 | site_settings: 11 | categories: 12 | discourse_zendesk: "Zendesk для Discourse" 13 | js: 14 | topic: 15 | create_zendesk_issue: "Создать тикет в Zendesk" 16 | view_zendesk_issue: "Посмотреть на Zendesk" 17 | zendesk: 18 | credentials_not_setup: Вы не настроили учётные данные Zendesk. 19 | url_not_set: URL-адрес Zendesk не установлен. 20 | general_error: Ошибка. 21 | site_settings: 22 | zendesk_url: URL-адрес Zendesk. Например, https://ВАШ_ПОДДОМЕН.zendesk.com/api/v2 23 | zendesk_enabled: Включить плагин Zendesk 24 | zendesk_autogenerate_all_categories: Автоматическое создание тикетов для всех категорий. Если включено, `zendesk_autogenerate_categories` игнорируется. 25 | zendesk_autogenerate_categories: Список категорий, в которых будут автоматически создаваться тикеты Zendesk. 26 | zendesk_jobs_api_token: Токен API для аутентификации Zendesk 27 | zendesk_jobs_email: Электронная почта для аутентификации Zendesk 28 | zendesk_job_push_all_posts: Отправлять все записи в теме как комментарии Zendesk? Если отключено, в Zendesk будет отправлена только первая запись. 29 | zendesk_job_push_only_author_posts: Отправлять в Zendesk только записи исходного автора темы. Применяется, только если включен параметр `zendesk_job_push_all_posts`. 30 | sync_comments_from_zendesk: Автопубликация комментариев из Zendesk в Discourse. Относится только к темам в `zendesk_autogenerate_categories`. 31 | zendesk_incoming_webhook_token: Токен, отправляемый в сопровождение из исходящего вебхука Zendesk. Требуется, только если включен параметр `sync_comments_from_zendesk`. 32 | zendesk_tags: Список тегов, которые нужно добавить к каждому тикету 33 | -------------------------------------------------------------------------------- /spec/requests/sync_controller_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rails_helper" 4 | 5 | RSpec.describe DiscourseZendeskPlugin::SyncController do 6 | describe "#webhook" do 7 | let!(:token) { "secret-token" } 8 | let!(:topic) { Fabricate(:topic) } 9 | 10 | before do 11 | SiteSetting.zendesk_enabled = true 12 | SiteSetting.sync_comments_from_zendesk = true 13 | SiteSetting.zendesk_incoming_webhook_token = token 14 | end 15 | 16 | it "raises an error when the token is missing" do 17 | put "/zendesk-plugin/sync.json" 18 | expect(response.status).to eq(400) 19 | end 20 | 21 | it "raises an error when the token is invalid" do 22 | put "/zendesk-plugin/sync.json", params: { token: "token" } 23 | expect(response.status).to eq(403) 24 | end 25 | 26 | it "raises an error if the plugin is disabled" do 27 | SiteSetting.zendesk_enabled = false 28 | put "/zendesk-plugin/sync.json", params: { token: token } 29 | expect(response.status).to eq(404) 30 | end 31 | 32 | it "raises an error if `sync_comments_from_zendesk` is disabled" do 33 | SiteSetting.sync_comments_from_zendesk = false 34 | put "/zendesk-plugin/sync.json", params: { token: token } 35 | expect(response.status).to eq(422) 36 | end 37 | 38 | it "raises an error if required parameters are missing" do 39 | put "/zendesk-plugin/sync.json", params: { token: token, topic_id: topic.id } 40 | expect(response.status).to eq(400) 41 | end 42 | 43 | it "raises an error when topic is not present" do 44 | topic.destroy! 45 | put "/zendesk-plugin/sync.json", params: { token: token, topic_id: topic.id, ticket_id: 12 } 46 | expect(response.status).to eq(400) 47 | end 48 | 49 | it "returns 204 when the request succeeds" do 50 | put "/zendesk-plugin/sync.json", params: { token: token, topic_id: topic.id, ticket_id: 12 } 51 | expect(response.status).to eq(204) 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /config/locales/client.tr_TR.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | tr_TR: 8 | admin_js: 9 | admin: 10 | site_settings: 11 | categories: 12 | discourse_zendesk: "Discourse Zendesk" 13 | js: 14 | topic: 15 | create_zendesk_issue: "Zendesk Bileti Oluştur" 16 | view_zendesk_issue: "Zendesk'te görüntüle" 17 | zendesk: 18 | credentials_not_setup: Zendesk giriş bilgilerinizi ayarlamadınız. 19 | url_not_set: Zendesk URL'niz şu anda ayarlı değil. 20 | general_error: Bir şeyler ters gitti. 21 | site_settings: 22 | zendesk_url: Zendesk URL'niz. ör. https://YOUR_SUBDOMAIN.zendesk.com/api/v2 23 | zendesk_enabled: Zendesk eklentisini etkinleştirin 24 | zendesk_autogenerate_all_categories: Tüm kategoriler için otomatik olarak bilet oluştur. Etkinleştirilirse `zendesk_autogenerate_categories` yok sayılır. 25 | zendesk_autogenerate_categories: Otomatik olarak Zendesk biletleri oluşturacak kategorilerin listesi. 26 | zendesk_jobs_api_token: Zendesk kimlik doğrulaması için API token'ı 27 | zendesk_jobs_email: Zendesk kimlik doğrulaması için kullanılacak e-posta 28 | zendesk_job_push_all_posts: Bir konudaki tüm gönderiler Zendesk yorumları olarak itilsin mi? Devre dışı bırakılırsa yalnızca ilk gönderi Zendesk'e gönderilir. 29 | zendesk_job_push_only_author_posts: Yalnızca asıl konu yazarının gönderilerini zendesk'e itin. Yalnızca "zendesk_job_push_all_posts" etkinleştirilmişse geçerlidir 30 | sync_comments_from_zendesk: Zendesk'ten Discourse'a otomatik yorum gönderin. Yalnızca `zendesk_autogenerate_categories` içindeki konular için geçerlidir. 31 | zendesk_incoming_webhook_token: Zendesk giden web kancasından gelen yükte gönderilen token. Yalnızca "sync_comments_from_zendesk" etkinleştirilmişse gereklidir. 32 | zendesk_tags: Her bir bilete eklenecek etiketlerin listesi 33 | -------------------------------------------------------------------------------- /config/locales/client.nl.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | nl: 8 | admin_js: 9 | admin: 10 | site_settings: 11 | categories: 12 | discourse_zendesk: "Discourse Zendesk" 13 | js: 14 | topic: 15 | create_zendesk_issue: "Zendesk-ticket maken" 16 | view_zendesk_issue: "Weergeven op Zendesk" 17 | zendesk: 18 | credentials_not_setup: Je hebt je Zendesk-aanmeldgegevens niet ingesteld. 19 | url_not_set: Je Zendesk-URL is momenteel niet ingesteld. 20 | general_error: Er is iets misgegaan. 21 | site_settings: 22 | zendesk_url: Je Zendesk URL, bijvoorbeeld https://JOUW_SUBDOMEIN.zendesk.com/api/v2 23 | zendesk_enabled: Zendesk-plug-in inschakelen 24 | zendesk_autogenerate_all_categories: Maak automatisch tickets voor alle categorieën. Indien ingeschakeld, wordt 'zendesk_enabled_categories' genegeerd. 25 | zendesk_autogenerate_categories: Lijst van categorieën die automatisch Zendesk-tickets maken. 26 | zendesk_jobs_api_token: API-token voor Zendesk-verificatie 27 | zendesk_jobs_email: Te gebruiken e-mailadres voor Zendesk-verificatie 28 | zendesk_job_push_all_posts: Alle berichten in een topic pushen als Zendesk-opmerkingen? Indien uitgeschakeld, wordt alleen het eerste bericht naar Zendesk gepusht. 29 | zendesk_job_push_only_author_posts: Push alleen berichten van de oorspronkelijke topicauteur naar Zendesk. Alleen van toepassing als `zendesk_job_push_all_posts` is ingeschakeld 30 | sync_comments_from_zendesk: Reacties van Zendesk automatisch op Discourse plaatsen. Alleen van toepassing op topics in `zendesk_autogenerate_categories`. 31 | zendesk_incoming_webhook_token: Token verzonden in de payload van de uitgaande Zendesk-webhook. Alleen vereist als `sync_comments_from_zendesk` is ingeschakeld. 32 | zendesk_tags: Lijst van tags om toe te voegen aan elk ticket 33 | -------------------------------------------------------------------------------- /config/locales/client.pt_BR.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | pt_BR: 8 | admin_js: 9 | admin: 10 | site_settings: 11 | categories: 12 | discourse_zendesk: "Discourse Zendesk" 13 | js: 14 | topic: 15 | create_zendesk_issue: "Criar tíquete da Zendesk" 16 | view_zendesk_issue: "Ver na Zendesk" 17 | zendesk: 18 | credentials_not_setup: Você não configurou suas credenciais da Zendesk. 19 | url_not_set: Sua URL da Zendesk ainda não foi configurada. 20 | general_error: Algo deu errado. 21 | site_settings: 22 | zendesk_url: 'Sua URL da Zendesk, por exemplo: https://YOUR_SUBDOMAIN.zendesk.com/api/v2' 23 | zendesk_enabled: Ativar plugin da Zendesk 24 | zendesk_autogenerate_all_categories: Crie tíquetes automaticamente para todas as categorias. Se estiver ativado, "zendesk_autogenerate_categories" será ignorado. 25 | zendesk_autogenerate_categories: Lista de categorias que criarão automaticamente tíquetes da Zendesk. 26 | zendesk_jobs_api_token: Token de API para autenticação da Zendesk 27 | zendesk_jobs_email: E-mail para usar na autenticação da Zendesk 28 | zendesk_job_push_all_posts: Enviar todas as publicações em um tópico como comentários da Zendesk. Senão só a primeira postagem será enviada para a Zendesk. 29 | zendesk_job_push_only_author_posts: Enviar apenas postagens do(a) autor(a) do tópico original para a Zendesk. Aplicado apenas se "zendesk_job_push_all_posts" estiver ativado 30 | sync_comments_from_zendesk: Poste automaticamente comentários da Zendesk no Discourse. Aplicado apenas para tópicos em "zendesk_autogenerate_categories". 31 | zendesk_incoming_webhook_token: Token enviado no conteúdo do wekhook de saúda da Zendesk. Necessário apenas se "sync_comments_from_zendesk" estiver ativado. 32 | zendesk_tags: Lista de etiquetas para adicionar a cada tíquete 33 | -------------------------------------------------------------------------------- /config/locales/client.fi.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | fi: 8 | admin_js: 9 | admin: 10 | site_settings: 11 | categories: 12 | discourse_zendesk: "Discourse Zendesk" 13 | js: 14 | topic: 15 | create_zendesk_issue: "Luo Zendesk-palvelupyyntö" 16 | view_zendesk_issue: "Näytä Zendeskissä" 17 | zendesk: 18 | credentials_not_setup: Et ole määrittänyt Zendesk-tunnistetietojasi. 19 | url_not_set: Zendeskin URL-osoitettasi ei ole asetettu. 20 | general_error: Jokin meni vikaan. 21 | site_settings: 22 | zendesk_url: Zendeskin URL-osoitteesi. Esim. https://VERKKOTUNNUKSESI.zendesk.com/api/v2 23 | zendesk_enabled: Ota Zendesk-lisäosa käyttöön 24 | zendesk_autogenerate_all_categories: Luo palvelupyynnöt kaikista kategorioista automaattisesti. Jos tämä on käytössä, ei "zendesk_enabled_categories"-asetusta huomioida. 25 | zendesk_autogenerate_categories: Listaus kategorioista, joista luodaan palvelupyynnöt Zendeskiin automaattisesti. 26 | zendesk_jobs_api_token: API-tunnus Zendesk-todennusta varten 27 | zendesk_jobs_email: Sähköpostiosoite Zendesk-todennusta varten 28 | zendesk_job_push_all_posts: Työnnetäänkö kaikki ketjun viestit Zendesk-kommentteina? Jos tämä ei ole käytössä, vain ensimmäinen viesti työnnetään Zendeskiin. 29 | zendesk_job_push_only_author_posts: Työnnä vain ketjun alkuperäisen kirjoittajan viestit Zendeskiin. Pätee vain, jos "zendesk_job_push_all_posts" on käytössä. 30 | sync_comments_from_zendesk: Julkaise Zendesk-kommentit automaattisesti Discoursessa. Koskee vain "zendesk_enabled_categories"-asetuksen aiheita. 31 | zendesk_incoming_webhook_token: Tunnus, joka lähetetään tiedoissa Zendeskin lähtevästä webhookista. Vaaditaan vain, jos "sync_comments_from_zendesk" on käytössä. 32 | zendesk_tags: Luettelo jokaiseen palvelupyyntöön lisättävistä tunnisteista 33 | -------------------------------------------------------------------------------- /config/locales/client.es.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | es: 8 | admin_js: 9 | admin: 10 | site_settings: 11 | categories: 12 | discourse_zendesk: "Zendesk de Discourse" 13 | js: 14 | topic: 15 | create_zendesk_issue: "Crear una incidencia en Zendesk" 16 | view_zendesk_issue: "Ver en Zendesk" 17 | zendesk: 18 | credentials_not_setup: No has configurado tus credenciales de Zendesk. 19 | url_not_set: Tu URL de Zendesk no está configurada actualmente. 20 | general_error: Algo salió mal. 21 | site_settings: 22 | zendesk_url: Tu URL de Zendesk. Por ejemplo, https://TU_SUBDOMINIO.zendesk.com/api/v2 23 | zendesk_enabled: Activar el plugin de Zendesk 24 | zendesk_autogenerate_all_categories: Crear automáticamente incidencias para todas las categorías. Si está habilitado, se ignora `zendesk_autogenerate_categories`. 25 | zendesk_autogenerate_categories: Lista de categorías que crearán automáticamente incidencias de Zendesk. 26 | zendesk_jobs_api_token: Token de la API para la autenticación de Zendesk 27 | zendesk_jobs_email: Correo electrónico que se utilizará para la autenticación de Zendesk 28 | zendesk_job_push_all_posts: '¿Enviar todos los mensajes en un tema como comentarios de Zendesk? Si está deshabilitado solo se enviará el primer mensaje a Zendesk.' 29 | zendesk_job_push_only_author_posts: Enviar a Zendesk solo los mensajes del autor del tema original. Solo se aplica si `zendesk_job_push_all_posts` está activado 30 | sync_comments_from_zendesk: Publicar automáticamente los comentarios de Zendesk en Discourse. Solo se aplica a los temas en `zendesk_autogenerate_categories`. 31 | zendesk_incoming_webhook_token: Token enviado en la carga útil del webhook de salida de Zendesk. Solo es necesario si `sync_comments_from_zendesk` está activado. 32 | zendesk_tags: Lista de etiquetas para añadir a cada incidencia 33 | -------------------------------------------------------------------------------- /app/controllers/discourse_zendesk_plugin/sync_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module DiscourseZendeskPlugin 4 | class SyncController < ApplicationController 5 | include ::DiscourseZendeskPlugin::Helper 6 | 7 | requires_plugin ::DiscourseZendeskPlugin::PLUGIN_NAME 8 | 9 | layout false 10 | before_action :zendesk_token_valid?, only: :webhook 11 | skip_before_action :check_xhr, 12 | :preload_json, 13 | :verify_authenticity_token, 14 | :redirect_to_login_if_required, 15 | only: :webhook 16 | 17 | def webhook 18 | unless SiteSetting.zendesk_enabled? && SiteSetting.sync_comments_from_zendesk 19 | return render json: failed_json, status: 422 20 | end 21 | 22 | ticket_id = params[:ticket_id] 23 | raise Discourse::InvalidParameters.new(:ticket_id) if ticket_id.blank? 24 | topic = Topic.find_by_id(params[:topic_id]) 25 | raise Discourse::InvalidParameters.new(:topic_id) if topic.blank? 26 | return if !DiscourseZendeskPlugin::Helper.autogeneration_category?(topic.category_id) 27 | 28 | user = User.find_by_email(params[:email]) || Discourse.system_user 29 | latest_comment = get_latest_comment(ticket_id) 30 | if latest_comment.present? 31 | existing_comment = 32 | PostCustomField.where( 33 | name: ::DiscourseZendeskPlugin::ZENDESK_ID_FIELD, 34 | value: latest_comment.id, 35 | ).first 36 | 37 | if existing_comment.blank? 38 | post = topic.posts.create!(user: user, raw: latest_comment.body) 39 | update_post_custom_fields(post, latest_comment) 40 | end 41 | end 42 | 43 | render json: {}, status: 204 44 | end 45 | 46 | private 47 | 48 | def zendesk_token_valid? 49 | params.require(:token) 50 | 51 | if SiteSetting.zendesk_incoming_webhook_token.blank? || 52 | SiteSetting.zendesk_incoming_webhook_token != params[:token] 53 | raise Discourse::InvalidAccess.new 54 | end 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /config/locales/client.it.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | it: 8 | admin_js: 9 | admin: 10 | site_settings: 11 | categories: 12 | discourse_zendesk: "Discourse Zendesk" 13 | js: 14 | topic: 15 | create_zendesk_issue: "Crea ticket Zendesk" 16 | view_zendesk_issue: "Visualizza su Zendesk" 17 | zendesk: 18 | credentials_not_setup: Non hai configurato le tue credenziali Zendesk. 19 | url_not_set: Il tuo URL Zendesk non è attualmente configurato. 20 | general_error: Qualcosa è andato storto. 21 | site_settings: 22 | zendesk_url: Il tuo URL Zendesk. Ad esempio, https://TUO_SOTTODOMINIO.zendesk.com/api/v2 23 | zendesk_enabled: Abilita plug-in Zendesk 24 | zendesk_autogenerate_all_categories: Crea automaticamente ticket per tutte le categorie. Se abilitato, "zendesk_autogenerate_categories" viene ignorato. 25 | zendesk_autogenerate_categories: Elenco di categorie che genereranno automaticamente i ticket Zendesk. 26 | zendesk_jobs_api_token: Token API per l'autenticazione Zendesk 27 | zendesk_jobs_email: E-mail da utilizzare per l'autenticazione Zendesk 28 | zendesk_job_push_all_posts: Inviare tutti i messaggi in un argomento come commenti Zendesk? Se l'opzione è disabilitata, solo il primo messaggio verrà inviato a Zendesk. 29 | zendesk_job_push_only_author_posts: Invia solo i messaggi dell'autore originale dell'argomento su zendesk. L'opzione si applica solo se l'impostazione `zendesk_job_push_all_posts` è abilitata 30 | sync_comments_from_zendesk: Invia automaticamente i commenti da Zendesk a Discourse. L'opzione si applica solo agli argomenti nelle categorie definite da "zendesk_autogenerate_categories". 31 | zendesk_incoming_webhook_token: Token inviato nel payload dal webhook in uscita di Zendesk. Impostazione richiesta solo se l'opzione `sync_comments_from_zendesk` è abilitata. 32 | zendesk_tags: Elenco di etichette da aggiungere a ogni ticket 33 | -------------------------------------------------------------------------------- /config/locales/client.de.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | de: 8 | admin_js: 9 | admin: 10 | site_settings: 11 | categories: 12 | discourse_zendesk: "Discourse – Zendesk" 13 | js: 14 | topic: 15 | create_zendesk_issue: "Zendesk-Ticket erstellen" 16 | view_zendesk_issue: "Auf Zendesk anzeigen" 17 | zendesk: 18 | credentials_not_setup: Du hast deine Zendesk-Anmeldedaten nicht eingerichtet. 19 | url_not_set: Deine Zendesk-URL ist derzeit nicht festgelegt. 20 | general_error: Etwas ist schiefgelaufen. 21 | site_settings: 22 | zendesk_url: 'Deine Zendesk-URL. Zum Beispiel: https://YOUR_SUBDOMAIN.zendesk.com/api/v2' 23 | zendesk_enabled: Zendesk-Plug-in aktivieren 24 | zendesk_autogenerate_all_categories: Erstelle automatisch Tickets für alle Kategorien. Wenn diese Option aktiviert ist, wird `zendesk_autogenerate_categories` ignoriert. 25 | zendesk_autogenerate_categories: Liste der Kategorien, aus denen automatisch Zendesk-Tickets erstellt werden. 26 | zendesk_jobs_api_token: API-Token für die Zendesk-Authentifizierung 27 | zendesk_jobs_email: E-Mail-Adresse für die Zendesk-Authentifizierung 28 | zendesk_job_push_all_posts: Alle Beiträge in einem Thema als Zendesk-Kommentare weiterleiten? Wenn diese Option deaktiviert ist, wird nur der erste Beitrag an Zendesk weitergeleitet. 29 | zendesk_job_push_only_author_posts: Nur Beiträge des ursprünglichen Themen-Verfassers an Zendesk weiterleiten. Gilt nur, wenn `zendesk_job_push_all_posts` aktiviert ist 30 | sync_comments_from_zendesk: Kommentare von Zendesk automatisch auf Discourse veröffentlichen. Gilt nur für Themen in `zendesk_autogenerate_categories`. 31 | zendesk_incoming_webhook_token: Token, das im Payload vom ausgehenden Zendesk-Webhook gesendet wird. Nur erforderlich, wenn `sync_comments_from_zendesk` aktiviert ist. 32 | zendesk_tags: Liste der Schlagwörter, die zu jedem Ticket hinzugefügt werden 33 | -------------------------------------------------------------------------------- /config/locales/client.fr.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Never edit this file. 2 | # It will be overwritten when translations are pulled from Crowdin. 3 | # 4 | # To work with us on translations, join this project: 5 | # https://translate.discourse.org/ 6 | 7 | fr: 8 | admin_js: 9 | admin: 10 | site_settings: 11 | categories: 12 | discourse_zendesk: "Discourse Zendesk" 13 | js: 14 | topic: 15 | create_zendesk_issue: "Créer un ticket Zendesk" 16 | view_zendesk_issue: "Afficher sur Zendesk" 17 | zendesk: 18 | credentials_not_setup: Vous n'avez pas configuré vos informations d'identification Zendesk. 19 | url_not_set: Votre adresse URL Zendesk n'est pas définie actuellement. 20 | general_error: Une erreur est survenue. 21 | site_settings: 22 | zendesk_url: Votre adresse URL Zendesk. P. ex., https://YOUR_SUBDOMAIN.zendesk.com/api/v2 23 | zendesk_enabled: Activer l'extension Zendesk 24 | zendesk_autogenerate_all_categories: Créez automatiquement des tickets pour toutes les catégories. Si cette option est activée, « zendesk_autogenerate_categories » est ignoré. 25 | zendesk_autogenerate_categories: Liste des catégories qui créeront automatiquement des tickets Zendesk. 26 | zendesk_jobs_api_token: Jeton API pour l'authentification Zendesk 27 | zendesk_jobs_email: E-mail à utiliser pour l'authentification Zendesk 28 | zendesk_job_push_all_posts: Transférer tous les messages d'un sujet en tant que commentaires Zendesk ? Si ce paramètre est désactivé, seul le premier message sera envoyé à Zendesk. 29 | zendesk_job_push_only_author_posts: Envoyer uniquement les messages de l'auteur du sujet d'origine à zendesk. S'applique uniquement si « zendesk_job_push_all_posts » est activé 30 | sync_comments_from_zendesk: Publier automatiquement les commentaires de Zendesk sur Discourse. Ne s'applique que pour les sujets présents dans « zendesk_autogenerate_categories ». 31 | zendesk_incoming_webhook_token: Jeton envoyé dans les données depuis le webhook sortant de Zendesk. Obligatoire uniquement si « sync_comments_from_zendesk » est activé. 32 | zendesk_tags: Liste des étiquettes à ajouter à chaque ticket 33 | -------------------------------------------------------------------------------- /assets/javascripts/discourse-zendesk-plugin/connectors/after-topic-footer-buttons/zendesk-topic-actions.gjs: -------------------------------------------------------------------------------- 1 | import Component from "@ember/component"; 2 | import { action } from "@ember/object"; 3 | import { classNames, tagName } from "@ember-decorators/component"; 4 | import DButton from "discourse/components/d-button"; 5 | import { ajax } from "discourse/lib/ajax"; 6 | import { i18n } from "discourse-i18n"; 7 | 8 | @tagName("span") 9 | @classNames("after-topic-footer-buttons-outlet", "zendesk-topic-actions") 10 | export default class ZendeskTopicActions extends Component { 11 | init() { 12 | super.init(...arguments); 13 | const zendesk_id = this.topic.get("discourse_zendesk_plugin_zendesk_id"); 14 | if (zendesk_id && zendesk_id !== "") { 15 | this.set("zendesk_id", zendesk_id); 16 | } 17 | this.setProperties({ 18 | zendesk_url: this.topic.get("discourse_zendesk_plugin_zendesk_url"), 19 | valid_zendesk_credential: this.get( 20 | "currentUser.discourse_zendesk_plugin_status" 21 | ), 22 | }); 23 | } 24 | 25 | @action 26 | createZendeskIssue() { 27 | let self = this; 28 | self.set("dirty", true); 29 | ajax("/zendesk-plugin/issues", { 30 | type: "POST", 31 | data: { 32 | topic_id: this.get("topic").get("id"), 33 | }, 34 | }).then((topic) => { 35 | self.setProperties({ 36 | zendesk_id: topic.discourse_zendesk_plugin_zendesk_id, 37 | zendesk_url: topic.discourse_zendesk_plugin_zendesk_url, 38 | }); 39 | }); 40 | } 41 | 42 | 68 | } 69 | -------------------------------------------------------------------------------- /plugin.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # name: discourse-zendesk-plugin 4 | # about: Allows the creation of Zendesk tickets from Discourse topics. 5 | # meta_topic_id: 68005 6 | # version: 1.0.1 7 | # authors: Yana Agun Siswanto, Arpit Jalan 8 | # url: https://github.com/discourse/discourse-zendesk-plugin 9 | 10 | gem "inflection", "1.0.0" 11 | 12 | if Gem::Version.new(Faraday::VERSION) >= Gem::Version.new("2.0") 13 | gem "multipart-post", "2.2.3", require_name: "net/http/post/multipart" 14 | gem "faraday-multipart", "1.0.4", require_name: "faraday/multipart" 15 | gem "zendesk_api", "1.38.0.rc1" 16 | end 17 | 18 | enabled_site_setting :zendesk_enabled 19 | 20 | module ::DiscourseZendeskPlugin 21 | PLUGIN_NAME = "discourse-zendesk-plugin" 22 | 23 | ZENDESK_ID_FIELD = "discourse_zendesk_plugin_zendesk_id" 24 | ZENDESK_URL_FIELD = "discourse_zendesk_plugin_zendesk_url" 25 | ZENDESK_API_URL_FIELD = "discourse_zendesk_plugin_zendesk_api_url" 26 | end 27 | 28 | require_relative "lib/discourse_zendesk_plugin/engine" 29 | require_relative "lib/discourse_zendesk_plugin/helper" 30 | 31 | after_initialize do 32 | require_relative "app/jobs/onceoff/migrate_zendesk_autogenerate_categories_site_settings" 33 | require_relative "app/jobs/regular/zendesk_job" 34 | require_relative "lib/discourse_zendesk_plugin/post_extension" 35 | require_relative "lib/discourse_zendesk_plugin/topic_extension" 36 | 37 | reloadable_patch do |plugin| 38 | Post.prepend DiscourseZendeskPlugin::PostExtension 39 | Topic.prepend DiscourseZendeskPlugin::TopicExtension 40 | end 41 | 42 | add_to_serializer( 43 | :topic_view, 44 | ::DiscourseZendeskPlugin::ZENDESK_ID_FIELD.to_sym, 45 | respect_plugin_enabled: false, 46 | ) { object.topic.custom_fields[::DiscourseZendeskPlugin::ZENDESK_ID_FIELD] } 47 | 48 | add_to_serializer( 49 | :topic_view, 50 | ::DiscourseZendeskPlugin::ZENDESK_URL_FIELD.to_sym, 51 | respect_plugin_enabled: false, 52 | ) do 53 | id = object.topic.custom_fields[::DiscourseZendeskPlugin::ZENDESK_ID_FIELD] 54 | uri = URI.parse(SiteSetting.zendesk_url) 55 | "#{uri.scheme}://#{uri.host}/agent/tickets/#{id}" 56 | end 57 | 58 | add_to_serializer(:current_user, :discourse_zendesk_plugin_status) do 59 | SiteSetting.zendesk_jobs_email.present? && SiteSetting.zendesk_jobs_api_token.present? && 60 | SiteSetting.zendesk_url 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | activesupport (8.0.2) 5 | base64 6 | benchmark (>= 0.3) 7 | bigdecimal 8 | concurrent-ruby (~> 1.0, >= 1.3.1) 9 | connection_pool (>= 2.2.5) 10 | drb 11 | i18n (>= 1.6, < 2) 12 | logger (>= 1.4.2) 13 | minitest (>= 5.1) 14 | securerandom (>= 0.3) 15 | tzinfo (~> 2.0, >= 2.0.5) 16 | uri (>= 0.13.1) 17 | ast (2.4.3) 18 | base64 (0.3.0) 19 | benchmark (0.4.1) 20 | bigdecimal (3.2.0) 21 | concurrent-ruby (1.3.5) 22 | connection_pool (2.5.3) 23 | drb (2.2.3) 24 | i18n (1.14.7) 25 | concurrent-ruby (~> 1.0) 26 | json (2.12.2) 27 | language_server-protocol (3.17.0.5) 28 | lint_roller (1.1.0) 29 | logger (1.7.0) 30 | minitest (5.25.5) 31 | parallel (1.27.0) 32 | parser (3.3.8.0) 33 | ast (~> 2.4.1) 34 | racc 35 | prettier_print (1.2.1) 36 | prism (1.4.0) 37 | racc (1.8.1) 38 | rack (3.1.16) 39 | rainbow (3.1.1) 40 | regexp_parser (2.10.0) 41 | rubocop (1.75.8) 42 | json (~> 2.3) 43 | language_server-protocol (~> 3.17.0.2) 44 | lint_roller (~> 1.1.0) 45 | parallel (~> 1.10) 46 | parser (>= 3.3.0.2) 47 | rainbow (>= 2.2.2, < 4.0) 48 | regexp_parser (>= 2.9.3, < 3.0) 49 | rubocop-ast (>= 1.44.0, < 2.0) 50 | ruby-progressbar (~> 1.7) 51 | unicode-display_width (>= 2.4.0, < 4.0) 52 | rubocop-ast (1.44.1) 53 | parser (>= 3.3.7.2) 54 | prism (~> 1.4) 55 | rubocop-capybara (2.22.1) 56 | lint_roller (~> 1.1) 57 | rubocop (~> 1.72, >= 1.72.1) 58 | rubocop-discourse (3.12.1) 59 | activesupport (>= 6.1) 60 | lint_roller (>= 1.1.0) 61 | rubocop (>= 1.73.2) 62 | rubocop-capybara (>= 2.22.0) 63 | rubocop-factory_bot (>= 2.27.0) 64 | rubocop-rails (>= 2.30.3) 65 | rubocop-rspec (>= 3.0.1) 66 | rubocop-rspec_rails (>= 2.31.0) 67 | rubocop-factory_bot (2.27.1) 68 | lint_roller (~> 1.1) 69 | rubocop (~> 1.72, >= 1.72.1) 70 | rubocop-rails (2.32.0) 71 | activesupport (>= 4.2.0) 72 | lint_roller (~> 1.1) 73 | rack (>= 1.1) 74 | rubocop (>= 1.75.0, < 2.0) 75 | rubocop-ast (>= 1.44.0, < 2.0) 76 | rubocop-rspec (3.6.0) 77 | lint_roller (~> 1.1) 78 | rubocop (~> 1.72, >= 1.72.1) 79 | rubocop-rspec_rails (2.31.0) 80 | lint_roller (~> 1.1) 81 | rubocop (~> 1.72, >= 1.72.1) 82 | rubocop-rspec (~> 3.5) 83 | ruby-progressbar (1.13.0) 84 | securerandom (0.4.1) 85 | syntax_tree (6.2.0) 86 | prettier_print (>= 1.2.0) 87 | tzinfo (2.0.6) 88 | concurrent-ruby (~> 1.0) 89 | unicode-display_width (3.1.4) 90 | unicode-emoji (~> 4.0, >= 4.0.4) 91 | unicode-emoji (4.0.4) 92 | uri (1.0.3) 93 | 94 | PLATFORMS 95 | ruby 96 | 97 | DEPENDENCIES 98 | rubocop-discourse 99 | syntax_tree 100 | 101 | BUNDLED WITH 102 | 2.6.9 103 | -------------------------------------------------------------------------------- /spec/integration/zendesk_plugin_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rails_helper" 4 | RSpec.describe "Discourse Zendesk Plugin" do 5 | let(:staff) { Fabricate(:moderator) } 6 | let(:zendesk_url_default) { "https://your-url.zendesk.com/api/v2" } 7 | let(:zendesk_api_ticket_url) { zendesk_url_default + "/tickets" } 8 | 9 | let(:ticket_response) { { ticket: { id: "ticket_id", url: "ticket_url" } }.to_json } 10 | 11 | before do 12 | default_header = { "Content-Type" => "application/json; charset=UTF-8" } 13 | stub_request(:post, zendesk_api_ticket_url).to_return( 14 | status: 200, 15 | body: ticket_response, 16 | headers: default_header, 17 | ) 18 | stub_request(:get, zendesk_url_default + "/users/me").to_return( 19 | status: 200, 20 | body: { user: {} }.to_json, 21 | headers: default_header, 22 | ) 23 | end 24 | 25 | describe "Plugin Settings" do 26 | describe "Storage Preparation" do 27 | let(:zendesk_enabled_default) { false } 28 | 29 | it "has zendesk_url & zendesk_enabled site settings" do 30 | expect(SiteSetting.zendesk_url).to eq(zendesk_url_default) 31 | expect(SiteSetting.zendesk_enabled).to eq(zendesk_enabled_default) 32 | end 33 | end 34 | 35 | describe "zendesk_job_push_only_author_posts?" do 36 | it "disabled by default" do 37 | expect(SiteSetting.zendesk_job_push_only_author_posts?).to be_falsey 38 | end 39 | end 40 | end 41 | 42 | describe "Zendesk Integration" do 43 | describe "Create ticket" do 44 | let!(:topic) { Fabricate(:topic) } 45 | let!(:p1) { Fabricate(:post, topic: topic) } 46 | let(:zendesk_api_user_search_url) do 47 | zendesk_url_default + "/users/search?query=#{p1.user.email}" 48 | end 49 | let(:zendesk_api_user_create_url) { zendesk_url_default + "/users" } 50 | 51 | before do 52 | SiteSetting.zendesk_enabled = true 53 | sign_in staff 54 | default_header = { "Content-Type" => "application/json; charset=UTF-8" } 55 | stub_request(:get, zendesk_api_ticket_url + "/ticket_id/comments").to_return(status: 200) 56 | stub_request(:get, zendesk_api_user_search_url).to_return( 57 | status: 200, 58 | body: { user: {} }.to_json, 59 | headers: default_header, 60 | ) 61 | stub_request(:post, zendesk_api_user_create_url).to_return( 62 | status: 200, 63 | body: { user: { id: 24 } }.to_json, 64 | headers: default_header, 65 | ) 66 | end 67 | 68 | it "creates a new zendesk ticket" do 69 | post "/zendesk-plugin/issues.json", params: { topic_id: topic.id } 70 | 71 | expect(WebMock).to have_requested(:post, zendesk_api_ticket_url).with { |req| 72 | body = JSON.parse(req.body) 73 | body["ticket"]["submitter_id"] == 24 && body["ticket"]["priority"] == "normal" && 74 | body["ticket"]["custom_fields"].find do |field| 75 | field["imported_from"].present? && field["external_id"].present? && 76 | field["imported_by"] == "discourse_zendesk_plugin" 77 | end 78 | } 79 | 80 | custom_fields = topic.reload.custom_fields 81 | 82 | expect(custom_fields["discourse_zendesk_plugin_zendesk_api_url"]).to eq("ticket_url") 83 | expect(custom_fields["discourse_zendesk_plugin_zendesk_id"]).to eq("ticket_id") 84 | end 85 | end 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /spec/jobs/regular/zendesk_job_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rails_helper" 4 | 5 | RSpec.describe Jobs::ZendeskJob do 6 | subject(:execute) { job.execute(job_args) } 7 | 8 | let(:job) { described_class.new } 9 | 10 | let(:topic_user) { Fabricate(:user) } 11 | let(:other_user) { Fabricate(:user) } 12 | let(:post_user) { topic_user } 13 | let(:topic) do 14 | Fabricate(:topic, user: topic_user).tap do |topic| 15 | topic.custom_fields[::DiscourseZendeskPlugin::ZENDESK_ID_FIELD] = ticket_id 16 | topic.custom_fields[::DiscourseZendeskPlugin::ZENDESK_API_URL_FIELD] = ticket_url 17 | topic.save_custom_fields 18 | end 19 | end 20 | let(:ticket_id) { "1234" } 21 | let(:ticket_url) { "http://example.com/ticket/#{ticket_id}" } 22 | let(:post) { Fabricate(:post, topic: topic, user: post_user, post_number: 2) } 23 | let(:zendesk_job_push_only_author_posts) { false } 24 | let(:zendesk_job_push_all_posts) { true } 25 | let(:zendesk_enabled) { false } 26 | let(:zendesk_jobs_email) { "test@example.com" } 27 | let(:zendesk_jobs_api_token) { "1234567890" } 28 | 29 | let(:job_args) { { post_id: post.id } } 30 | 31 | before do 32 | SiteSetting.zendesk_job_push_only_author_posts = zendesk_job_push_only_author_posts 33 | SiteSetting.zendesk_enabled = zendesk_enabled 34 | SiteSetting.zendesk_jobs_email = zendesk_jobs_email 35 | SiteSetting.zendesk_jobs_api_token = zendesk_jobs_api_token 36 | SiteSetting.zendesk_job_push_all_posts = zendesk_job_push_all_posts 37 | end 38 | 39 | context "with zendesk disabled" do 40 | it "does nothing" do 41 | Topic.expects(:find_by).never 42 | Post.expects(:find_by).never 43 | execute 44 | end 45 | end 46 | 47 | context "with zendesk enabled" do 48 | let(:zendesk_enabled) { true } 49 | before(:each) do 50 | DiscourseZendeskPlugin::Helper 51 | .expects(:autogeneration_category?) 52 | .with(post.topic.category_id) 53 | .returns(true) 54 | .at_least(0) 55 | end 56 | 57 | context "with post_id" do 58 | before(:each) do 59 | Topic.expects(:find_by).never 60 | Post.expects(:find_by).with(id: post.id).returns(post).times(1) 61 | job.expects(:create_ticket).never 62 | end 63 | 64 | context "with zendesk_job_push_only_author_posts disabled" do 65 | it "adds the comment once" do 66 | job.expects(:add_comment).with(post, ticket_id).times(1) 67 | execute 68 | end 69 | 70 | context "when post not from topic author" do 71 | let(:post_user) { other_user } 72 | it "adds the comment once" do 73 | job.expects(:add_comment).with(post, ticket_id).times(1) 74 | execute 75 | end 76 | end 77 | end 78 | 79 | context "with zendesk_job_push_only_author_posts enabled" do 80 | let(:zendesk_job_push_only_author_posts) { true } 81 | 82 | context "with post from topic author" do 83 | it "adds the comment once" do 84 | job.expects(:add_comment).with(post, ticket_id).times(1) 85 | execute 86 | end 87 | end 88 | context "with post not from topic author" do 89 | let(:post_user) { other_user } 90 | it "does not adds the comment" do 91 | job.expects(:add_comment).never 92 | execute 93 | end 94 | end 95 | end 96 | end 97 | end 98 | end 99 | -------------------------------------------------------------------------------- /lib/discourse_zendesk_plugin/helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module DiscourseZendeskPlugin 4 | module Helper 5 | def zendesk_client 6 | ::ZendeskAPI::Client.new do |config| 7 | config.url = SiteSetting.zendesk_url 8 | config.username = SiteSetting.zendesk_jobs_email 9 | config.token = SiteSetting.zendesk_jobs_api_token 10 | end 11 | end 12 | 13 | def self.autogeneration_category?(category_id) 14 | return false if category_id.blank? 15 | 16 | if SiteSetting.zendesk_autogenerate_all_categories? 17 | true 18 | else 19 | SiteSetting.zendesk_autogenerate_categories.split("|").include?(category_id.to_s) 20 | end 21 | end 22 | 23 | def create_ticket(post) 24 | zendesk_user_id = fetch_submitter(post.user)&.id 25 | if zendesk_user_id.present? 26 | ticket = 27 | zendesk_client.tickets.create( 28 | subject: post.topic.title, 29 | comment: { 30 | html_body: get_post_content(post), 31 | }, 32 | requester_id: zendesk_user_id, 33 | submitter_id: zendesk_user_id, 34 | priority: "normal", 35 | tags: SiteSetting.zendesk_tags.split("|"), 36 | external_id: post.topic.id, 37 | custom_fields: [ 38 | imported_from: ::Discourse.current_hostname, 39 | external_id: post.topic.id, 40 | imported_by: "discourse_zendesk_plugin", 41 | ], 42 | ) 43 | 44 | if ticket.present? 45 | update_topic_custom_fields(post.topic, ticket) 46 | update_post_custom_fields(post, ticket.comments.first) 47 | end 48 | end 49 | end 50 | 51 | def comment_eligible_for_sync?(post) 52 | if SiteSetting.zendesk_job_push_only_author_posts? 53 | return false if post.blank? || post.user.blank? 54 | return false if post.topic.blank? || post.topic.user.blank? 55 | 56 | post.user.id == post.topic.user.id 57 | else 58 | true 59 | end 60 | end 61 | 62 | def add_comment(post, ticket_id) 63 | return if post.blank? || post.user.blank? 64 | zendesk_user_id = fetch_submitter(post.user)&.id 65 | 66 | if zendesk_user_id.present? 67 | ticket = ZendeskAPI::Ticket.new(zendesk_client, id: ticket_id) 68 | ticket.comment = { html_body: get_post_content(post), author_id: zendesk_user_id } 69 | ticket.save 70 | update_post_custom_fields(post, ticket.comments.last) 71 | end 72 | end 73 | 74 | def get_latest_comment(ticket_id) 75 | ticket = ZendeskAPI::Ticket.new(zendesk_client, id: ticket_id) 76 | last_public_comment = nil 77 | 78 | ticket.comments.all! { |comment| last_public_comment = comment if comment.public } 79 | last_public_comment 80 | end 81 | 82 | def update_topic_custom_fields(topic, ticket) 83 | topic.custom_fields[::DiscourseZendeskPlugin::ZENDESK_ID_FIELD] = ticket["id"] 84 | topic.custom_fields[::DiscourseZendeskPlugin::ZENDESK_API_URL_FIELD] = ticket["url"] 85 | topic.save_custom_fields 86 | end 87 | 88 | def update_post_custom_fields(post, comment) 89 | return if comment.blank? 90 | 91 | post.custom_fields[::DiscourseZendeskPlugin::ZENDESK_ID_FIELD] = comment["id"] 92 | post.save_custom_fields 93 | end 94 | 95 | def fetch_submitter(user) 96 | result = zendesk_client.users.search(query: user.email) 97 | return result.first if result.present? && result.size == 1 98 | zendesk_client.users.create( 99 | name: (user.name.present? ? user.name : user.username), 100 | email: user.email, 101 | verified: true, 102 | role: "end-user", 103 | ) 104 | end 105 | 106 | def get_post_content(post) 107 | style = Email::Styles.new(post.cooked) 108 | style.format_basic 109 | style.format_html 110 | html = style.to_html 111 | 112 | "#{html} \n\n [Discourse post]" 113 | end 114 | end 115 | end 116 | --------------------------------------------------------------------------------