diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-01-15 06:10:30 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-01-15 06:10:30 +0300 |
commit | c9bdf919932b7285ef9920bdac955459340da8fe (patch) | |
tree | c133d05b5cb003de0c6bb2234afb884f6882ccd9 | |
parent | 8b75948934667904196aba319aedda25b00f06ff (diff) |
Add latest changes from gitlab-org/gitlab@master
38 files changed, 319 insertions, 443 deletions
diff --git a/app/assets/javascripts/snippets/components/snippet_header.vue b/app/assets/javascripts/snippets/components/snippet_header.vue index e65e55f6595..5ba62908b43 100644 --- a/app/assets/javascripts/snippets/components/snippet_header.vue +++ b/app/assets/javascripts/snippets/components/snippet_header.vue @@ -200,6 +200,13 @@ export default { <gl-avatar :size="24" :src="snippet.author.avatarUrl" /> <span class="bold">{{ snippet.author.name }}</span> </a> + <gl-emoji + v-if="snippet.author.status" + v-gl-tooltip + class="gl-vertical-align-baseline font-size-inherit gl-mr-1" + :title="snippet.author.status.message" + :data-name="snippet.author.status.emoji" + /> </template> </gl-sprintf> </div> diff --git a/app/graphql/queries/snippet/snippet.query.graphql b/app/graphql/queries/snippet/snippet.query.graphql index 2205dc26642..ebfc135c51c 100644 --- a/app/graphql/queries/snippet/snippet.query.graphql +++ b/app/graphql/queries/snippet/snippet.query.graphql @@ -59,6 +59,11 @@ query GetSnippetQuery($ids: [SnippetID!]) { name username webUrl + status { + __typename + emoji + message + } } } } diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index f2a9a927ded..2a1652cf2ba 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -44,7 +44,7 @@ module ApplicationHelper # current_controller?('gitlab/application') # => false def current_controller?(*args) args.any? do |v| - v.to_s.downcase == controller.controller_name || v.to_s.downcase == controller.controller_path + Gitlab::Utils.safe_downcase!(v.to_s) == controller.controller_name || Gitlab::Utils.safe_downcase!(v.to_s) == controller.controller_path end end @@ -59,7 +59,7 @@ module ApplicationHelper # current_action?(:create) # => false # current_action?(:new, :create) # => true def current_action?(*args) - args.any? { |v| v.to_s.downcase == action_name } + args.any? { |v| Gitlab::Utils.safe_downcase!(v.to_s) == action_name } end def admin_section? diff --git a/app/helpers/tab_helper.rb b/app/helpers/tab_helper.rb index 6fbe2642056..e81986d4453 100644 --- a/app/helpers/tab_helper.rb +++ b/app/helpers/tab_helper.rb @@ -72,7 +72,8 @@ module TabHelper # Add our custom class into the html_options, which may or may not exist # and which may or may not already have a :class key o = options.delete(:html_options) || {} - o[:class] = [*o[:class], klass].join(' ').strip + o[:class] = [*o[:class], klass].join(' ') + o[:class].strip! if block_given? content_tag(:li, capture(&block), o) diff --git a/app/models/project.rb b/app/models/project.rb index b03dcde9f3f..0905815cec2 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -147,7 +147,6 @@ class Project < ApplicationRecord has_many :boards # Project services - has_one :alerts_service has_one :campfire_service has_one :datadog_service has_one :discord_service @@ -1357,9 +1356,9 @@ class Project < ApplicationRecord end def disabled_services - return ['datadog'] unless Feature.enabled?(:datadog_ci_integration, self) + return %w(datadog alerts) unless Feature.enabled?(:datadog_ci_integration, self) - [] + %w(alerts) end def find_or_initialize_service(name) diff --git a/app/models/project_services/alerts_service.rb b/app/models/project_services/alerts_service.rb index 58d507971ca..4afce0dfe95 100644 --- a/app/models/project_services/alerts_service.rb +++ b/app/models/project_services/alerts_service.rb @@ -1,54 +1,10 @@ # frozen_string_literal: true -require 'securerandom' - +# This service is scheduled for removal. All records must +# be deleted before the class can be removed. +# https://gitlab.com/groups/gitlab-org/-/epics/5056 class AlertsService < Service - has_one :data, class_name: 'AlertsServiceData', autosave: true, - inverse_of: :service, foreign_key: :service_id - - attribute :token, :string - delegate :token, :token=, :token_changed?, :token_was, to: :data - - validates :token, presence: true, if: :activated? - - before_validation :prevent_token_assignment - before_validation :ensure_token, if: :activated? - - after_save :update_http_integration - - def url - return if instance? || template? - - url_helpers.project_alerts_notify_url(project, format: :json) - end - - def json_fields - super + %w(token) - end - - def editable? - false - end - - def show_active_box? - false - end - - def can_test? - false - end - - def title - _('Alerts endpoint') - end - - def description - _('Authorize external services to send alerts to GitLab') - end - - def detailed_description - description - end + before_save :prevent_save def self.to_param 'alerts' @@ -58,33 +14,15 @@ class AlertsService < Service %w() end - def data - super || build_data - end - private - def prevent_token_assignment - self.token = token_was if token.present? && token_changed? - end - - def ensure_token - self.token = generate_token if token.blank? - end - - def generate_token - SecureRandom.hex - end - - def url_helpers - Gitlab::Routing.url_helpers - end - - def update_http_integration - return unless project_id && type == 'AlertsService' + def prevent_save + errors.add(:base, _('Alerts endpoint is deprecated and should not be created or modified. Use HTTP Integrations instead.')) + log_error('Prevented attempt to save or update deprecated AlertsService') - AlertManagement::SyncAlertServiceDataService # rubocop: disable CodeReuse/ServiceClass - .new(self) - .execute + # Stops execution of callbacks and database operation while + # preserving expectations of #save (will not raise) & #save! (raises) + # https://guides.rubyonrails.org/active_record_callbacks.html#halting-execution + throw :abort # rubocop:disable Cop/BanCatchThrow end end diff --git a/app/models/project_services/alerts_service_data.rb b/app/models/project_services/alerts_service_data.rb deleted file mode 100644 index 827a4ef613a..00000000000 --- a/app/models/project_services/alerts_service_data.rb +++ /dev/null @@ -1,18 +0,0 @@ -# frozen_string_literal: true - -require 'securerandom' - -class AlertsServiceData < ApplicationRecord - belongs_to :service, class_name: 'AlertsService' - - validates :service, presence: true - - attr_encrypted :token, - mode: :per_attribute_iv, - key: Settings.attr_encrypted_db_key_base_truncated, - algorithm: 'aes-256-gcm' - - def token_changed? - attribute_changed?(:token) - end -end diff --git a/app/models/service.rb b/app/models/service.rb index 9f17279d0a3..e5626462dd3 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -19,7 +19,6 @@ class Service < ApplicationRecord PROJECT_SPECIFIC_SERVICE_NAMES = %w[ jenkins - alerts ].freeze # Fake services to help with local development. diff --git a/app/services/alert_management/sync_alert_service_data_service.rb b/app/services/alert_management/sync_alert_service_data_service.rb deleted file mode 100644 index 1ba197065c5..00000000000 --- a/app/services/alert_management/sync_alert_service_data_service.rb +++ /dev/null @@ -1,56 +0,0 @@ -# frozen_string_literal: true - -module AlertManagement - class SyncAlertServiceDataService - # @param alert_service [AlertsService] - def initialize(alert_service) - @alert_service = alert_service - end - - def execute - http_integration = find_http_integration - - result = if http_integration - update_integration_data(http_integration) - else - create_integration - end - - result ? ServiceResponse.success : ServiceResponse.error(message: 'Update failed') - end - - private - - attr_reader :alert_service - - def find_http_integration - AlertManagement::HttpIntegrationsFinder.new( - alert_service.project, - endpoint_identifier: ::AlertManagement::HttpIntegration::LEGACY_IDENTIFIER - ) - .execute - .first - end - - def create_integration - new_integration = AlertManagement::HttpIntegration.create( - project_id: alert_service.project_id, - name: 'HTTP endpoint', - endpoint_identifier: AlertManagement::HttpIntegration::LEGACY_IDENTIFIER, - active: alert_service.active, - encrypted_token: alert_service.data.encrypted_token, - encrypted_token_iv: alert_service.data.encrypted_token_iv - ) - - new_integration.persisted? - end - - def update_integration_data(http_integration) - http_integration.update( - active: alert_service.active, - encrypted_token: alert_service.data.encrypted_token, - encrypted_token_iv: alert_service.data.encrypted_token_iv - ) - end - end -end diff --git a/app/views/projects/services/alerts/_help.html.haml b/app/views/projects/services/alerts/_help.html.haml deleted file mode 100644 index 7abd198bea5..00000000000 --- a/app/views/projects/services/alerts/_help.html.haml +++ /dev/null @@ -1 +0,0 @@ -.js-alerts-service-settings{ data: alerts_settings_data(disabled: true) } diff --git a/app/views/projects/services/alerts/_top.html.haml b/app/views/projects/services/alerts/_top.html.haml deleted file mode 100644 index e3bcb6bd3a0..00000000000 --- a/app/views/projects/services/alerts/_top.html.haml +++ /dev/null @@ -1,8 +0,0 @@ -.row - .col-lg-12 - .gl-alert.gl-alert-info{ role: 'alert' } - = sprite_icon('information-o', css_class: 'gl-icon gl-alert-icon gl-alert-icon-no-title') - .gl-alert-body - = _('You can now manage alert endpoint configuration in the Alerts section on the Operations settings page. Fields on this page have been deprecated.') - .gl-alert-actions - = link_to _('Visit settings page'), project_settings_operations_path(@project, anchor: 'js-alert-management-settings'), class: 'btn gl-alert-action btn-info new-gl-button' diff --git a/changelogs/unreleased/262394-show-status-of-snippet-author.yml b/changelogs/unreleased/262394-show-status-of-snippet-author.yml new file mode 100644 index 00000000000..e7ed85c99ee --- /dev/null +++ b/changelogs/unreleased/262394-show-status-of-snippet-author.yml @@ -0,0 +1,5 @@ +--- +title: Show status of snippet author in header +merge_request: 51030 +author: Kev @KevSlashNull +type: fixed diff --git a/changelogs/unreleased/lower-allocations-nav.yml b/changelogs/unreleased/lower-allocations-nav.yml new file mode 100644 index 00000000000..f9ea2a20073 --- /dev/null +++ b/changelogs/unreleased/lower-allocations-nav.yml @@ -0,0 +1,5 @@ +--- +title: Lower allocations when building nav +merge_request: 51628 +author: +type: performance diff --git a/changelogs/unreleased/sy-remove-alerts-service.yml b/changelogs/unreleased/sy-remove-alerts-service.yml new file mode 100644 index 00000000000..0ca8f108a38 --- /dev/null +++ b/changelogs/unreleased/sy-remove-alerts-service.yml @@ -0,0 +1,5 @@ +--- +title: Remove deprecated generic alert integration in favor of HTTP Integrations +merge_request: 50913 +author: +type: removed diff --git a/db/post_migrate/20210107194543_remove_alerts_service_records.rb b/db/post_migrate/20210107194543_remove_alerts_service_records.rb new file mode 100644 index 00000000000..51a2f96ac7f --- /dev/null +++ b/db/post_migrate/20210107194543_remove_alerts_service_records.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +class RemoveAlertsServiceRecords < ActiveRecord::Migration[6.0] + DOWNTIME = false + + disable_ddl_transaction! + + class Service < ActiveRecord::Base + self.table_name = 'services' + end + + def up + Service.delete_by(type: 'AlertsService') + end + + def down + # no-op + end +end diff --git a/db/schema_migrations/20210107194543 b/db/schema_migrations/20210107194543 new file mode 100644 index 00000000000..997bdd98b5a --- /dev/null +++ b/db/schema_migrations/20210107194543 @@ -0,0 +1 @@ +d72cf1c88a060ccadd9f90cbef5ae7d4ea6a4416a6263d11a870e01b02d1f935
\ No newline at end of file diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql index 8c27a3b72d9..3c82e19e3c5 100644 --- a/doc/api/graphql/reference/gitlab_schema.graphql +++ b/doc/api/graphql/reference/gitlab_schema.graphql @@ -22981,7 +22981,6 @@ type ServiceEdge { } enum ServiceType { - ALERTS_SERVICE ASANA_SERVICE ASSEMBLA_SERVICE BAMBOO_SERVICE diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json index 6ae3f9afda7..e872ecf8ad8 100644 --- a/doc/api/graphql/reference/gitlab_schema.json +++ b/doc/api/graphql/reference/gitlab_schema.json @@ -66649,12 +66649,6 @@ "interfaces": null, "enumValues": [ { - "name": "ALERTS_SERVICE", - "description": null, - "isDeprecated": false, - "deprecationReason": null - }, - { "name": "ASANA_SERVICE", "description": null, "isDeprecated": false, diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 0a732c59832..70b6a29f1ed 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -4921,7 +4921,6 @@ State of a Sentry error. | Value | Description | | ----- | ----------- | -| `ALERTS_SERVICE` | | | `ASANA_SERVICE` | | | `ASSEMBLA_SERVICE` | | | `BAMBOO_SERVICE` | | diff --git a/doc/integration/jira_development_panel.md b/doc/integration/jira_development_panel.md index 9eef739c52b..bebb584b125 100644 --- a/doc/integration/jira_development_panel.md +++ b/doc/integration/jira_development_panel.md @@ -272,7 +272,13 @@ The GitLab user only needs access when adding a new namespace. For syncing with ![Configure namespace on GitLab Jira App](img/jira_dev_panel_setup_com_3.png) -After a namespace is added, all future commits, branches, and merge requests of all projects under that namespace are synced to Jira. Past Merge Request data is initially synced. Past branch and commit data cannot be synced at the moment. +After a namespace is added: + +- All future commits, branches, and merge requests of all projects under that namespace + are synced to Jira. +- From GitLab 13.8, past merge request data is synced to Jira. + +Support for syncing past branch and commit data [is planned](https://gitlab.com/gitlab-org/gitlab/-/issues/263240). For more information, see [Usage](#usage). diff --git a/doc/user/application_security/dast/index.md b/doc/user/application_security/dast/index.md index 0f7f490cdcf..395a8702d1b 100644 --- a/doc/user/application_security/dast/index.md +++ b/doc/user/application_security/dast/index.md @@ -214,7 +214,7 @@ variables: DAST_PASSWORD_FIELD: session[password] # the name of password field at the sign-in HTML form DAST_SUBMIT_FIELD: login # the `id` or `name` of the element that when clicked will submit the login form or the password form of a multi-page login process DAST_FIRST_SUBMIT_FIELD: next # the `id` or `name` of the element that when clicked will submit the username form of a multi-page login process - DAST_AUTH_EXCLUDE_URLS: http://example.com/sign-out,http://example.com/sign-out-2 # optional, URLs to skip during the authenticated scan; comma-separated, no spaces in between + DAST_EXCLUDE_URLS: http://example.com/sign-out,http://example.com/sign-out-2 # optional, URLs to skip during the authenticated scan; comma-separated, no spaces in between DAST_AUTH_VALIDATION_URL: http://example.com/loggedin_page # optional, a URL only accessible to logged in users that DAST can use to confirm successful authentication ``` @@ -570,7 +570,7 @@ DAST can be [configured](#customizing-the-dast-settings) using environment varia | `DAST_PASSWORD_FIELD` | string | The name of password field at the sign-in HTML form. | | `DAST_SKIP_TARGET_CHECK` | boolean | Set to `true` to prevent DAST from checking that the target is available before scanning. Default: `false`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/229067) in GitLab 13.8. | | `DAST_MASK_HTTP_HEADERS` | string | Comma-separated list of request and response headers to be masked (GitLab 13.1). Must contain **all** headers to be masked. Refer to [list of headers that are masked by default](#hide-sensitive-information). | -| `DAST_AUTH_EXCLUDE_URLS` | URLs | The URLs to skip during the authenticated scan; comma-separated. Regular expression syntax can be used to match multiple URLs. For example, `.*` matches an arbitrary character sequence. Not supported for API scans. | +| `DAST_EXCLUDE_URLS` | URLs | The URLs to skip during the authenticated scan; comma-separated. Regular expression syntax can be used to match multiple URLs. For example, `.*` matches an arbitrary character sequence. Not supported for API scans. In [GitLab 13.7 and earlier](https://gitlab.com/gitlab-org/security-products/dast/-/merge_requests/367), was `DAST_AUTH_EXCLUDE_URLS` (which we plan to support until GitLab 14.0). | | `DAST_FULL_SCAN_ENABLED` | boolean | Set to `true` to run a [ZAP Full Scan](https://github.com/zaproxy/zaproxy/wiki/ZAP-Full-Scan) instead of a [ZAP Baseline Scan](https://github.com/zaproxy/zaproxy/wiki/ZAP-Baseline-Scan). Default: `false` | | `DAST_FULL_SCAN_DOMAIN_VALIDATION_REQUIRED` | boolean | Set to `true` to require [domain validation](#domain-validation) when running DAST full scans. Not supported for API scans. Default: `false` | | `DAST_AUTO_UPDATE_ADDONS` | boolean | ZAP add-ons are pinned to specific versions in the DAST Docker image. Set to `true` to download the latest versions when the scan starts. Default: `false` | diff --git a/lib/api/helpers/services_helpers.rb b/lib/api/helpers/services_helpers.rb index 9d2fd9978d9..6101a8d307e 100644 --- a/lib/api/helpers/services_helpers.rb +++ b/lib/api/helpers/services_helpers.rb @@ -161,7 +161,6 @@ module API def self.services { - 'alerts' => [], 'asana' => [ { required: true, @@ -807,7 +806,6 @@ module API def self.service_classes [ - ::AlertsService, ::AsanaService, ::AssemblaService, ::BambooService, diff --git a/lib/gitlab/tracking.rb b/lib/gitlab/tracking.rb index 618e359211b..ca4afb4c19c 100644 --- a/lib/gitlab/tracking.rb +++ b/lib/gitlab/tracking.rb @@ -24,7 +24,9 @@ module Gitlab Gitlab::CurrentSettings.snowplow_enabled? end - def event(category, action, label: nil, property: nil, value: nil, context: nil) + def event(category, action, label: nil, property: nil, value: nil, context: [], standard_context: nil) + context.push(standard_context.to_context) if standard_context + snowplow.event(category, action, label: label, property: property, value: value, context: context) product_analytics.event(category, action, label: label, property: property, value: value, context: context) end diff --git a/lib/gitlab/tracking/standard_context.rb b/lib/gitlab/tracking/standard_context.rb new file mode 100644 index 00000000000..71dfe27dd5a --- /dev/null +++ b/lib/gitlab/tracking/standard_context.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +module Gitlab + module Tracking + class StandardContext + GITLAB_STANDARD_SCHEMA_URL = 'iglu:com.gitlab/gitlab_standard/jsonschema/1-0-1'.freeze + + def initialize(namespace: nil, project: nil, **data) + @namespace = namespace + @project = project + @data = data + end + + def namespace_id + namespace&.id + end + + def project_id + @project&.id + end + + def to_context + SnowplowTracker::SelfDescribingJson.new(GITLAB_STANDARD_SCHEMA_URL, to_h) + end + + private + + def namespace + @namespace || @project&.namespace + end + + def to_h + public_methods(false).each_with_object({}) do |method, hash| + next if method == :to_context + + hash[method] = public_send(method) # rubocop:disable GitlabSecurity/PublicSend + end.merge(@data) + end + end + end +end diff --git a/lib/gitlab/utils.rb b/lib/gitlab/utils.rb index 3df54e74b4f..29f02a5912a 100644 --- a/lib/gitlab/utils.rb +++ b/lib/gitlab/utils.rb @@ -174,6 +174,18 @@ module Gitlab rescue IPAddr::InvalidAddressError end + # A safe alternative to String#downcase! + # + # This will make copies of frozen strings but downcase unfrozen + # strings in place, reducing allocations. + def safe_downcase!(str) + if str.frozen? + str.downcase + else + str.downcase! || str + end + end + # Converts a string to an Addressable::URI object. # If the string is not a valid URI, it returns nil. # Param uri_string should be a String object. diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 4a36a464686..6cc8b4be071 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -2760,7 +2760,7 @@ msgstr "" msgid "Alerts" msgstr "" -msgid "Alerts endpoint" +msgid "Alerts endpoint is deprecated and should not be created or modified. Use HTTP Integrations instead." msgstr "" msgid "AlertsIntegrations|Alerts will be created through this integration" @@ -4151,9 +4151,6 @@ msgstr "" msgid "Authorize %{user} to use your account?" msgstr "" -msgid "Authorize external services to send alerts to GitLab" -msgstr "" - msgid "Authorized %{new_chat_name}" msgstr "" @@ -32306,9 +32303,6 @@ msgstr "" msgid "You can now export your security dashboard to a CSV report." msgstr "" -msgid "You can now manage alert endpoint configuration in the Alerts section on the Operations settings page. Fields on this page have been deprecated." -msgstr "" - msgid "You can now submit a merge request to get this change into the original branch." msgstr "" diff --git a/spec/factories/alerts_service_data.rb b/spec/factories/alerts_service_data.rb deleted file mode 100644 index 2dd1d0a714e..00000000000 --- a/spec/factories/alerts_service_data.rb +++ /dev/null @@ -1,8 +0,0 @@ -# frozen_string_literal: true - -FactoryBot.define do - factory :alerts_service_data do - service { association(:alerts_service) } - token { SecureRandom.hex } - end -end diff --git a/spec/factories/services.rb b/spec/factories/services.rb index 44b157014a5..18d3b2d99b7 100644 --- a/spec/factories/services.rb +++ b/spec/factories/services.rb @@ -38,24 +38,6 @@ FactoryBot.define do end end - factory :alerts_service do - active - project - type { 'AlertsService' } - - trait :active do - active { true } - end - - trait :inactive do - active { false } - end - - before(:create) do |service| - service.data = build(:alerts_service_data, service: service) - end - end - factory :drone_ci_service do project active { true } diff --git a/spec/factories/usage_data.rb b/spec/factories/usage_data.rb index f933461a07a..0ed4176109a 100644 --- a/spec/factories/usage_data.rb +++ b/spec/factories/usage_data.rb @@ -32,8 +32,8 @@ FactoryBot.define do create(:service, project: projects[2], type: 'CustomIssueTrackerService', active: true) create(:project_error_tracking_setting, project: projects[0]) create(:project_error_tracking_setting, project: projects[1], enabled: false) - create(:alerts_service, project: projects[0]) - create(:alerts_service, :inactive, project: projects[1]) + create(:service, project: projects[0], type: 'AlertsService', active: true) + create(:service, project: projects[1], type: 'AlertsService', active: false) alert_bot_issues = create_list(:incident, 2, project: projects[0], author: User.alert_bot) create_list(:incident, 2, project: projects[1], author: User.alert_bot) issues = create_list(:issue, 4, project: projects[0]) diff --git a/spec/features/projects/services/user_activates_alerts_spec.rb b/spec/features/projects/services/user_activates_alerts_spec.rb deleted file mode 100644 index e8eddea8ed0..00000000000 --- a/spec/features/projects/services/user_activates_alerts_spec.rb +++ /dev/null @@ -1,68 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe 'User activates Alerts', :js do - let_it_be(:project) { create(:project) } - let_it_be(:user) { create(:user) } - - let(:service_name) { 'alerts' } - let(:service_title) { 'Alerts endpoint' } - - before do - sign_in(user) - project.add_maintainer(user) - end - - context 'when service is deactivated' do - it 'user cannot activate service' do - visit_project_services - - expect(page).to have_link(service_title) - click_link(service_title) - - expect(page).to have_callout_message - expect(page).to have_toggle_active_disabled - end - end - - context 'when service is activated' do - let_it_be(:activated_alerts_service) do - create(:alerts_service, :active, project: project) - end - - before do - visit_alerts_service - end - - it 'user cannot change settings' do - expect(page).to have_callout_message - expect(page).to have_toggle_active_disabled - expect(page).to have_button_reset_key_disabled - end - end - - private - - def visit_project_services - visit(project_settings_integrations_path(project)) - end - - def visit_alerts_service - visit(edit_project_service_path(project, service_name)) - end - - def have_callout_message - within('.gl-alert') do - have_content('You can now manage alert endpoint configuration in the Alerts section on the Operations settings page.') - end - end - - def have_toggle_active_disabled - have_selector('#activated .project-feature-toggle.is-disabled') - end - - def have_button_reset_key_disabled - have_button('Reset key', disabled: true) - end -end diff --git a/spec/frontend/snippets/components/snippet_header_spec.js b/spec/frontend/snippets/components/snippet_header_spec.js index 5836de1fdbe..93a66db32c6 100644 --- a/spec/frontend/snippets/components/snippet_header_spec.js +++ b/spec/frontend/snippets/components/snippet_header_spec.js @@ -17,6 +17,8 @@ describe('Snippet header component', () => { let err; const originalRelativeUrlRoot = gon.relative_url_root; + const GlEmoji = { template: '<img/>' }; + function createComponent({ loading = false, permissions = {}, @@ -47,10 +49,15 @@ describe('Snippet header component', () => { }, stubs: { ApolloMutation, + GlEmoji, }, }); } + const findAuthorEmoji = () => wrapper.find(GlEmoji); + const findAuthoredMessage = () => wrapper.find('[data-testid="authored-message"]').text(); + const buttonCount = () => wrapper.findAll(GlButton).length; + beforeEach(() => { gon.relative_url_root = '/foo/'; snippet = { @@ -66,6 +73,7 @@ describe('Snippet header component', () => { project: null, author: { name: 'Thor Odinson', + status: null, }, blobs: [Blob], createdAt: new Date(differenceInMilliseconds(32 * 24 * 3600 * 1000)).toISOString(), @@ -100,17 +108,36 @@ describe('Snippet header component', () => { it('renders a message showing snippet creation date and author', () => { createComponent(); - const text = wrapper.find('[data-testid="authored-message"]').text(); + const text = findAuthoredMessage(); expect(text).toContain('Authored 1 month ago by'); expect(text).toContain('Thor Odinson'); }); + describe('author status', () => { + it('is rendered when it is set', () => { + snippet.author.status = { + message: 'At work', + emoji: 'hammer', + }; + createComponent(); + + expect(findAuthorEmoji().attributes('title')).toBe(snippet.author.status.message); + expect(findAuthorEmoji().attributes('data-name')).toBe(snippet.author.status.emoji); + }); + + it('is not rendered when the user has no status', () => { + createComponent(); + + expect(findAuthorEmoji().exists()).toBe(false); + }); + }); + it('renders a message showing only snippet creation date if author is null', () => { snippet.author = null; createComponent(); - const text = wrapper.find('[data-testid="authored-message"]').text(); + const text = findAuthoredMessage(); expect(text).toBe('Authored 1 month ago'); }); @@ -121,7 +148,7 @@ describe('Snippet header component', () => { updateSnippet: false, }, }); - expect(wrapper.findAll(GlButton).length).toEqual(0); + expect(buttonCount()).toEqual(0); createComponent({ permissions: { @@ -129,7 +156,7 @@ describe('Snippet header component', () => { updateSnippet: false, }, }); - expect(wrapper.findAll(GlButton).length).toEqual(1); + expect(buttonCount()).toEqual(1); createComponent({ permissions: { @@ -137,7 +164,7 @@ describe('Snippet header component', () => { updateSnippet: true, }, }); - expect(wrapper.findAll(GlButton).length).toEqual(2); + expect(buttonCount()).toEqual(2); createComponent({ permissions: { @@ -149,7 +176,7 @@ describe('Snippet header component', () => { canCreateSnippet: true, }); return wrapper.vm.$nextTick().then(() => { - expect(wrapper.findAll(GlButton).length).toEqual(3); + expect(buttonCount()).toEqual(3); }); }); diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 8798099f9f3..825513bdfc5 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -525,7 +525,6 @@ project: - designs - project_aliases - external_pull_requests -- alerts_service - grafana_integration - remove_source_branch_after_merge - deleting_user diff --git a/spec/lib/gitlab/tracking/standard_context_spec.rb b/spec/lib/gitlab/tracking/standard_context_spec.rb new file mode 100644 index 00000000000..acf7aeb303a --- /dev/null +++ b/spec/lib/gitlab/tracking/standard_context_spec.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Tracking::StandardContext do + let_it_be(:project) { create(:project) } + let_it_be(:namespace) { create(:namespace) } + + let(:snowplow_context) { subject.to_context } + + describe '#to_context' do + context 'with no arguments' do + it 'creates a Snowplow context with no data' do + snowplow_context.to_json[:data].each do |_, v| + expect(v).to be_nil + end + end + end + + context 'with extra data' do + subject { described_class.new(foo: 'bar') } + + it 'creates a Snowplow context with the given data' do + expect(snowplow_context.to_json.dig(:data, :foo)).to eq('bar') + end + end + + context 'with namespace' do + subject { described_class.new(namespace: namespace) } + + it 'creates a Snowplow context using the given data' do + expect(snowplow_context.to_json.dig(:data, :namespace_id)).to eq(namespace.id) + expect(snowplow_context.to_json.dig(:data, :project_id)).to be_nil + end + end + + context 'with project' do + subject { described_class.new(project: project) } + + it 'creates a Snowplow context using the given data' do + expect(snowplow_context.to_json.dig(:data, :namespace_id)).to eq(project.namespace.id) + expect(snowplow_context.to_json.dig(:data, :project_id)).to eq(project.id) + end + end + + context 'with project and namespace' do + subject { described_class.new(namespace: namespace, project: project) } + + it 'creates a Snowplow context using the given data' do + expect(snowplow_context.to_json.dig(:data, :namespace_id)).to eq(namespace.id) + expect(snowplow_context.to_json.dig(:data, :project_id)).to eq(project.id) + end + end + end +end diff --git a/spec/lib/gitlab/tracking_spec.rb b/spec/lib/gitlab/tracking_spec.rb index 57882de0974..8f1fd49f4c5 100644 --- a/spec/lib/gitlab/tracking_spec.rb +++ b/spec/lib/gitlab/tracking_spec.rb @@ -41,21 +41,42 @@ RSpec.describe Gitlab::Tracking do allow_any_instance_of(Gitlab::Tracking::Destinations::ProductAnalytics).to receive(:event) end - it 'delegates to snowplow destination' do - expect_any_instance_of(Gitlab::Tracking::Destinations::Snowplow) - .to receive(:event) - .with('category', 'action', label: 'label', property: 'property', value: 1.5, context: nil) + shared_examples 'delegates to destination' do |klass| + context 'with standard context' do + it "delegates to #{klass} destination" do + expect_any_instance_of(klass).to receive(:event) do |_, category, action, args| + expect(category).to eq('category') + expect(action).to eq('action') + expect(args[:label]).to eq('label') + expect(args[:property]).to eq('property') + expect(args[:value]).to eq(1.5) + expect(args[:context].length).to eq(1) + expect(args[:context].first.to_json[:schema]).to eq(Gitlab::Tracking::StandardContext::GITLAB_STANDARD_SCHEMA_URL) + expect(args[:context].first.to_json[:data]).to include(foo: 'bar') + end - described_class.event('category', 'action', label: 'label', property: 'property', value: 1.5) - end + described_class.event('category', 'action', label: 'label', property: 'property', value: 1.5, + standard_context: Gitlab::Tracking::StandardContext.new(foo: 'bar')) + end + end - it 'delegates to ProductAnalytics destination' do - expect_any_instance_of(Gitlab::Tracking::Destinations::ProductAnalytics) - .to receive(:event) - .with('category', 'action', label: 'label', property: 'property', value: 1.5, context: nil) + context 'without standard context' do + it "delegates to #{klass} destination" do + expect_any_instance_of(klass).to receive(:event) do |_, category, action, args| + expect(category).to eq('category') + expect(action).to eq('action') + expect(args[:label]).to eq('label') + expect(args[:property]).to eq('property') + expect(args[:value]).to eq(1.5) + end - described_class.event('category', 'action', label: 'label', property: 'property', value: 1.5) + described_class.event('category', 'action', label: 'label', property: 'property', value: 1.5) + end + end end + + include_examples 'delegates to destination', Gitlab::Tracking::Destinations::Snowplow + include_examples 'delegates to destination', Gitlab::Tracking::Destinations::ProductAnalytics end describe '.self_describing_event' do diff --git a/spec/lib/gitlab/utils_spec.rb b/spec/lib/gitlab/utils_spec.rb index 36257a0605b..1052d4cbacc 100644 --- a/spec/lib/gitlab/utils_spec.rb +++ b/spec/lib/gitlab/utils_spec.rb @@ -392,6 +392,23 @@ RSpec.describe Gitlab::Utils do end end + describe ".safe_downcase!" do + using RSpec::Parameterized::TableSyntax + + where(:str, :result) do + "test".freeze | "test" + "Test".freeze | "test" + "test" | "test" + "Test" | "test" + end + + with_them do + it "downcases the string" do + expect(described_class.safe_downcase!(str)).to eq(result) + end + end + end + describe '.parse_url' do it 'returns Addressable::URI object' do expect(described_class.parse_url('http://gitlab.com')).to be_instance_of(Addressable::URI) diff --git a/spec/migrations/remove_alerts_service_records_spec.rb b/spec/migrations/remove_alerts_service_records_spec.rb new file mode 100644 index 00000000000..eaf9f90b445 --- /dev/null +++ b/spec/migrations/remove_alerts_service_records_spec.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20210107194543_remove_alerts_service_records.rb') + +RSpec.describe RemoveAlertsServiceRecords do + let(:services) { table(:services) } + let(:alerts_service_data) { table(:alerts_service_data) } + + before do + 5.times do + service = services.create!(type: 'AlertsService') + alerts_service_data.create!(service_id: service.id) + end + + services.create!(type: 'SomeOtherType') + end + + it 'removes services records of type AlertsService and corresponding data', :aggregate_failures do + expect(services.count).to eq(6) + expect(alerts_service_data.count).to eq(5) + + migrate! + + expect(services.count).to eq(1) + expect(services.first.type).to eq('SomeOtherType') + expect(services.where(type: 'AlertsService')).to be_empty + expect(alerts_service_data.all).to be_empty + end +end diff --git a/spec/models/project_services/alerts_service_spec.rb b/spec/models/project_services/alerts_service_spec.rb index db25885c76a..75b91c29914 100644 --- a/spec/models/project_services/alerts_service_spec.rb +++ b/spec/models/project_services/alerts_service_spec.rb @@ -2,108 +2,38 @@ require 'spec_helper' +# AlertsService is stripped down to only required methods +# to avoid errors loading integration-related pages if +# records are present. RSpec.describe AlertsService do let_it_be(:project) { create(:project) } - let(:service_params) { { project: project, active: active } } - let(:active) { true } - let(:service) { described_class.new(service_params) } + subject(:service) { described_class.new(project: project) } - shared_context 'when active' do - let(:active) { true } - end - - shared_context 'when inactive' do - let(:active) { false } - end - - shared_context 'when persisted' do - before do - service.save! - service.reload - end - end - - describe '#url' do - include Gitlab::Routing - - subject { service.url } + it { is_expected.to be_valid } - it { is_expected.to eq(project_alerts_notify_url(project, format: :json)) } - end - - describe '#json_fields' do - subject { service.json_fields } + describe '#to_param' do + subject { service.to_param } - it { is_expected.to eq(%w(active token)) } + it { is_expected.to eq('alerts') } end - describe '#as_json' do - subject { service.as_json(only: service.json_fields) } + describe '#supported_events' do + subject { service.supported_events } - it { is_expected.to eq('active' => true, 'token' => nil) } + it { is_expected.to be_empty } end - describe '#token' do - shared_context 'reset token' do - before do - service.token = '' - service.valid? - end - end - - shared_context 'assign token' do |token| - before do - service.token = token - service.valid? - end - end - - shared_examples 'valid token' do - it { is_expected.to match(/\A\h{32}\z/) } - end - - shared_examples 'no token' do - it { is_expected.to be_blank } - end - - subject { service.token } - - context 'when active' do - include_context 'when active' - - context 'when resetting' do - let!(:previous_token) { service.token } - - include_context 'reset token' - - it_behaves_like 'valid token' - - it { is_expected.not_to eq(previous_token) } - end - - context 'when assigning' do - include_context 'assign token', 'random token' - - it_behaves_like 'valid token' - end - end - - context 'when inactive' do - include_context 'when inactive' - - context 'when resetting' do - let!(:previous_token) { service.token } - - include_context 'reset token' - - it_behaves_like 'no token' - end - end + describe '#save' do + it 'prevents records from being created or updated' do + expect(Gitlab::ProjectServiceLogger).to receive(:error).with( + hash_including(message: 'Prevented attempt to save or update deprecated AlertsService') + ) - context 'when persisted' do - include_context 'when persisted' + expect(service.save).to be_falsey - it_behaves_like 'valid token' + expect(service.errors.full_messages).to include( + 'Alerts endpoint is deprecated and should not be created or modified. Use HTTP Integrations instead.' + ) end end end diff --git a/spec/services/alert_management/sync_alert_service_data_service_spec.rb b/spec/services/alert_management/sync_alert_service_data_service_spec.rb deleted file mode 100644 index ecec60011db..00000000000 --- a/spec/services/alert_management/sync_alert_service_data_service_spec.rb +++ /dev/null @@ -1,55 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe AlertManagement::SyncAlertServiceDataService do - let_it_be(:alerts_service) do - AlertsService.skip_callback(:save, :after, :update_http_integration) - service = create(:alerts_service, :active) - AlertsService.set_callback(:save, :after, :update_http_integration) - - service - end - - describe '#execute' do - subject(:execute) { described_class.new(alerts_service).execute } - - context 'without http integration' do - it 'creates the integration' do - expect { execute } - .to change { AlertManagement::HttpIntegration.count }.by(1) - end - - it 'returns a success' do - expect(subject.success?).to eq(true) - end - end - - context 'existing legacy http integration' do - let_it_be(:integration) { create(:alert_management_http_integration, :legacy, project: alerts_service.project) } - - it 'updates the integration' do - expect { execute } - .to change { integration.reload.encrypted_token }.to(alerts_service.data.encrypted_token) - .and change { integration.encrypted_token_iv }.to(alerts_service.data.encrypted_token_iv) - end - - it 'returns a success' do - expect(subject.success?).to eq(true) - end - end - - context 'existing other http integration' do - let_it_be(:integration) { create(:alert_management_http_integration, project: alerts_service.project) } - - it 'creates the integration' do - expect { execute } - .to change { AlertManagement::HttpIntegration.count }.by(1) - end - - it 'returns a success' do - expect(subject.success?).to eq(true) - end - end - end -end |