From f2a6eabea7fdce6645028f3f01e25993f0ce2c05 Mon Sep 17 00:00:00 2001 From: Baodong Date: Wed, 13 Oct 2021 21:29:32 +0800 Subject: [PATCH 01/16] Add integration with ZenTao Add integration with ZenTao. Changelog: added --- .../concerns/integrations/params.rb | 4 +- app/helpers/integrations_helper.rb | 14 ++ app/models/integration.rb | 2 +- app/models/integrations/zentao.rb | 23 ++- app/models/project.rb | 5 +- .../development/zentao_issues_integration.yml | 8 ++ ...zentao_integration_list_issues_monthly.yml | 24 ++++ ..._zentao_integration_list_issues_weekly.yml | 24 ++++ .../20210730011801_projects_zentao_active.yml | 22 +++ .../20210730011802_groups_zentao_active.yml | 22 +++ ...20210730011803_templates_zentao_active.yml | 22 +++ ...20210730011804_instances_zentao_active.yml | 22 +++ ...1805_projects_inheriting_zentao_active.yml | 22 +++ ...011806_groups_inheriting_zentao_active.yml | 22 +++ doc/api/graphql/reference/index.md | 1 + .../integrations/zentao/issues_controller.rb | 74 ++++++++++ ee/app/models/ee/project.rb | 4 - .../zentao_serializers/issue_detail_entity.rb | 29 ++++ .../issue_detail_serializer.rb | 9 ++ .../zentao_serializers/issue_entity.rb | 88 ++++++++++++ .../zentao_serializers/issue_serializer.rb | 11 ++ .../zentao/issues/index.html.haml | 10 ++ .../integrations/zentao/issues/show.html.haml | 5 + ee/config/routes/project.rb | 4 + .../ee/sidebars/projects/menus/zentao_menu.rb | 41 ++++++ .../zentao/issues_controller_spec.rb | 135 ++++++++++++++++++ .../projects/menus/zentao_menu_spec.rb | 54 +++++++ lib/api/helpers/integrations_helpers.rb | 33 ++++- .../known_events/ecosystem.yml | 4 + lib/gitlab/zentao/client.rb | 8 +- lib/gitlab/zentao/query.rb | 60 ++++++++ lib/sidebars/projects/menus/zentao_menu.rb | 81 +++++++++++ lib/sidebars/projects/panel.rb | 1 + locale/gitlab.pot | 27 +++- spec/lib/gitlab/zentao/client_spec.rb | 61 +++++--- spec/lib/gitlab/zentao/query_spec.rb | 61 ++++++++ .../projects/menus/zentao_menu_spec.rb | 7 + spec/support/helpers/usage_data_helpers.rb | 1 + .../menus/zentao_menu_shared_examples.rb | 42 ++++++ 39 files changed, 1044 insertions(+), 43 deletions(-) create mode 100644 config/feature_flags/development/zentao_issues_integration.yml create mode 100644 config/metrics/counts_28d/20210801073526_i_ecosystem_zentao_integration_list_issues_monthly.yml create mode 100644 config/metrics/counts_7d/20210801073522_i_ecosystem_zentao_integration_list_issues_weekly.yml create mode 100644 config/metrics/counts_all/20210730011801_projects_zentao_active.yml create mode 100644 config/metrics/counts_all/20210730011802_groups_zentao_active.yml create mode 100644 config/metrics/counts_all/20210730011803_templates_zentao_active.yml create mode 100644 config/metrics/counts_all/20210730011804_instances_zentao_active.yml create mode 100644 config/metrics/counts_all/20210730011805_projects_inheriting_zentao_active.yml create mode 100644 config/metrics/counts_all/20210730011806_groups_inheriting_zentao_active.yml create mode 100644 ee/app/controllers/projects/integrations/zentao/issues_controller.rb create mode 100644 ee/app/serializers/integrations/zentao_serializers/issue_detail_entity.rb create mode 100644 ee/app/serializers/integrations/zentao_serializers/issue_detail_serializer.rb create mode 100644 ee/app/serializers/integrations/zentao_serializers/issue_entity.rb create mode 100644 ee/app/serializers/integrations/zentao_serializers/issue_serializer.rb create mode 100644 ee/app/views/projects/integrations/zentao/issues/index.html.haml create mode 100644 ee/app/views/projects/integrations/zentao/issues/show.html.haml create mode 100644 ee/lib/ee/sidebars/projects/menus/zentao_menu.rb create mode 100644 ee/spec/controllers/projects/integrations/zentao/issues_controller_spec.rb create mode 100644 ee/spec/lib/ee/sidebars/projects/menus/zentao_menu_spec.rb create mode 100644 lib/gitlab/zentao/query.rb create mode 100644 lib/sidebars/projects/menus/zentao_menu.rb create mode 100644 spec/lib/gitlab/zentao/query_spec.rb create mode 100644 spec/lib/sidebars/projects/menus/zentao_menu_spec.rb create mode 100644 spec/support/shared_examples/lib/sidebars/projects/menus/zentao_menu_shared_examples.rb diff --git a/app/controllers/concerns/integrations/params.rb b/app/controllers/concerns/integrations/params.rb index 62585ab95af7b1..201fb1dc83f2c9 100644 --- a/app/controllers/concerns/integrations/params.rb +++ b/app/controllers/concerns/integrations/params.rb @@ -9,6 +9,7 @@ module Params :add_pusher, :alert_events, :api_key, + :api_token, :api_url, :bamboo_url, :branches_to_be_notified, @@ -74,7 +75,8 @@ module Params :url, :user_key, :username, - :webhook + :webhook, + :zentao_product_xid ].freeze def integration_params diff --git a/app/helpers/integrations_helper.rb b/app/helpers/integrations_helper.rb index 8819aa9e9cc1b2..bb4a7fef6be1dc 100644 --- a/app/helpers/integrations_helper.rb +++ b/app/helpers/integrations_helper.rb @@ -132,6 +132,20 @@ def jira_issue_breadcrumb_link(issue_reference) end end + def zentao_issue_breadcrumb_link(issue) + link_to issue[:web_url], { target: '_blank', rel: 'noopener noreferrer', class: 'gl-display-flex gl-align-items-center gl-white-space-nowrap' } do + icon = image_tag image_path('logos/zentao.svg'), width: 15, height: 15, class: 'gl-mr-2' + [icon, html_escape(issue[:id])].join.html_safe + end + end + + def zentao_issues_show_data + { + issues_show_path: project_integrations_zentao_issue_path(@project, params[:id], format: :json), + issues_list_path: project_integrations_zentao_issues_path(@project) + } + end + extend self private diff --git a/app/models/integration.rb b/app/models/integration.rb index 158764bb783e9f..4dd3e1a1785831 100644 --- a/app/models/integration.rb +++ b/app/models/integration.rb @@ -14,7 +14,7 @@ class Integration < ApplicationRecord asana assembla bamboo bugzilla buildkite campfire confluence custom_issue_tracker datadog discord drone_ci emails_on_push ewm external_wiki flowdock hangouts_chat irker jira mattermost mattermost_slash_commands microsoft_teams packagist pipelines_email - pivotaltracker prometheus pushover redmine slack slack_slash_commands teamcity unify_circuit webex_teams youtrack + pivotaltracker prometheus pushover redmine slack slack_slash_commands teamcity unify_circuit webex_teams youtrack zentao ].freeze PROJECT_SPECIFIC_INTEGRATION_NAMES = %w[ diff --git a/app/models/integrations/zentao.rb b/app/models/integrations/zentao.rb index 68c02f54c61246..2b3e5b6001fa8f 100644 --- a/app/models/integrations/zentao.rb +++ b/app/models/integrations/zentao.rb @@ -9,16 +9,25 @@ class Zentao < Integration validates :api_token, presence: true, if: :activated? validates :zentao_product_xid, presence: true, if: :activated? + def self.feature_flag_enabled?(project) + Feature.enabled?(:zentao_issues_integration, project) + end + + # License Level: EEP_FEATURES + def self.issues_license_available?(project) + project&.licensed_feature_available?(:zentao_issues_integration) + end + def data_fields zentao_tracker_data || self.build_zentao_tracker_data end def title - self.class.name.demodulize + 'ZenTao' end def description - s_("ZentaoIntegration|Use Zentao as this project's issue tracker.") + s_("ZentaoIntegration|Use ZenTao as this project's issue tracker.") end def self.to_param @@ -42,28 +51,28 @@ def fields { type: 'text', name: 'url', - title: s_('ZentaoIntegration|Zentao Web URL'), + title: s_('ZentaoIntegration|ZenTao Web URL'), placeholder: 'https://www.zentao.net', - help: s_('ZentaoIntegration|Base URL of the Zentao instance.'), + help: s_('ZentaoIntegration|Base URL of the ZenTao instance.'), required: true }, { type: 'text', name: 'api_url', - title: s_('ZentaoIntegration|Zentao API URL (optional)'), + title: s_('ZentaoIntegration|ZenTao API URL (optional)'), help: s_('ZentaoIntegration|If different from Web URL.') }, { type: 'password', name: 'api_token', - title: s_('ZentaoIntegration|Zentao API token'), + title: s_('ZentaoIntegration|ZenTao API token'), non_empty_password_title: s_('ZentaoIntegration|Enter API token'), required: true }, { type: 'text', name: 'zentao_product_xid', - title: s_('ZentaoIntegration|Zentao Product ID'), + title: s_('ZentaoIntegration|ZenTao Product ID'), required: true } ] diff --git a/app/models/project.rb b/app/models/project.rb index 6eb19b4462cfbb..b827f48e706eb6 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1453,7 +1453,10 @@ def find_or_initialize_integrations end def disabled_integrations - [:zentao] + disabled_integrations = [] + disabled_integrations << :zentao unless ::Integrations::Zentao.feature_flag_enabled?(self) + + disabled_integrations end def find_or_initialize_integration(name) diff --git a/config/feature_flags/development/zentao_issues_integration.yml b/config/feature_flags/development/zentao_issues_integration.yml new file mode 100644 index 00000000000000..150340874cb54a --- /dev/null +++ b/config/feature_flags/development/zentao_issues_integration.yml @@ -0,0 +1,8 @@ +--- +name: zentao_issues_integration +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/69602 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/338775 +milestone: '14.4' +type: development +group: group::integrations +default_enabled: false diff --git a/config/metrics/counts_28d/20210801073526_i_ecosystem_zentao_integration_list_issues_monthly.yml b/config/metrics/counts_28d/20210801073526_i_ecosystem_zentao_integration_list_issues_monthly.yml new file mode 100644 index 00000000000000..6bfdf8492373b2 --- /dev/null +++ b/config/metrics/counts_28d/20210801073526_i_ecosystem_zentao_integration_list_issues_monthly.yml @@ -0,0 +1,24 @@ +--- +key_path: redis_hll_counters.ecosystem.i_ecosystem_zentao_integration_list_issues_monthly +name: i_ecosystem_zentao_integration_list_issues_monthly +description: 'Count of Zentao issue list visits by month' +product_section: dev +product_stage: ecosystem +product_group: group::integrations +product_category: integrations +value_type: number +status: implemented +milestone: "14.4" +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/issues/338178 +time_frame: 28d +data_source: redis_hll +data_category: Optional +instrumentation_class: RedisHLLMetric +options: + events: + - i_ecosystem_zentao_integration_list_issues +distribution: +- ee +tier: +- premium +- ultimate \ No newline at end of file diff --git a/config/metrics/counts_7d/20210801073522_i_ecosystem_zentao_integration_list_issues_weekly.yml b/config/metrics/counts_7d/20210801073522_i_ecosystem_zentao_integration_list_issues_weekly.yml new file mode 100644 index 00000000000000..27f95b403b4449 --- /dev/null +++ b/config/metrics/counts_7d/20210801073522_i_ecosystem_zentao_integration_list_issues_weekly.yml @@ -0,0 +1,24 @@ +--- +key_path: redis_hll_counters.ecosystem.i_ecosystem_zentao_integration_list_issues_weekly +name: i_ecosystem_zentao_integration_list_issues_weekly +description: 'Count of Zentao issue list visits by week' +product_section: dev +product_stage: ecosystem +product_group: group::integrations +product_category: integrations +value_type: number +status: implemented +milestone: "14.4" +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/issues/338178 +time_frame: 7d +data_source: redis_hll +data_category: Optional +instrumentation_class: RedisHLLMetric +options: + events: + - i_ecosystem_zentao_integration_list_issues +distribution: +- ee +tier: +- premium +- ultimate diff --git a/config/metrics/counts_all/20210730011801_projects_zentao_active.yml b/config/metrics/counts_all/20210730011801_projects_zentao_active.yml new file mode 100644 index 00000000000000..03a48ac5ffcebf --- /dev/null +++ b/config/metrics/counts_all/20210730011801_projects_zentao_active.yml @@ -0,0 +1,22 @@ +--- +key_path: counts.projects_zentao_active +name: count_all_projects_zentao_active +description: Count of projects with active Zentao integrations +product_section: dev +product_stage: ecosystem +product_group: group::integrations +product_category: integrations +value_type: number +status: implemented +milestone: "14.4" +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/issues/338178 +time_frame: all +data_source: database +data_category: Operational +distribution: +- ce +- ee +tier: +- free +- premium +- ultimate diff --git a/config/metrics/counts_all/20210730011802_groups_zentao_active.yml b/config/metrics/counts_all/20210730011802_groups_zentao_active.yml new file mode 100644 index 00000000000000..09795f11fe3af3 --- /dev/null +++ b/config/metrics/counts_all/20210730011802_groups_zentao_active.yml @@ -0,0 +1,22 @@ +--- +key_path: counts.groups_zentao_active +name: count_all_groups_zentao_active +description: Count of groups with active Zentao integrations +product_section: dev +product_stage: ecosystem +product_group: group::integrations +product_category: integrations +value_type: number +status: implemented +milestone: "14.4" +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/issues/338178 +time_frame: all +data_source: database +data_category: Operational +distribution: +- ce +- ee +tier: +- free +- premium +- ultimate diff --git a/config/metrics/counts_all/20210730011803_templates_zentao_active.yml b/config/metrics/counts_all/20210730011803_templates_zentao_active.yml new file mode 100644 index 00000000000000..5def7b5f1fc474 --- /dev/null +++ b/config/metrics/counts_all/20210730011803_templates_zentao_active.yml @@ -0,0 +1,22 @@ +--- +key_path: counts.templates_zentao_active +name: count_all_templates_zentao_active +description: Count of active service templates for Zentao +product_section: dev +product_stage: ecosystem +product_group: group::integrations +product_category: integrations +value_type: number +status: implemented +milestone: "14.4" +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/issues/338178 +time_frame: all +data_source: database +data_category: Operational +distribution: +- ce +- ee +tier: +- free +- premium +- ultimate diff --git a/config/metrics/counts_all/20210730011804_instances_zentao_active.yml b/config/metrics/counts_all/20210730011804_instances_zentao_active.yml new file mode 100644 index 00000000000000..f7d67dfe3ecfef --- /dev/null +++ b/config/metrics/counts_all/20210730011804_instances_zentao_active.yml @@ -0,0 +1,22 @@ +--- +key_path: counts.instances_zentao_active +name: count_all_instances_zentao_active +description: Count of instances with active Zentao integrations +product_section: dev +product_stage: ecosystem +product_group: group::integrations +product_category: integrations +value_type: number +status: implemented +milestone: "14.4" +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/issues/338178 +time_frame: all +data_source: database +data_category: Operational +distribution: +- ce +- ee +tier: +- free +- premium +- ultimate diff --git a/config/metrics/counts_all/20210730011805_projects_inheriting_zentao_active.yml b/config/metrics/counts_all/20210730011805_projects_inheriting_zentao_active.yml new file mode 100644 index 00000000000000..58224a754248d2 --- /dev/null +++ b/config/metrics/counts_all/20210730011805_projects_inheriting_zentao_active.yml @@ -0,0 +1,22 @@ +--- +key_path: counts.projects_inheriting_zentao_active +name: count_all_projects_inheriting_zentao_active +description: Count of projects that inherit active Zentao integrations +product_section: dev +product_stage: ecosystem +product_group: group::integrations +product_category: integrations +value_type: number +status: implemented +milestone: "14.4" +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/issues/338178 +time_frame: all +data_source: database +data_category: Operational +distribution: +- ce +- ee +tier: +- free +- premium +- ultimate diff --git a/config/metrics/counts_all/20210730011806_groups_inheriting_zentao_active.yml b/config/metrics/counts_all/20210730011806_groups_inheriting_zentao_active.yml new file mode 100644 index 00000000000000..fbe8fc87939c88 --- /dev/null +++ b/config/metrics/counts_all/20210730011806_groups_inheriting_zentao_active.yml @@ -0,0 +1,22 @@ +--- +key_path: counts.groups_inheriting_zentao_active +name: count_all_groups_inheriting_zentao_active +description: Count of groups that inherit active Zentao integrations +product_section: dev +product_stage: ecosystem +product_group: group::integrations +product_category: integrations +value_type: number +status: implemented +milestone: "14.4" +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/issues/338178 +time_frame: all +data_source: database +data_category: Operational +distribution: +- ce +- ee +tier: +- free +- premium +- ultimate diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 17d62ea19ad8a2..c228d8ae205a30 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -16578,6 +16578,7 @@ State of a Sentry error. | `UNIFY_CIRCUIT_SERVICE` | UnifyCircuitService type. | | `WEBEX_TEAMS_SERVICE` | WebexTeamsService type. | | `YOUTRACK_SERVICE` | YoutrackService type. | +| `ZENTAO_SERVICE` | ZentaoService type. | ### `SharedRunnersSetting` diff --git a/ee/app/controllers/projects/integrations/zentao/issues_controller.rb b/ee/app/controllers/projects/integrations/zentao/issues_controller.rb new file mode 100644 index 00000000000000..fdcb1f31fe061f --- /dev/null +++ b/ee/app/controllers/projects/integrations/zentao/issues_controller.rb @@ -0,0 +1,74 @@ +# frozen_string_literal: true + +module Projects + module Integrations + module Zentao + class IssuesController < Projects::ApplicationController + include RecordUserLastActivity + include RedisTracking + + track_redis_hll_event :index, name: 'i_ecosystem_zentao_integration_list_issues' + + before_action :check_feature_enabled! + + rescue_from ::Gitlab::Zentao::Client::Error, with: :render_error + + feature_category :integrations + + def index + respond_to do |format| + format.html + format.json do + render json: issues_json + end + end + end + + def show + @issue_json = issue_json + respond_to do |format| + format.html + format.json do + render json: @issue_json + end + end + end + + private + + def query + ::Gitlab::Zentao::Query.new(project.zentao_integration, params) + end + + def issue_json + ::Integrations::ZentaoSerializers::IssueDetailSerializer.new + .represent(query.issue, project: project) + end + + def issues_json + ::Integrations::ZentaoSerializers::IssueSerializer.new + .with_pagination(request, response) + .represent(query.issues, project: project) + end + + # for track data + def visitor_id + current_user&.id + end + + def check_feature_enabled! + return render_404 unless ::Integrations::Zentao.feature_flag_enabled?(project) + + return render_404 unless ::Integrations::Zentao.issues_license_available?(project) && project.zentao_integration&.active? + end + + def render_error(exception) + log_exception(exception) + + render json: { errors: [s_('ZentaoIntegration|An error occurred while requesting data from the ZenTao service.')] }, + status: :bad_request + end + end + end + end +end diff --git a/ee/app/models/ee/project.rb b/ee/app/models/ee/project.rb index d4ac2be5e98217..1f68b5aa44e733 100644 --- a/ee/app/models/ee/project.rb +++ b/ee/app/models/ee/project.rb @@ -404,10 +404,6 @@ def jira_issues_integration_available? feature_available?(:jira_issues_integration) end - def zentao_issues_integration_available? - feature_available?(:zentao_issues_integration) - end - def multiple_approval_rules_available? feature_available?(:multiple_approval_rules) end diff --git a/ee/app/serializers/integrations/zentao_serializers/issue_detail_entity.rb b/ee/app/serializers/integrations/zentao_serializers/issue_detail_entity.rb new file mode 100644 index 00000000000000..0f40bf87a95037 --- /dev/null +++ b/ee/app/serializers/integrations/zentao_serializers/issue_detail_entity.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module Integrations + module ZentaoSerializers + class IssueDetailEntity < IssueEntity + expose :description_html do |item| + sanitize(item['desc']) + end + + expose :comments do |item| + item['comments'].map do |comment| + { + id: comment['id']&.to_i, + created_at: comment['date']&.to_datetime&.utc, + body_html: body_html(comment), + author: user_info(comment['actor']) + } + end + end + + private + + def body_html(comment) + content = [comment['title'], comment['body_html']].join('
') + sanitize(content) + end + end + end +end diff --git a/ee/app/serializers/integrations/zentao_serializers/issue_detail_serializer.rb b/ee/app/serializers/integrations/zentao_serializers/issue_detail_serializer.rb new file mode 100644 index 00000000000000..72bd7e41b080e3 --- /dev/null +++ b/ee/app/serializers/integrations/zentao_serializers/issue_detail_serializer.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module Integrations + module ZentaoSerializers + class IssueDetailSerializer < BaseSerializer + entity ::Integrations::ZentaoSerializers::IssueDetailEntity + end + end +end diff --git a/ee/app/serializers/integrations/zentao_serializers/issue_entity.rb b/ee/app/serializers/integrations/zentao_serializers/issue_entity.rb new file mode 100644 index 00000000000000..d9561def105d75 --- /dev/null +++ b/ee/app/serializers/integrations/zentao_serializers/issue_entity.rb @@ -0,0 +1,88 @@ +# frozen_string_literal: true + +module Integrations + module ZentaoSerializers + class IssueEntity < Grape::Entity + include RequestAwareEntity + + expose :id do |item| + sanitize(item['id']) + end + + expose :project_id do |item| + project.id + end + + expose :title do |item| + sanitize(item['title']) + end + + expose :created_at do |item| + item['openedDate']&.to_datetime&.utc + end + + expose :updated_at do |item| + item['lastEditedDate']&.to_datetime&.utc + end + + expose :closed_at do |item| + item['lastEditedDate']&.to_datetime&.utc if item['status'] == 'closed' + end + + expose :status do |item| + sanitize(item['status']) + end + + expose :labels do |item| + item['labels'].compact.map do |label| + name = sanitize(label) + { + id: name, + title: name, + name: name, + color: '#0052CC', + text_color: '#FFFFFF' + } + end + end + + expose :author do |item| + user_info(item['openedBy']) + end + + expose :assignees do |item| + item['assignedTo'].compact.map do |user| + user_info(user) + end + end + + expose :web_url do |item| + item['url'] + end + + expose :gitlab_web_url do |item| + project_integrations_zentao_issue_path(project, item['id']) + end + + private + + def project + @project ||= options[:project] + end + + def sanitize(content) + ActionController::Base.helpers.sanitize(content) + end + + def user_info(user) + return {} unless user.present? + + { + "name": sanitize(user['realname'].presence || user['account']), + "web_url": user['url'], + "avatar_url": user['avatar'] + } + end + end + end +end diff --git a/ee/app/serializers/integrations/zentao_serializers/issue_serializer.rb b/ee/app/serializers/integrations/zentao_serializers/issue_serializer.rb new file mode 100644 index 00000000000000..064833554f290a --- /dev/null +++ b/ee/app/serializers/integrations/zentao_serializers/issue_serializer.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Integrations + module ZentaoSerializers + class IssueSerializer < BaseSerializer + include WithPagination + + entity ::Integrations::ZentaoSerializers::IssueEntity + end + end +end diff --git a/ee/app/views/projects/integrations/zentao/issues/index.html.haml b/ee/app/views/projects/integrations/zentao/issues/index.html.haml new file mode 100644 index 00000000000000..c58ff6297ce52f --- /dev/null +++ b/ee/app/views/projects/integrations/zentao/issues/index.html.haml @@ -0,0 +1,10 @@ +- page_title _('Zentao issues') +- add_page_specific_style 'page_bundles/issues_list' + +.js-zentao-issues-list{ data: { issues_fetch_path: project_integrations_zentao_issues_path(@project, format: :json), +page: params[:page], +initial_state: params[:state], +initial_sort_by: params[:sort], +project_full_path: @project.full_path, +issue_create_url: @project.zentao_integration&.url, +empty_state_path: image_path('illustrations/issues.svg') } } diff --git a/ee/app/views/projects/integrations/zentao/issues/show.html.haml b/ee/app/views/projects/integrations/zentao/issues/show.html.haml new file mode 100644 index 00000000000000..057a2fd8f26413 --- /dev/null +++ b/ee/app/views/projects/integrations/zentao/issues/show.html.haml @@ -0,0 +1,5 @@ +- add_to_breadcrumbs _('Zentao issues'), project_integrations_zentao_issues_path(@project) +- breadcrumb_title zentao_issue_breadcrumb_link(@issue_json) +- page_title html_escape(@issue_json[:title]) + +.js-zentao-issues-show-app{ data: zentao_issues_show_data } diff --git a/ee/config/routes/project.rb b/ee/config/routes/project.rb index 5ba33247a6c8df..f8ca182ad1d4dc 100644 --- a/ee/config/routes/project.rb +++ b/ee/config/routes/project.rb @@ -119,6 +119,10 @@ end end end + + namespace :zentao do + resources :issues, only: [:index, :show] + end end # Added for backward compatibility with https://gitlab.com/gitlab-org/gitlab/-/merge_requests/39543 diff --git a/ee/lib/ee/sidebars/projects/menus/zentao_menu.rb b/ee/lib/ee/sidebars/projects/menus/zentao_menu.rb new file mode 100644 index 00000000000000..049cbc063d1cf2 --- /dev/null +++ b/ee/lib/ee/sidebars/projects/menus/zentao_menu.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +module EE + module Sidebars + module Projects + module Menus + module ZentaoMenu + extend ::Gitlab::Utils::Override + + override :link + def link + return super unless feature_available? + + project_integrations_zentao_issues_path(context.project) + end + + override :add_items + def add_items + add_item(issue_list_menu_item) if feature_available? + super + end + + private + + def feature_available? + ::Integrations::Zentao.issues_license_available?(context.project) + end + + def issue_list_menu_item + ::Sidebars::MenuItem.new( + title: s_('ZentaoIntegration|Issue list'), + link: project_integrations_zentao_issues_path(context.project), + active_routes: { controller: 'projects/integrations/zentao/issues' }, + item_id: :issue_list + ) + end + end + end + end + end +end diff --git a/ee/spec/controllers/projects/integrations/zentao/issues_controller_spec.rb b/ee/spec/controllers/projects/integrations/zentao/issues_controller_spec.rb new file mode 100644 index 00000000000000..11d0bff24ef76c --- /dev/null +++ b/ee/spec/controllers/projects/integrations/zentao/issues_controller_spec.rb @@ -0,0 +1,135 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Projects::Integrations::Zentao::IssuesController do + let_it_be(:project) { create(:project) } + let_it_be(:user) { create(:user, developer_projects: [project]) } + let_it_be(:zentao_integration) { create(:zentao_integration, project: project) } + + before do + stub_licensed_features(zentao_issues_integration: true) + sign_in(user) + end + + describe 'GET #index' do + context 'when zentao_issues_integration licensed feature is not available' do + before do + stub_licensed_features(zentao_issues_integration: false) + end + + it 'returns 404 status' do + get :index, params: { namespace_id: project.namespace, project_id: project } + + expect(response).to have_gitlab_http_status(:not_found) + end + end + + it_behaves_like 'unauthorized when external service denies access' do + subject { get :index, params: { namespace_id: project.namespace, project_id: project } } + end + + it 'renders the "index" template' do + get :index, params: { namespace_id: project.namespace, project_id: project } + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to render_template(:index) + end + + it 'tracks usage' do + expect(Gitlab::UsageDataCounters::HLLRedisCounter) + .to receive(:track_event) + .with('i_ecosystem_zentao_integration_list_issues', values: user.id) + + get :index, params: { namespace_id: project.namespace, project_id: project } + end + + context 'json request' do + let(:zentao_issue) { [] } + + it 'returns a list of serialized zentao issues' do + expect_next_instance_of(::Gitlab::Zentao::Query) do |query| + expect(query).to receive(:issues).and_return(zentao_issue) + end + + expect_next_instance_of(Integrations::ZentaoSerializers::IssueSerializer) do |serializer| + expect(serializer).to receive(:represent).with(zentao_issue, project: project) + end + + get :index, params: { namespace_id: project.namespace, project_id: project }, format: :json + end + + it 'renders bad request for Error' do + expect_next_instance_of(::Gitlab::Zentao::Query) do |query| + expect(query).to receive(:issues).and_raise(::Gitlab::Zentao::Client::Error) + end + expect(Gitlab::ErrorTracking).to receive(:track_exception) + + get :index, params: { namespace_id: project.namespace, project_id: project }, format: :json + + expect(response).to have_gitlab_http_status(:bad_request) + expect(json_response['errors']).to match_array [s_('ZentaoIntegration|An error occurred while requesting data from the ZenTao service.')] + end + end + end + + describe 'GET #show' do + context 'when zentao_issues_integration licensed feature is not available' do + before do + stub_licensed_features(zentao_issues_integration: false) + end + + it 'returns 404 status' do + get :show, params: { namespace_id: project.namespace, project_id: project, id: 1 } + + expect(response).to have_gitlab_http_status(:not_found) + end + end + + context 'when zentao_issues_integration licensed feature is available' do + let(:zentao_issue) { { 'from' => 'zentao' } } + let(:issue_json) { { 'from' => 'backend' } } + + before do + stub_licensed_features(zentao_issues_integration: true) + + expect_next_instance_of(::Gitlab::Zentao::Query) do |query| + allow(query).to receive(:issue).and_return(zentao_issue) + end + + allow_next_instance_of(Integrations::ZentaoSerializers::IssueDetailSerializer) do |serializer| + allow(serializer).to receive(:represent).with(zentao_issue, project: project).and_return(issue_json) + end + end + + it 'renders `show` template' do + get :show, params: { namespace_id: project.namespace, project_id: project, id: 'story-1' } + + expect(assigns(:issue_json)).to eq(issue_json) + expect(response).to have_gitlab_http_status(:ok) + expect(response).to render_template(:show) + end + + it 'returns JSON response' do + get :show, params: { namespace_id: project.namespace, project_id: project, id: 'story-1', format: :json } + + expect(json_response).to eq(issue_json) + end + + context 'when the JSON fetched from ZenTao contains HTML' do + let(:payload) { "" } + let(:issue_json) { { id: payload, title: payload, status: payload, labels: [payload] } } + + render_views + + it 'escapes the HTML in issue' do + get :show, params: { namespace_id: project.namespace, project_id: project, id: 'story-1' } + + expect(response).to have_gitlab_http_status(:ok) + expect(response.body).not_to include(payload) + expect(response.body).to include(html_escape(payload)) + end + end + end + end +end diff --git a/ee/spec/lib/ee/sidebars/projects/menus/zentao_menu_spec.rb b/ee/spec/lib/ee/sidebars/projects/menus/zentao_menu_spec.rb new file mode 100644 index 00000000000000..c69969b31633bc --- /dev/null +++ b/ee/spec/lib/ee/sidebars/projects/menus/zentao_menu_spec.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Sidebars::Projects::Menus::ZentaoMenu do + let(:project) { create(:project, has_external_issue_tracker: true) } + let(:user) { project.owner } + let(:context) { Sidebars::Projects::Context.new(current_user: user, container: project) } + let(:zentao_integration) { create(:zentao_integration, project: project) } + + subject { described_class.new(context) } + + describe 'with STARTER_PLAN' do + before do + stub_licensed_features(zentao_issues_integration: false) + end + + it_behaves_like 'ZenTao menu with CE version' + end + + describe 'with PREMIUM_PLAN or ULTIMATE_PLAN' do + before do + stub_licensed_features(zentao_issues_integration: true) + end + + context 'when issues integration is disabled' do + before do + zentao_integration.update!(active: false) + end + + it 'returns false' do + expect(subject.render?).to eq false + end + end + + context 'when issues integration is enabled' do + before do + zentao_integration.update!(active: true) + end + + it 'returns true' do + expect(subject.render?).to eq true + end + + it 'renders menu link' do + expect(subject.link).to include('/-/integrations/zentao/issues') + end + + it 'contains issue list and open ZenTao menu items' do + expect(subject.renderable_items.map(&:item_id)).to match_array [:issue_list, :open_zentao] + end + end + end +end diff --git a/lib/api/helpers/integrations_helpers.rb b/lib/api/helpers/integrations_helpers.rb index e0ef9099104e4d..cab4ee6d8bac78 100644 --- a/lib/api/helpers/integrations_helpers.rb +++ b/lib/api/helpers/integrations_helpers.rb @@ -254,7 +254,7 @@ def self.integrations type: Boolean, desc: 'DEPRECATED: This parameter has no effect since SSL verification will always be enabled' } - ], + ], 'campfire' => [ { required: true, @@ -768,7 +768,33 @@ def self.integrations desc: 'The Webex Teams webhook. For example, https://api.ciscospark.com/v1/webhooks/incoming/...' }, chat_notification_events - ].flatten + ].flatten, + 'zentao' => [ + { + required: true, + name: :url, + type: String, + desc: 'The base URL to the ZenTao instance web interface which is being linked to this GitLab project. E.g., https://www.zentao.net' + }, + { + required: false, + name: :api_url, + type: String, + desc: 'The base URL to the ZenTao instance API. Web URL value will be used if not set. E.g., https://www.zentao.net' + }, + { + required: true, + name: :api_token, + type: String, + desc: 'The API token created from ZenTao dashboard' + }, + { + required: true, + name: :zentao_product_xid, + type: String, + desc: 'The product ID of ZenTao project' + } + ] } end @@ -805,7 +831,8 @@ def self.integration_classes ::Integrations::Slack, ::Integrations::SlackSlashCommands, ::Integrations::Teamcity, - ::Integrations::Youtrack + ::Integrations::Youtrack, + ::Integrations::Zentao ] end diff --git a/lib/gitlab/usage_data_counters/known_events/ecosystem.yml b/lib/gitlab/usage_data_counters/known_events/ecosystem.yml index f594c6a1b7c365..7ad48defb32453 100644 --- a/lib/gitlab/usage_data_counters/known_events/ecosystem.yml +++ b/lib/gitlab/usage_data_counters/known_events/ecosystem.yml @@ -52,3 +52,7 @@ category: ecosystem redis_slot: ecosystem aggregation: weekly +- name: i_ecosystem_zentao_integration_list_issues + category: ecosystem + redis_slot: ecosystem + aggregation: weekly diff --git a/lib/gitlab/zentao/client.rb b/lib/gitlab/zentao/client.rb index bdfa4b3a308e85..312f6957bfcc2a 100644 --- a/lib/gitlab/zentao/client.rb +++ b/lib/gitlab/zentao/client.rb @@ -32,15 +32,21 @@ def fetch_product(product_id) def fetch_issues(params = {}) get("products/#{zentao_product_xid}/issues", - params.reverse_merge(page: 1, limit: 20)) + params.reverse_merge(page: 1, limit: 20)) end def fetch_issue(issue_id) + return {} unless issue_id_pattern.match(issue_id) + get("issues/#{issue_id}") end private + def issue_id_pattern + /^\S+-\d+$/ + end + def get(path, params = {}) options = { headers: headers, query: params } response = Gitlab::HTTP.get(url(path), options) diff --git a/lib/gitlab/zentao/query.rb b/lib/gitlab/zentao/query.rb new file mode 100644 index 00000000000000..82c1528ff4aa90 --- /dev/null +++ b/lib/gitlab/zentao/query.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +module Gitlab + module Zentao + class Query + STATUSES = %w[all opened closed].freeze + + attr_reader :client, :params + + def initialize(integration, params) + @client = Client.new(integration) + @params = params + end + + def issues + issues_response = client.fetch_issues(query_options) + Kaminari.paginate_array( + issues_response['issues'], + limit: issues_response['limit'], + total_count: issues_response['total'] + ) + end + + def issue + issue_response = client.fetch_issue(params[:id]) + issue_response['issue'] + end + + private + + def query_options + options = { + order: query_order, + status: query_status, + labels: query_labels + } + + params.permit(:page, :limit, :search).merge(options) + end + + def query_order + key, order = params['sort'].to_s.split('_', 2) + zentao_key = (key == 'created' ? 'openedDate' : 'lastEditedDate') + zentao_order = (order == 'asc' ? 'asc' : 'desc') + + "#{zentao_key}_#{zentao_order}" + end + + def query_status + return params[:state] if params[:state].present? && params[:state].in?(STATUSES) + + 'opened' + end + + def query_labels + (params[:labels].presence || []).join(',') + end + end + end +end diff --git a/lib/sidebars/projects/menus/zentao_menu.rb b/lib/sidebars/projects/menus/zentao_menu.rb new file mode 100644 index 00000000000000..fe96fb6d9f485c --- /dev/null +++ b/lib/sidebars/projects/menus/zentao_menu.rb @@ -0,0 +1,81 @@ +# frozen_string_literal: true + +module Sidebars + module Projects + module Menus + class ZentaoMenu < ::Sidebars::Menu + override :configure_menu_items + def configure_menu_items + render?.tap do |render| + break unless render + + add_items + end + end + + override :link + def link + zentao_integration.url + end + + override :title + def title + s_('ZentaoIntegration|ZenTao issues') + end + + override :title_html_options + def title_html_options + { + id: 'js-onboarding-settings-link' + } + end + + override :image_path + def image_path + 'logos/zentao.svg' + end + + # Hardcode sizes so image doesn't flash before CSS loads https://gitlab.com/gitlab-org/gitlab/-/issues/321022 + override :image_html_options + def image_html_options + { + size: 16 + } + end + + override :render? + def render? + return false if zentao_integration.blank? + + zentao_integration.active? + end + + def add_items + add_item(open_zentao_menu_item) + end + + private + + def zentao_integration + @zentao_integration ||= context.project.zentao_integration + end + + def open_zentao_menu_item + ::Sidebars::MenuItem.new( + title: s_('ZentaoIntegration|Open ZenTao'), + link: zentao_integration.url, + active_routes: {}, + item_id: :open_zentao, + sprite_icon: 'external-link', + container_html_options: { + target: '_blank', + rel: 'noopener noreferrer' + } + ) + end + end + end + end +end + +::Sidebars::Projects::Menus::ZentaoMenu.prepend_mod diff --git a/lib/sidebars/projects/panel.rb b/lib/sidebars/projects/panel.rb index d5311c0a0c143d..f37f404d03cd13 100644 --- a/lib/sidebars/projects/panel.rb +++ b/lib/sidebars/projects/panel.rb @@ -23,6 +23,7 @@ def add_menus add_menu(Sidebars::Projects::Menus::RepositoryMenu.new(context)) add_menu(Sidebars::Projects::Menus::IssuesMenu.new(context)) add_menu(Sidebars::Projects::Menus::ExternalIssueTrackerMenu.new(context)) + add_menu(Sidebars::Projects::Menus::ZentaoMenu.new(context)) if ::Integrations::Zentao.feature_flag_enabled?(context.project) add_menu(Sidebars::Projects::Menus::MergeRequestsMenu.new(context)) add_menu(Sidebars::Projects::Menus::CiCdMenu.new(context)) add_menu(Sidebars::Projects::Menus::SecurityComplianceMenu.new(context)) diff --git a/locale/gitlab.pot b/locale/gitlab.pot index c235817cff66c6..d3b79e361bdaf7 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -39678,7 +39678,13 @@ msgstr "" msgid "ZenTaoIntegration|ZenTao user" msgstr "" -msgid "ZentaoIntegration|Base URL of the Zentao instance." +msgid "Zentao issues" +msgstr "" + +msgid "ZentaoIntegration|An error occurred while requesting data from the ZenTao service." +msgstr "" + +msgid "ZentaoIntegration|Base URL of the ZenTao instance." msgstr "" msgid "ZentaoIntegration|Enter API token" @@ -39687,19 +39693,28 @@ msgstr "" msgid "ZentaoIntegration|If different from Web URL." msgstr "" -msgid "ZentaoIntegration|Use Zentao as this project's issue tracker." +msgid "ZentaoIntegration|Issue list" +msgstr "" + +msgid "ZentaoIntegration|Open ZenTao" +msgstr "" + +msgid "ZentaoIntegration|Use ZenTao as this project's issue tracker." +msgstr "" + +msgid "ZentaoIntegration|ZenTao API URL (optional)" msgstr "" -msgid "ZentaoIntegration|Zentao API URL (optional)" +msgid "ZentaoIntegration|ZenTao API token" msgstr "" -msgid "ZentaoIntegration|Zentao API token" +msgid "ZentaoIntegration|ZenTao Product ID" msgstr "" -msgid "ZentaoIntegration|Zentao Product ID" +msgid "ZentaoIntegration|ZenTao Web URL" msgstr "" -msgid "ZentaoIntegration|Zentao Web URL" +msgid "ZentaoIntegration|ZenTao issues" msgstr "" msgid "Zoom meeting added" diff --git a/spec/lib/gitlab/zentao/client_spec.rb b/spec/lib/gitlab/zentao/client_spec.rb index e3a335c1e89ae8..d9f574ebdf8c93 100644 --- a/spec/lib/gitlab/zentao/client_spec.rb +++ b/spec/lib/gitlab/zentao/client_spec.rb @@ -6,7 +6,23 @@ subject(:integration) { described_class.new(zentao_integration) } let(:zentao_integration) { create(:zentao_integration) } - let(:mock_get_products_url) { integration.send(:url, "products/#{zentao_integration.zentao_product_xid}") } + + def mock_get_products_url + integration.send(:url, "products/#{zentao_integration.zentao_product_xid}") + end + + def mock_fetch_issue_url(issue_id) + integration.send(:url, "issues/#{issue_id}") + end + + let(:mock_headers) do + { + headers: { + 'Content-Type' => 'application/json', + 'Token' => zentao_integration.api_token + } + } + end describe '#new' do context 'if integration is nil' do @@ -25,15 +41,6 @@ end describe '#fetch_product' do - let(:mock_headers) do - { - headers: { - 'Content-Type' => 'application/json', - 'Token' => zentao_integration.api_token - } - } - end - context 'with valid product' do let(:mock_response) { { 'id' => zentao_integration.zentao_product_xid } } @@ -71,15 +78,6 @@ end describe '#ping' do - let(:mock_headers) do - { - headers: { - 'Content-Type' => 'application/json', - 'Token' => zentao_integration.api_token - } - } - end - context 'with valid resource' do before do WebMock.stub_request(:get, mock_get_products_url) @@ -102,4 +100,29 @@ end end end + + describe '#fetch_issue' do + context 'with invalid id' do + let(:invalid_ids) { ['story', 'story-', '-', '123', ''] } + + it 'returns empty object' do + invalid_ids.each do |id| + expect(integration.fetch_issue(id)).to eq({}) + end + end + end + + context 'with valid id' do + let(:valid_ids) { %w[story-1 bug-23] } + + it 'fetches current issue' do + valid_ids.each do |id| + WebMock.stub_request(:get, mock_fetch_issue_url(id)) + .with(mock_headers).to_return(status: 200, body: { issue: { id: id } }.to_json) + + expect(integration.fetch_issue(id).dig('issue', 'id')).to eq id + end + end + end + end end diff --git a/spec/lib/gitlab/zentao/query_spec.rb b/spec/lib/gitlab/zentao/query_spec.rb new file mode 100644 index 00000000000000..f7495e640c32e2 --- /dev/null +++ b/spec/lib/gitlab/zentao/query_spec.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Zentao::Query do + let(:zentao_integration) { create(:zentao_integration) } + let(:params) { {} } + + subject(:query) { described_class.new(zentao_integration, ActionController::Parameters.new(params)) } + + describe '#issues' do + let(:response) { { 'page' => 1, 'total' => 0, 'limit' => 20, 'issues' => [] } } + + def expect_query_option_include(expected_params) + expect_next_instance_of(Gitlab::Zentao::Client) do |client| + expect(client).to receive(:fetch_issues) + .with(hash_including(expected_params)) + .and_return(response) + end + + query.issues + end + + context 'when params are empty' do + it 'fills default params' do + expect_query_option_include(status: 'opened', order: 'lastEditedDate_desc', labels: '') + end + end + + context 'when params contain valid options' do + let(:params) { { state: 'closed', sort: 'created_asc', labels: %w[Bugs Features] } } + + it 'fills params with standard of ZenTao' do + expect_query_option_include(status: 'closed', order: 'openedDate_asc', labels: 'Bugs,Features') + end + end + + context 'when params contain invalid options' do + let(:params) { { state: 'xxx', sort: 'xxx', labels: %w[xxx] } } + + it 'fills default params with standard of ZenTao' do + expect_query_option_include(status: 'opened', order: 'lastEditedDate_desc', labels: 'xxx') + end + end + end + + describe '#issue' do + let(:response) { { 'issue' => { 'id' => 'story-1' } } } + + before do + expect_next_instance_of(Gitlab::Zentao::Client) do |client| + expect(client).to receive(:fetch_issue) + .and_return(response) + end + end + + it 'returns issue object by client' do + expect(query.issue).to include('id' => 'story-1') + end + end +end diff --git a/spec/lib/sidebars/projects/menus/zentao_menu_spec.rb b/spec/lib/sidebars/projects/menus/zentao_menu_spec.rb new file mode 100644 index 00000000000000..f0bce6b7ea57fb --- /dev/null +++ b/spec/lib/sidebars/projects/menus/zentao_menu_spec.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Sidebars::Projects::Menus::ZentaoMenu do + it_behaves_like 'ZenTao menu with CE version' +end diff --git a/spec/support/helpers/usage_data_helpers.rb b/spec/support/helpers/usage_data_helpers.rb index 5ead1813439a4a..1aed5d9681860b 100644 --- a/spec/support/helpers/usage_data_helpers.rb +++ b/spec/support/helpers/usage_data_helpers.rb @@ -84,6 +84,7 @@ module UsageDataHelpers projects_jira_cloud_active projects_jira_dvcs_cloud_active projects_jira_dvcs_server_active + projects_zentao_active projects_slack_active projects_slack_slash_commands_active projects_custom_issue_tracker_active diff --git a/spec/support/shared_examples/lib/sidebars/projects/menus/zentao_menu_shared_examples.rb b/spec/support/shared_examples/lib/sidebars/projects/menus/zentao_menu_shared_examples.rb new file mode 100644 index 00000000000000..d3fd28727b5917 --- /dev/null +++ b/spec/support/shared_examples/lib/sidebars/projects/menus/zentao_menu_shared_examples.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.shared_examples 'ZenTao menu with CE version' do + let(:project) { create(:project, has_external_issue_tracker: true) } + let(:user) { project.owner } + let(:context) { Sidebars::Projects::Context.new(current_user: user, container: project) } + let(:zentao_integration) { create(:zentao_integration, project: project) } + + subject { described_class.new(context) } + + describe '#render?' do + context 'when issues integration is disabled' do + before do + zentao_integration.update!(active: false) + end + + it 'returns false' do + expect(subject.render?).to eq false + end + end + + context 'when issues integration is enabled' do + before do + zentao_integration.update!(active: true) + end + + it 'returns true' do + expect(subject.render?).to eq true + end + + it 'renders menu link' do + expect(subject.link).to eq zentao_integration.url + end + + it 'contains only open ZenTao item' do + expect(subject.renderable_items.map(&:item_id)).to match_array [:open_zentao] + end + end + end +end -- GitLab From b6269178153dcf0d5b3922ca756677e1cdf925f8 Mon Sep 17 00:00:00 2001 From: Baodong Date: Thu, 14 Oct 2021 09:38:28 +0800 Subject: [PATCH 02/16] Fix for indent --- .../integrations/zentao/issues/index.html.haml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ee/app/views/projects/integrations/zentao/issues/index.html.haml b/ee/app/views/projects/integrations/zentao/issues/index.html.haml index c58ff6297ce52f..3ed1265e38e1cd 100644 --- a/ee/app/views/projects/integrations/zentao/issues/index.html.haml +++ b/ee/app/views/projects/integrations/zentao/issues/index.html.haml @@ -2,9 +2,9 @@ - add_page_specific_style 'page_bundles/issues_list' .js-zentao-issues-list{ data: { issues_fetch_path: project_integrations_zentao_issues_path(@project, format: :json), -page: params[:page], -initial_state: params[:state], -initial_sort_by: params[:sort], -project_full_path: @project.full_path, -issue_create_url: @project.zentao_integration&.url, -empty_state_path: image_path('illustrations/issues.svg') } } + page: params[:page], + initial_state: params[:state], + initial_sort_by: params[:sort], + project_full_path: @project.full_path, + issue_create_url: @project.zentao_integration&.url, + empty_state_path: image_path('illustrations/issues.svg') } } -- GitLab From 432efd0d820e068fe79912af9b3e6f92448af6e6 Mon Sep 17 00:00:00 2001 From: Baodong Date: Thu, 14 Oct 2021 11:31:32 +0800 Subject: [PATCH 03/16] Remove tracker for ZenTao --- ...zentao_integration_list_issues_monthly.yml | 24 ------------------- ..._zentao_integration_list_issues_weekly.yml | 24 ------------------- ...20210730011803_templates_zentao_active.yml | 22 ----------------- .../integrations/zentao/issues_controller.rb | 8 ------- .../zentao/issues_controller_spec.rb | 8 ------- .../known_events/ecosystem.yml | 4 ---- spec/support/helpers/usage_data_helpers.rb | 1 - 7 files changed, 91 deletions(-) delete mode 100644 config/metrics/counts_28d/20210801073526_i_ecosystem_zentao_integration_list_issues_monthly.yml delete mode 100644 config/metrics/counts_7d/20210801073522_i_ecosystem_zentao_integration_list_issues_weekly.yml delete mode 100644 config/metrics/counts_all/20210730011803_templates_zentao_active.yml diff --git a/config/metrics/counts_28d/20210801073526_i_ecosystem_zentao_integration_list_issues_monthly.yml b/config/metrics/counts_28d/20210801073526_i_ecosystem_zentao_integration_list_issues_monthly.yml deleted file mode 100644 index 6bfdf8492373b2..00000000000000 --- a/config/metrics/counts_28d/20210801073526_i_ecosystem_zentao_integration_list_issues_monthly.yml +++ /dev/null @@ -1,24 +0,0 @@ ---- -key_path: redis_hll_counters.ecosystem.i_ecosystem_zentao_integration_list_issues_monthly -name: i_ecosystem_zentao_integration_list_issues_monthly -description: 'Count of Zentao issue list visits by month' -product_section: dev -product_stage: ecosystem -product_group: group::integrations -product_category: integrations -value_type: number -status: implemented -milestone: "14.4" -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/issues/338178 -time_frame: 28d -data_source: redis_hll -data_category: Optional -instrumentation_class: RedisHLLMetric -options: - events: - - i_ecosystem_zentao_integration_list_issues -distribution: -- ee -tier: -- premium -- ultimate \ No newline at end of file diff --git a/config/metrics/counts_7d/20210801073522_i_ecosystem_zentao_integration_list_issues_weekly.yml b/config/metrics/counts_7d/20210801073522_i_ecosystem_zentao_integration_list_issues_weekly.yml deleted file mode 100644 index 27f95b403b4449..00000000000000 --- a/config/metrics/counts_7d/20210801073522_i_ecosystem_zentao_integration_list_issues_weekly.yml +++ /dev/null @@ -1,24 +0,0 @@ ---- -key_path: redis_hll_counters.ecosystem.i_ecosystem_zentao_integration_list_issues_weekly -name: i_ecosystem_zentao_integration_list_issues_weekly -description: 'Count of Zentao issue list visits by week' -product_section: dev -product_stage: ecosystem -product_group: group::integrations -product_category: integrations -value_type: number -status: implemented -milestone: "14.4" -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/issues/338178 -time_frame: 7d -data_source: redis_hll -data_category: Optional -instrumentation_class: RedisHLLMetric -options: - events: - - i_ecosystem_zentao_integration_list_issues -distribution: -- ee -tier: -- premium -- ultimate diff --git a/config/metrics/counts_all/20210730011803_templates_zentao_active.yml b/config/metrics/counts_all/20210730011803_templates_zentao_active.yml deleted file mode 100644 index 5def7b5f1fc474..00000000000000 --- a/config/metrics/counts_all/20210730011803_templates_zentao_active.yml +++ /dev/null @@ -1,22 +0,0 @@ ---- -key_path: counts.templates_zentao_active -name: count_all_templates_zentao_active -description: Count of active service templates for Zentao -product_section: dev -product_stage: ecosystem -product_group: group::integrations -product_category: integrations -value_type: number -status: implemented -milestone: "14.4" -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/issues/338178 -time_frame: all -data_source: database -data_category: Operational -distribution: -- ce -- ee -tier: -- free -- premium -- ultimate diff --git a/ee/app/controllers/projects/integrations/zentao/issues_controller.rb b/ee/app/controllers/projects/integrations/zentao/issues_controller.rb index fdcb1f31fe061f..4708051370a9bb 100644 --- a/ee/app/controllers/projects/integrations/zentao/issues_controller.rb +++ b/ee/app/controllers/projects/integrations/zentao/issues_controller.rb @@ -5,9 +5,6 @@ module Integrations module Zentao class IssuesController < Projects::ApplicationController include RecordUserLastActivity - include RedisTracking - - track_redis_hll_event :index, name: 'i_ecosystem_zentao_integration_list_issues' before_action :check_feature_enabled! @@ -51,11 +48,6 @@ def issues_json .represent(query.issues, project: project) end - # for track data - def visitor_id - current_user&.id - end - def check_feature_enabled! return render_404 unless ::Integrations::Zentao.feature_flag_enabled?(project) diff --git a/ee/spec/controllers/projects/integrations/zentao/issues_controller_spec.rb b/ee/spec/controllers/projects/integrations/zentao/issues_controller_spec.rb index 11d0bff24ef76c..f60bd94ee8c153 100644 --- a/ee/spec/controllers/projects/integrations/zentao/issues_controller_spec.rb +++ b/ee/spec/controllers/projects/integrations/zentao/issues_controller_spec.rb @@ -36,14 +36,6 @@ expect(response).to render_template(:index) end - it 'tracks usage' do - expect(Gitlab::UsageDataCounters::HLLRedisCounter) - .to receive(:track_event) - .with('i_ecosystem_zentao_integration_list_issues', values: user.id) - - get :index, params: { namespace_id: project.namespace, project_id: project } - end - context 'json request' do let(:zentao_issue) { [] } diff --git a/lib/gitlab/usage_data_counters/known_events/ecosystem.yml b/lib/gitlab/usage_data_counters/known_events/ecosystem.yml index 7ad48defb32453..f594c6a1b7c365 100644 --- a/lib/gitlab/usage_data_counters/known_events/ecosystem.yml +++ b/lib/gitlab/usage_data_counters/known_events/ecosystem.yml @@ -52,7 +52,3 @@ category: ecosystem redis_slot: ecosystem aggregation: weekly -- name: i_ecosystem_zentao_integration_list_issues - category: ecosystem - redis_slot: ecosystem - aggregation: weekly diff --git a/spec/support/helpers/usage_data_helpers.rb b/spec/support/helpers/usage_data_helpers.rb index 1aed5d9681860b..5ead1813439a4a 100644 --- a/spec/support/helpers/usage_data_helpers.rb +++ b/spec/support/helpers/usage_data_helpers.rb @@ -84,7 +84,6 @@ module UsageDataHelpers projects_jira_cloud_active projects_jira_dvcs_cloud_active projects_jira_dvcs_server_active - projects_zentao_active projects_slack_active projects_slack_slash_commands_active projects_custom_issue_tracker_active -- GitLab From a1529c11087165e02e675dedbc45cff787378d22 Mon Sep 17 00:00:00 2001 From: Baodong Date: Thu, 14 Oct 2021 11:39:28 +0800 Subject: [PATCH 04/16] Fix ZenTao metrics status --- .../counts_all/20210730011801_projects_zentao_active.yml | 2 +- .../metrics/counts_all/20210730011802_groups_zentao_active.yml | 2 +- .../counts_all/20210730011804_instances_zentao_active.yml | 2 +- .../20210730011805_projects_inheriting_zentao_active.yml | 2 +- .../20210730011806_groups_inheriting_zentao_active.yml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/config/metrics/counts_all/20210730011801_projects_zentao_active.yml b/config/metrics/counts_all/20210730011801_projects_zentao_active.yml index 03a48ac5ffcebf..8a9c3f969a07cb 100644 --- a/config/metrics/counts_all/20210730011801_projects_zentao_active.yml +++ b/config/metrics/counts_all/20210730011801_projects_zentao_active.yml @@ -7,7 +7,7 @@ product_stage: ecosystem product_group: group::integrations product_category: integrations value_type: number -status: implemented +status: active milestone: "14.4" introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/issues/338178 time_frame: all diff --git a/config/metrics/counts_all/20210730011802_groups_zentao_active.yml b/config/metrics/counts_all/20210730011802_groups_zentao_active.yml index 09795f11fe3af3..c8a1dfbbdd22fd 100644 --- a/config/metrics/counts_all/20210730011802_groups_zentao_active.yml +++ b/config/metrics/counts_all/20210730011802_groups_zentao_active.yml @@ -7,7 +7,7 @@ product_stage: ecosystem product_group: group::integrations product_category: integrations value_type: number -status: implemented +status: active milestone: "14.4" introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/issues/338178 time_frame: all diff --git a/config/metrics/counts_all/20210730011804_instances_zentao_active.yml b/config/metrics/counts_all/20210730011804_instances_zentao_active.yml index f7d67dfe3ecfef..b1c2a9d8b13e5b 100644 --- a/config/metrics/counts_all/20210730011804_instances_zentao_active.yml +++ b/config/metrics/counts_all/20210730011804_instances_zentao_active.yml @@ -7,7 +7,7 @@ product_stage: ecosystem product_group: group::integrations product_category: integrations value_type: number -status: implemented +status: active milestone: "14.4" introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/issues/338178 time_frame: all diff --git a/config/metrics/counts_all/20210730011805_projects_inheriting_zentao_active.yml b/config/metrics/counts_all/20210730011805_projects_inheriting_zentao_active.yml index 58224a754248d2..34c48ada0ee71e 100644 --- a/config/metrics/counts_all/20210730011805_projects_inheriting_zentao_active.yml +++ b/config/metrics/counts_all/20210730011805_projects_inheriting_zentao_active.yml @@ -7,7 +7,7 @@ product_stage: ecosystem product_group: group::integrations product_category: integrations value_type: number -status: implemented +status: active milestone: "14.4" introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/issues/338178 time_frame: all diff --git a/config/metrics/counts_all/20210730011806_groups_inheriting_zentao_active.yml b/config/metrics/counts_all/20210730011806_groups_inheriting_zentao_active.yml index fbe8fc87939c88..4d374db026e80d 100644 --- a/config/metrics/counts_all/20210730011806_groups_inheriting_zentao_active.yml +++ b/config/metrics/counts_all/20210730011806_groups_inheriting_zentao_active.yml @@ -7,7 +7,7 @@ product_stage: ecosystem product_group: group::integrations product_category: integrations value_type: number -status: implemented +status: active milestone: "14.4" introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/issues/338178 time_frame: all -- GitLab From 6dad979429354ab062436eb15b5b73169168102c Mon Sep 17 00:00:00 2001 From: Baodong Date: Mon, 18 Oct 2021 11:27:01 +0800 Subject: [PATCH 05/16] Add state to ZentaoSerializers --- .../integrations/zentao_serializers/issue_entity.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ee/app/serializers/integrations/zentao_serializers/issue_entity.rb b/ee/app/serializers/integrations/zentao_serializers/issue_entity.rb index d9561def105d75..1195af108f249f 100644 --- a/ee/app/serializers/integrations/zentao_serializers/issue_entity.rb +++ b/ee/app/serializers/integrations/zentao_serializers/issue_entity.rb @@ -33,6 +33,10 @@ class IssueEntity < Grape::Entity sanitize(item['status']) end + expose :state do |item| + sanitize(item['status']) + end + expose :labels do |item| item['labels'].compact.map do |label| name = sanitize(label) -- GitLab From 31f4ac3b1d4344460dd6e9dd9f10cfca921ed9c0 Mon Sep 17 00:00:00 2001 From: Baodong Date: Mon, 18 Oct 2021 15:37:30 +0800 Subject: [PATCH 06/16] Use strong params for zentao/issues_controller --- .../projects/integrations/zentao/issues_controller.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ee/app/controllers/projects/integrations/zentao/issues_controller.rb b/ee/app/controllers/projects/integrations/zentao/issues_controller.rb index 4708051370a9bb..0d40c46f793d97 100644 --- a/ee/app/controllers/projects/integrations/zentao/issues_controller.rb +++ b/ee/app/controllers/projects/integrations/zentao/issues_controller.rb @@ -33,8 +33,12 @@ def show private + def query_params + params.permit(:id, :page, :limit, :search, :sort, :state, :labels) + end + def query - ::Gitlab::Zentao::Query.new(project.zentao_integration, params) + ::Gitlab::Zentao::Query.new(project.zentao_integration, query_params) end def issue_json -- GitLab From fdd38a98e892c45ca71971284faef1619844a3d8 Mon Sep 17 00:00:00 2001 From: Baodong Date: Mon, 18 Oct 2021 15:44:44 +0800 Subject: [PATCH 07/16] Tighten up the security of this regex --- lib/gitlab/zentao/client.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/gitlab/zentao/client.rb b/lib/gitlab/zentao/client.rb index 312f6957bfcc2a..6f460c97b156f4 100644 --- a/lib/gitlab/zentao/client.rb +++ b/lib/gitlab/zentao/client.rb @@ -44,7 +44,7 @@ def fetch_issue(issue_id) private def issue_id_pattern - /^\S+-\d+$/ + /\A\S+-\d+\z/ end def get(path, params = {}) -- GitLab From 255c2a5dbaa6c2464137eeeb90186b5156f0f365 Mon Sep 17 00:00:00 2001 From: Baodong Date: Mon, 18 Oct 2021 16:12:26 +0800 Subject: [PATCH 08/16] Refactor zentao query limit --- lib/gitlab/zentao/client.rb | 3 +-- lib/gitlab/zentao/query.rb | 22 +++++++++++++++++++--- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/lib/gitlab/zentao/client.rb b/lib/gitlab/zentao/client.rb index 6f460c97b156f4..71a46859c61ce1 100644 --- a/lib/gitlab/zentao/client.rb +++ b/lib/gitlab/zentao/client.rb @@ -31,8 +31,7 @@ def fetch_product(product_id) end def fetch_issues(params = {}) - get("products/#{zentao_product_xid}/issues", - params.reverse_merge(page: 1, limit: 20)) + get("products/#{zentao_product_xid}/issues", params) end def fetch_issue(issue_id) diff --git a/lib/gitlab/zentao/query.rb b/lib/gitlab/zentao/query.rb index 82c1528ff4aa90..08d74b7d8b335b 100644 --- a/lib/gitlab/zentao/query.rb +++ b/lib/gitlab/zentao/query.rb @@ -4,6 +4,8 @@ module Gitlab module Zentao class Query STATUSES = %w[all opened closed].freeze + ISSUES_DEFAULT_LIMIT = 20 + ISSUES_MAX_LIMIT = 50 attr_reader :client, :params @@ -29,13 +31,27 @@ def issue private def query_options - options = { + { order: query_order, status: query_status, - labels: query_labels + labels: query_labels, + page: query_page, + limit: query_limit, + search: query_search } + end + + def query_page + params[:page].presence || 1 + end + + def query_limit + limit = params[:limit].presence || ISSUES_DEFAULT_LIMIT + [limit.to_i, ISSUES_MAX_LIMIT].min + end - params.permit(:page, :limit, :search).merge(options) + def query_search + params[:search] || '' end def query_order -- GitLab From c5495868f49b76e80b069feb09df6ad54435d5f2 Mon Sep 17 00:00:00 2001 From: Baodong Date: Mon, 18 Oct 2021 16:36:59 +0800 Subject: [PATCH 09/16] Fix zentao client with raising exception --- lib/gitlab/zentao/client.rb | 6 +++--- lib/gitlab/zentao/query.rb | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/gitlab/zentao/client.rb b/lib/gitlab/zentao/client.rb index 71a46859c61ce1..32760fe7d2cb59 100644 --- a/lib/gitlab/zentao/client.rb +++ b/lib/gitlab/zentao/client.rb @@ -35,7 +35,7 @@ def fetch_issues(params = {}) end def fetch_issue(issue_id) - return {} unless issue_id_pattern.match(issue_id) + raise Gitlab::Zentao::Client::Error unless issue_id_pattern.match(issue_id) get("issues/#{issue_id}") end @@ -50,11 +50,11 @@ def get(path, params = {}) options = { headers: headers, query: params } response = Gitlab::HTTP.get(url(path), options) - return {} unless response.success? + raise Gitlab::Zentao::Client::Error unless response.success? Gitlab::Json.parse(response.body) rescue JSON::ParserError - {} + raise Gitlab::Zentao::Client::Error end def url(path) diff --git a/lib/gitlab/zentao/query.rb b/lib/gitlab/zentao/query.rb index 08d74b7d8b335b..8f3f7b6d316aee 100644 --- a/lib/gitlab/zentao/query.rb +++ b/lib/gitlab/zentao/query.rb @@ -16,6 +16,8 @@ def initialize(integration, params) def issues issues_response = client.fetch_issues(query_options) + return [] if issues_response.blank? + Kaminari.paginate_array( issues_response['issues'], limit: issues_response['limit'], -- GitLab From e07b70a36461ad4facd8d95be3505a99b0ad6f8b Mon Sep 17 00:00:00 2001 From: Baodong Date: Mon, 18 Oct 2021 16:57:42 +0800 Subject: [PATCH 10/16] Refactor sanitize of zentao_serializers --- .../integrations/zentao_serializers/issue_entity.rb | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/ee/app/serializers/integrations/zentao_serializers/issue_entity.rb b/ee/app/serializers/integrations/zentao_serializers/issue_entity.rb index 1195af108f249f..266af532362dfb 100644 --- a/ee/app/serializers/integrations/zentao_serializers/issue_entity.rb +++ b/ee/app/serializers/integrations/zentao_serializers/issue_entity.rb @@ -3,6 +3,7 @@ module Integrations module ZentaoSerializers class IssueEntity < Grape::Entity + include ActionView::Helpers::SanitizeHelper include RequestAwareEntity expose :id do |item| @@ -74,10 +75,6 @@ def project @project ||= options[:project] end - def sanitize(content) - ActionController::Base.helpers.sanitize(content) - end - def user_info(user) return {} unless user.present? -- GitLab From 1aaa6cde692c4a8a64ed031b96975d9342fbddd4 Mon Sep 17 00:00:00 2001 From: Baodong Date: Mon, 18 Oct 2021 17:00:18 +0800 Subject: [PATCH 11/16] Update I18N key of ZenTao issues --- .../views/projects/integrations/zentao/issues/index.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ee/app/views/projects/integrations/zentao/issues/index.html.haml b/ee/app/views/projects/integrations/zentao/issues/index.html.haml index 3ed1265e38e1cd..59ff44527e6032 100644 --- a/ee/app/views/projects/integrations/zentao/issues/index.html.haml +++ b/ee/app/views/projects/integrations/zentao/issues/index.html.haml @@ -1,4 +1,4 @@ -- page_title _('Zentao issues') +- page_title _('ZentaoIntegration|Zentao issues') - add_page_specific_style 'page_bundles/issues_list' .js-zentao-issues-list{ data: { issues_fetch_path: project_integrations_zentao_issues_path(@project, format: :json), -- GitLab From b289341e65743ba2bff430a571292379499dcd4d Mon Sep 17 00:00:00 2001 From: Baodong Date: Mon, 18 Oct 2021 17:01:33 +0800 Subject: [PATCH 12/16] Update index.html.haml --- .../views/projects/integrations/zentao/issues/index.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ee/app/views/projects/integrations/zentao/issues/index.html.haml b/ee/app/views/projects/integrations/zentao/issues/index.html.haml index 59ff44527e6032..16420a300a0cd8 100644 --- a/ee/app/views/projects/integrations/zentao/issues/index.html.haml +++ b/ee/app/views/projects/integrations/zentao/issues/index.html.haml @@ -6,5 +6,5 @@ initial_state: params[:state], initial_sort_by: params[:sort], project_full_path: @project.full_path, - issue_create_url: @project.zentao_integration&.url, + issue_create_url: @project.zentao_integration.url, empty_state_path: image_path('illustrations/issues.svg') } } -- GitLab From 531f79a5c11a3cde6c67355e9b47578af6952bc0 Mon Sep 17 00:00:00 2001 From: Baodong Date: Mon, 18 Oct 2021 17:19:15 +0800 Subject: [PATCH 13/16] Rename zentao rspec describe --- ee/spec/lib/ee/sidebars/projects/menus/zentao_menu_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ee/spec/lib/ee/sidebars/projects/menus/zentao_menu_spec.rb b/ee/spec/lib/ee/sidebars/projects/menus/zentao_menu_spec.rb index c69969b31633bc..37b9a5347b9502 100644 --- a/ee/spec/lib/ee/sidebars/projects/menus/zentao_menu_spec.rb +++ b/ee/spec/lib/ee/sidebars/projects/menus/zentao_menu_spec.rb @@ -10,7 +10,7 @@ subject { described_class.new(context) } - describe 'with STARTER_PLAN' do + describe 'when feature is not licensed' do before do stub_licensed_features(zentao_issues_integration: false) end @@ -18,7 +18,7 @@ it_behaves_like 'ZenTao menu with CE version' end - describe 'with PREMIUM_PLAN or ULTIMATE_PLAN' do + describe 'when feature is licensed' do before do stub_licensed_features(zentao_issues_integration: true) end -- GitLab From 18d8ad1e30b0f696d1f27fd49d86065f76ee0216 Mon Sep 17 00:00:00 2001 From: Baodong Date: Mon, 18 Oct 2021 17:39:39 +0800 Subject: [PATCH 14/16] Fix ZenTao integration helper text --- lib/api/helpers/integrations_helpers.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/api/helpers/integrations_helpers.rb b/lib/api/helpers/integrations_helpers.rb index cab4ee6d8bac78..7ae46abd0971c5 100644 --- a/lib/api/helpers/integrations_helpers.rb +++ b/lib/api/helpers/integrations_helpers.rb @@ -774,13 +774,13 @@ def self.integrations required: true, name: :url, type: String, - desc: 'The base URL to the ZenTao instance web interface which is being linked to this GitLab project. E.g., https://www.zentao.net' + desc: 'The base URL to the ZenTao instance web interface which is being linked to this GitLab project. For example, https://www.zentao.net' }, { required: false, name: :api_url, type: String, - desc: 'The base URL to the ZenTao instance API. Web URL value will be used if not set. E.g., https://www.zentao.net' + desc: 'The base URL to the ZenTao instance API. Web URL value will be used if not set. For example, https://www.zentao.net' }, { required: true, -- GitLab From f61c21cd03f7005aeed61825064f946f48476e08 Mon Sep 17 00:00:00 2001 From: Baodong Date: Mon, 18 Oct 2021 20:51:16 +0800 Subject: [PATCH 15/16] Update gitlab.pot --- locale/gitlab.pot | 3 +++ 1 file changed, 3 insertions(+) diff --git a/locale/gitlab.pot b/locale/gitlab.pot index d3b79e361bdaf7..f9fd2bd83d3c12 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -39717,6 +39717,9 @@ msgstr "" msgid "ZentaoIntegration|ZenTao issues" msgstr "" +msgid "ZentaoIntegration|Zentao issues" +msgstr "" + msgid "Zoom meeting added" msgstr "" -- GitLab From 650ae62ec27918611a735e83126436ee92b57133 Mon Sep 17 00:00:00 2001 From: Baodong Date: Mon, 18 Oct 2021 20:55:05 +0800 Subject: [PATCH 16/16] Fix ZenTao client test --- lib/gitlab/zentao/client.rb | 4 +--- spec/lib/gitlab/zentao/client_spec.rb | 10 +++++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/gitlab/zentao/client.rb b/lib/gitlab/zentao/client.rb index 32760fe7d2cb59..e22d57faeca425 100644 --- a/lib/gitlab/zentao/client.rb +++ b/lib/gitlab/zentao/client.rb @@ -15,10 +15,8 @@ def initialize(integration) end def ping - response = fetch_product(zentao_product_xid) - + response = fetch_product(zentao_product_xid) rescue {} active = response.fetch('deleted') == '0' rescue false - if active { success: true } else diff --git a/spec/lib/gitlab/zentao/client_spec.rb b/spec/lib/gitlab/zentao/client_spec.rb index d9f574ebdf8c93..6e56ac3a8ff0c3 100644 --- a/spec/lib/gitlab/zentao/client_spec.rb +++ b/spec/lib/gitlab/zentao/client_spec.rb @@ -61,7 +61,9 @@ def mock_fetch_issue_url(issue_id) end it 'fetches the empty product' do - expect(integration.fetch_product(zentao_integration.zentao_product_xid)).to eq({}) + expect do + integration.fetch_product(zentao_integration.zentao_product_xid) + end.to raise_error(Gitlab::Zentao::Client::Error) end end @@ -72,7 +74,9 @@ def mock_fetch_issue_url(issue_id) end it 'fetches the empty product' do - expect(integration.fetch_product(zentao_integration.zentao_product_xid)).to eq({}) + expect do + integration.fetch_product(zentao_integration.zentao_product_xid) + end.to raise_error(Gitlab::Zentao::Client::Error) end end end @@ -107,7 +111,7 @@ def mock_fetch_issue_url(issue_id) it 'returns empty object' do invalid_ids.each do |id| - expect(integration.fetch_issue(id)).to eq({}) + expect { integration.fetch_issue(id) }.to raise_error(Gitlab::Zentao::Client::Error) end end end -- GitLab