diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 5e54f9cde35..79ca127e42f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -64,7 +64,7 @@ variables: BUILD_ASSETS_IMAGE: "false" ES_JAVA_OPTS: "-Xms256m -Xmx256m" ELASTIC_URL: "http://elastic:changeme@elasticsearch:9200" - DOCKER_VERSION: "19.03.0" + DOCKER_VERSION: "20.10.1" CACHE_CLASSES: "true" # Preparing custom clone path to reduce space used by all random forks diff --git a/.gitlab/ci/rails.gitlab-ci.yml b/.gitlab/ci/rails.gitlab-ci.yml index 59eabf2770c..f5e12f0730c 100644 --- a/.gitlab/ci/rails.gitlab-ci.yml +++ b/.gitlab/ci/rails.gitlab-ci.yml @@ -311,6 +311,14 @@ db:check-schema: script: - source scripts/schema_changed.sh +db:check-migrations: + extends: + - .db-job-base + - .rails:rules:ee-and-foss-mr-with-migration + script: + - scripts/validate_migration_schema + allow_failure: true + db:migrate-from-v12.10.0: extends: .db-job-base variables: diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml index 61e6b93a777..74f8dba8036 100644 --- a/.gitlab/ci/rules.gitlab-ci.yml +++ b/.gitlab/ci/rules.gitlab-ci.yml @@ -507,6 +507,12 @@ - <<: *if-merge-request changes: *db-patterns +.rails:rules:ee-and-foss-mr-with-migration: + rules: + - <<: *if-merge-request + changes: *db-patterns + - <<: *if-merge-request-title-run-all-rspec + .rails:rules:ee-and-foss-unit: rules: - changes: *backend-patterns diff --git a/Gemfile.lock b/Gemfile.lock index e046f9172d6..539657482c2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -5,59 +5,59 @@ GEM abstract_type (0.0.7) acme-client (2.0.6) faraday (>= 0.17, < 2.0.0) - actioncable (6.0.3.3) - actionpack (= 6.0.3.3) + actioncable (6.0.3.4) + actionpack (= 6.0.3.4) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (6.0.3.3) - actionpack (= 6.0.3.3) - activejob (= 6.0.3.3) - activerecord (= 6.0.3.3) - activestorage (= 6.0.3.3) - activesupport (= 6.0.3.3) + actionmailbox (6.0.3.4) + actionpack (= 6.0.3.4) + activejob (= 6.0.3.4) + activerecord (= 6.0.3.4) + activestorage (= 6.0.3.4) + activesupport (= 6.0.3.4) mail (>= 2.7.1) - actionmailer (6.0.3.3) - actionpack (= 6.0.3.3) - actionview (= 6.0.3.3) - activejob (= 6.0.3.3) + actionmailer (6.0.3.4) + actionpack (= 6.0.3.4) + actionview (= 6.0.3.4) + activejob (= 6.0.3.4) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (6.0.3.3) - actionview (= 6.0.3.3) - activesupport (= 6.0.3.3) + actionpack (6.0.3.4) + actionview (= 6.0.3.4) + activesupport (= 6.0.3.4) rack (~> 2.0, >= 2.0.8) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (6.0.3.3) - actionpack (= 6.0.3.3) - activerecord (= 6.0.3.3) - activestorage (= 6.0.3.3) - activesupport (= 6.0.3.3) + actiontext (6.0.3.4) + actionpack (= 6.0.3.4) + activerecord (= 6.0.3.4) + activestorage (= 6.0.3.4) + activesupport (= 6.0.3.4) nokogiri (>= 1.8.5) - actionview (6.0.3.3) - activesupport (= 6.0.3.3) + actionview (6.0.3.4) + activesupport (= 6.0.3.4) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.1, >= 1.2.0) - activejob (6.0.3.3) - activesupport (= 6.0.3.3) + activejob (6.0.3.4) + activesupport (= 6.0.3.4) globalid (>= 0.3.6) - activemodel (6.0.3.3) - activesupport (= 6.0.3.3) - activerecord (6.0.3.3) - activemodel (= 6.0.3.3) - activesupport (= 6.0.3.3) + activemodel (6.0.3.4) + activesupport (= 6.0.3.4) + activerecord (6.0.3.4) + activemodel (= 6.0.3.4) + activesupport (= 6.0.3.4) activerecord-explain-analyze (0.1.0) activerecord (>= 4) pg - activestorage (6.0.3.3) - actionpack (= 6.0.3.3) - activejob (= 6.0.3.3) - activerecord (= 6.0.3.3) + activestorage (6.0.3.4) + actionpack (= 6.0.3.4) + activejob (= 6.0.3.4) + activerecord (= 6.0.3.4) marcel (~> 0.3.1) - activesupport (6.0.3.3) + activesupport (6.0.3.4) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 0.7, < 2) minitest (~> 5.1) @@ -686,7 +686,7 @@ GEM activesupport (>= 4) railties (>= 4) request_store (~> 1.0) - loofah (2.7.0) + loofah (2.8.0) crass (~> 1.0.2) nokogiri (>= 1.5.9) lru_redux (1.1.0) @@ -895,20 +895,20 @@ GEM rack-test (1.1.0) rack (>= 1.0, < 3) rack-timeout (0.5.2) - rails (6.0.3.3) - actioncable (= 6.0.3.3) - actionmailbox (= 6.0.3.3) - actionmailer (= 6.0.3.3) - actionpack (= 6.0.3.3) - actiontext (= 6.0.3.3) - actionview (= 6.0.3.3) - activejob (= 6.0.3.3) - activemodel (= 6.0.3.3) - activerecord (= 6.0.3.3) - activestorage (= 6.0.3.3) - activesupport (= 6.0.3.3) + rails (6.0.3.4) + actioncable (= 6.0.3.4) + actionmailbox (= 6.0.3.4) + actionmailer (= 6.0.3.4) + actionpack (= 6.0.3.4) + actiontext (= 6.0.3.4) + actionview (= 6.0.3.4) + activejob (= 6.0.3.4) + activemodel (= 6.0.3.4) + activerecord (= 6.0.3.4) + activestorage (= 6.0.3.4) + activesupport (= 6.0.3.4) bundler (>= 1.3.0) - railties (= 6.0.3.3) + railties (= 6.0.3.4) sprockets-rails (>= 2.0.0) rails-controller-testing (1.0.5) actionpack (>= 5.0.1.rc1) @@ -922,15 +922,15 @@ GEM rails-i18n (6.0.0) i18n (>= 0.7, < 2) railties (>= 6.0.0, < 7) - railties (6.0.3.3) - actionpack (= 6.0.3.3) - activesupport (= 6.0.3.3) + railties (6.0.3.4) + actionpack (= 6.0.3.4) + activesupport (= 6.0.3.4) method_source rake (>= 0.8.7) thor (>= 0.20.3, < 2.0) rainbow (3.0.0) raindrops (0.19.1) - rake (13.0.1) + rake (13.0.3) rb-fsevent (0.10.4) rb-inotify (0.10.1) ffi (~> 1.0) @@ -1186,7 +1186,7 @@ GEM truncato (0.7.11) htmlentities (~> 4.3.1) nokogiri (>= 1.7.0, <= 2.0) - tzinfo (1.2.8) + tzinfo (1.2.9) thread_safe (~> 0.1) u2f (0.2.1) uber (0.1.0) @@ -1261,7 +1261,7 @@ GEM xpath (3.2.0) nokogiri (~> 1.8) yajl-ruby (1.4.1) - zeitwerk (2.4.1) + zeitwerk (2.4.2) PLATFORMS ruby diff --git a/app/assets/javascripts/diffs/utils/uuids.js b/app/assets/javascripts/diffs/utils/uuids.js index 12448350e62..1fe5f9f6499 100644 --- a/app/assets/javascripts/diffs/utils/uuids.js +++ b/app/assets/javascripts/diffs/utils/uuids.js @@ -11,7 +11,7 @@ * @typedef {String} UUIDv4 */ -import MersenneTwister from 'mersenne-twister'; +import { MersenneTwister } from 'fast-mersenne-twister'; import stringHash from 'string-hash'; import { isString } from 'lodash'; import { v4 } from 'uuid'; @@ -49,7 +49,7 @@ function randomValuesForUuid(prng) { const buffer = new ArrayBuffer(4); const view = new DataView(buffer); - view.setUint32(0, prng.random_int()); + view.setUint32(0, prng.randomNumber()); randomValues.push(view.getUint8(0), view.getUint8(1), view.getUint8(2), view.getUint8(3)); } diff --git a/app/assets/javascripts/jira_connect/api.js b/app/assets/javascripts/jira_connect/api.js new file mode 100644 index 00000000000..55f2ef4f820 --- /dev/null +++ b/app/assets/javascripts/jira_connect/api.js @@ -0,0 +1,24 @@ +import axios from 'axios'; + +const getJwt = async () => { + return AP.context.getToken(); +}; + +export const addSubscription = async (addPath, namespace) => { + const jwt = await getJwt(); + + return axios.post(addPath, { + jwt, + namespace_path: namespace, + }); +}; + +export const removeSubscription = async (removePath) => { + const jwt = await getJwt(); + + return axios.delete(removePath, { + params: { + jwt, + }, + }); +}; diff --git a/app/assets/javascripts/jira_connect/components/app.vue b/app/assets/javascripts/jira_connect/components/app.vue index 490bf2fdd66..4a58113db1f 100644 --- a/app/assets/javascripts/jira_connect/components/app.vue +++ b/app/assets/javascripts/jira_connect/components/app.vue @@ -1,6 +1,9 @@ + - + + + {{ s__('Integrations|Linked namespaces') }} + + diff --git a/app/assets/javascripts/jira_connect/index.js b/app/assets/javascripts/jira_connect/index.js index cc18d67aebd..bd261750ccd 100644 --- a/app/assets/javascripts/jira_connect/index.js +++ b/app/assets/javascripts/jira_connect/index.js @@ -1,6 +1,11 @@ import Vue from 'vue'; import $ from 'jquery'; +import setConfigs from '@gitlab/ui/dist/config'; +import Translate from '~/vue_shared/translate'; +import GlFeatureFlagsPlugin from '~/vue_shared/gl_feature_flags_plugin'; + import App from './components/app.vue'; +import { addSubscription, removeSubscription } from '~/jira_connect/api'; const store = { state: { @@ -27,46 +32,35 @@ const initJiraFormHandlers = () => { alert(error); }; - AP.getLocation((location) => { - $('.js-jira-connect-sign-in').each(function updateSignInLink() { - const updatedLink = `${$(this).attr('href')}?return_to=${location}`; - $(this).attr('href', updatedLink); + if (typeof AP.getLocation === 'function') { + AP.getLocation((location) => { + $('.js-jira-connect-sign-in').each(function updateSignInLink() { + const updatedLink = `${$(this).attr('href')}?return_to=${location}`; + $(this).attr('href', updatedLink); + }); }); - }); + } $('#add-subscription-form').on('submit', function onAddSubscriptionForm(e) { - const actionUrl = $(this).attr('action'); + const addPath = $(this).attr('action'); + const namespace = $('#namespace-input').val(); + e.preventDefault(); - AP.context.getToken((token) => { - // eslint-disable-next-line no-jquery/no-ajax - $.post(actionUrl, { - jwt: token, - namespace_path: $('#namespace-input').val(), - format: 'json', - }) - .done(reqComplete) - .fail((err) => reqFailed(err, 'Failed to add namespace. Please try again.')); - }); + addSubscription(addPath, namespace) + .then(reqComplete) + .catch((err) => reqFailed(err.response.data, 'Failed to add namespace. Please try again.')); }); $('.remove-subscription').on('click', function onRemoveSubscriptionClick(e) { - const href = $(this).attr('href'); + const removePath = $(this).attr('href'); e.preventDefault(); - AP.context.getToken((token) => { - // eslint-disable-next-line no-jquery/no-ajax - $.ajax({ - url: href, - method: 'DELETE', - data: { - jwt: token, - format: 'json', - }, - }) - .done(reqComplete) - .fail((err) => reqFailed(err, 'Failed to remove namespace. Please try again.')); - }); + removeSubscription(removePath) + .then(reqComplete) + .catch((err) => + reqFailed(err.response.data, 'Failed to remove namespace. Please try again.'), + ); }); }; @@ -75,6 +69,14 @@ function initJiraConnect() { initJiraFormHandlers(); + if (!el) { + return null; + } + + setConfigs(); + Vue.use(Translate); + Vue.use(GlFeatureFlagsPlugin); + return new Vue({ el, data: { diff --git a/app/assets/stylesheets/page_bundles/jira_connect.scss b/app/assets/stylesheets/page_bundles/jira_connect.scss index c3e49da92a6..ff989b474ad 100644 --- a/app/assets/stylesheets/page_bundles/jira_connect.scss +++ b/app/assets/stylesheets/page_bundles/jira_connect.scss @@ -2,6 +2,11 @@ // We should only import styles that we actually use. // @import '@gitlab/ui/src/scss/gitlab_ui'; +@import '@gitlab/ui/src/scss/bootstrap'; +@import 'bootstrap-vue/src/index'; + +@import '@gitlab/ui/src/scss/utilities'; + $atlaskit-border-color: #dfe1e6; .ac-content { @@ -40,14 +45,16 @@ $header-height: 40px; } .jira-connect-user { - float: right; - position: relative; - top: -30px; + font-size: $gl-font-size; + position: fixed; + top: 10px; + right: 20px; } .jira-connect-app { margin-top: $header-height; max-width: 600px; + min-height: 95vh; padding-top: 48px; padding-left: 16px; padding-right: 16px; @@ -108,5 +115,6 @@ svg { } .browser-limitations-notice { + font-size: $gl-font-size; margin-top: 32px; } diff --git a/app/assets/stylesheets/page_bundles/wiki.scss b/app/assets/stylesheets/page_bundles/wiki.scss index eb34e7f3876..9f0fa137910 100644 --- a/app/assets/stylesheets/page_bundles/wiki.scss +++ b/app/assets/stylesheets/page_bundles/wiki.scss @@ -15,11 +15,6 @@ padding: 11px 0; } - .wiki-page-title { - margin: 0; - font-size: 22px; - } - .wiki-last-edit-by { display: block; color: var(--gray-500, $gray-500); diff --git a/app/controllers/jira_connect/subscriptions_controller.rb b/app/controllers/jira_connect/subscriptions_controller.rb index 3ff12f29f10..161280a05fc 100644 --- a/app/controllers/jira_connect/subscriptions_controller.rb +++ b/app/controllers/jira_connect/subscriptions_controller.rb @@ -19,6 +19,9 @@ class JiraConnect::SubscriptionsController < JiraConnect::ApplicationController before_action :allow_rendering_in_iframe, only: :index before_action :verify_qsh_claim!, only: :index before_action :authenticate_user!, only: :create + before_action do + push_frontend_feature_flag(:new_jira_connect_ui, type: :development, default_enabled: :yaml) + end def index @subscriptions = current_jira_installation.subscriptions.preload_namespace_route diff --git a/app/helpers/jira_connect_helper.rb b/app/helpers/jira_connect_helper.rb new file mode 100644 index 00000000000..c30eb1b007a --- /dev/null +++ b/app/helpers/jira_connect_helper.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module JiraConnectHelper + def new_jira_connect_ui? + Feature.enabled?(:new_jira_connect_ui, type: :development, default_enabled: :yaml) + end +end diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index f04fcf34f43..2868e3ac80a 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -196,6 +196,10 @@ module Issuable is_a?(Issue) end + def supports_assignee? + false + end + def severity return IssuableSeverity::DEFAULT unless supports_severity? diff --git a/app/models/concerns/issue_available_features.rb b/app/models/concerns/issue_available_features.rb index 886db133a94..a9768d93148 100644 --- a/app/models/concerns/issue_available_features.rb +++ b/app/models/concerns/issue_available_features.rb @@ -9,7 +9,9 @@ module IssueAvailableFeatures class_methods do # EE only features are listed on EE::IssueAvailableFeatures def available_features_for_issue_types - {}.with_indifferent_access + { + assignee: %w(issue incident) + }.with_indifferent_access end end diff --git a/app/models/environment.rb b/app/models/environment.rb index 31a95bb1b5d..4f7f688a040 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -405,6 +405,11 @@ class Environment < ApplicationRecord deployment_platform.patch_ingress(deployment_namespace, ingress, data) end + def clear_all_caches + expire_etag_cache + clear_reactive_cache! + end + private def rollout_status_available? diff --git a/app/models/issue.rb b/app/models/issue.rb index 253f4465cd9..5da9f67f6ef 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -434,6 +434,10 @@ class Issue < ApplicationRecord moved_to || duplicated_to end + def supports_assignee? + issue_type_supports?(:assignee) + end + private def ensure_metrics diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 5b0b8dc4e55..db066184e91 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -1774,6 +1774,10 @@ class MergeRequest < ApplicationRecord false end + def supports_assignee? + true + end + private def with_rebase_lock diff --git a/app/serializers/merge_request_poll_cached_widget_entity.rb b/app/serializers/merge_request_poll_cached_widget_entity.rb index 080b6554de1..1db4ec37d4a 100644 --- a/app/serializers/merge_request_poll_cached_widget_entity.rb +++ b/app/serializers/merge_request_poll_cached_widget_entity.rb @@ -104,6 +104,16 @@ class MergeRequestPollCachedWidgetEntity < IssuableEntity presenter(merge_request).api_unapprove_path end + expose :blob_path do + expose :head_path, if: -> (mr, _) { mr.source_branch_sha } do |merge_request| + project_blob_path(merge_request.project, merge_request.source_branch_sha) + end + + expose :base_path, if: -> (mr, _) { mr.diff_base_sha } do |merge_request| + project_blob_path(merge_request.project, merge_request.diff_base_sha) + end + end + private delegate :current_user, to: :request diff --git a/app/serializers/merge_request_widget_entity.rb b/app/serializers/merge_request_widget_entity.rb index afd4d5b9a2b..ca4e16bc5ff 100644 --- a/app/serializers/merge_request_widget_entity.rb +++ b/app/serializers/merge_request_widget_entity.rb @@ -115,16 +115,6 @@ class MergeRequestWidgetEntity < Grape::Entity end end - expose :blob_path do - expose :head_path, if: -> (mr, _) { mr.source_branch_sha } do |merge_request| - project_blob_path(merge_request.project, merge_request.source_branch_sha) - end - - expose :base_path, if: -> (mr, _) { mr.diff_base_sha } do |merge_request| - project_blob_path(merge_request.project, merge_request.diff_base_sha) - end - end - expose :codeclimate, if: -> (mr, _) { head_pipeline_downloadable_path_for_report_type(:codequality) } do expose :head_path do |merge_request| head_pipeline_downloadable_path_for_report_type(:codequality) diff --git a/app/services/environments/canary_ingress/update_service.rb b/app/services/environments/canary_ingress/update_service.rb index 474c3de23d9..2b510280873 100644 --- a/app/services/environments/canary_ingress/update_service.rb +++ b/app/services/environments/canary_ingress/update_service.rb @@ -24,6 +24,7 @@ module Environments end if environment.patch_ingress(canary_ingress, patch_data) + environment.clear_all_caches success else error(_('Failed to update the Canary Ingress.'), :bad_request) diff --git a/app/services/groups/destroy_service.rb b/app/services/groups/destroy_service.rb index 1bff70e6c2e..c7107e2fa56 100644 --- a/app/services/groups/destroy_service.rb +++ b/app/services/groups/destroy_service.rb @@ -29,17 +29,32 @@ module Groups group.chat_team&.remove_mattermost_team(current_user) - user_ids_for_project_authorizations_refresh = group.user_ids_for_project_authorizations + # If any other groups are shared with the group that is being destroyed, + # we should specifically trigger update of all project authorizations + # for users that are the members of this group. + # If not, the project authorization records of these users to projects within the shared groups + # will never be removed, causing inconsistencies with access permissions. + if any_other_groups_are_shared_with_this_group? + user_ids_for_project_authorizations_refresh = group.user_ids_for_project_authorizations + end group.destroy - UserProjectAccessChangedService - .new(user_ids_for_project_authorizations_refresh) - .execute(blocking: true) + if user_ids_for_project_authorizations_refresh.present? + UserProjectAccessChangedService + .new(user_ids_for_project_authorizations_refresh) + .execute(blocking: true) + end group end # rubocop: enable CodeReuse/ActiveRecord + + private + + def any_other_groups_are_shared_with_this_group? + group.shared_group_links.any? + end end end diff --git a/app/views/admin/dev_ops_report/show.html.haml b/app/views/admin/dev_ops_report/show.html.haml index 75398f3aa21..19ce285162f 100644 --- a/app/views/admin/dev_ops_report/show.html.haml +++ b/app/views/admin/dev_ops_report/show.html.haml @@ -3,7 +3,7 @@ .container .gl-mt-3 - - if Gitlab.ee? && Feature.enabled?(:devops_adoption_feature, default_enabled: true) && License.feature_available?(:devops_adoption) + - if Gitlab.ee? && License.feature_available?(:devops_adoption) = render_if_exists 'admin/dev_ops_report/devops_tabs' - else = render 'report' diff --git a/app/views/jira_connect/subscriptions/index.html.haml b/app/views/jira_connect/subscriptions/index.html.haml index b826a1b6fc6..90a92f5c6d5 100644 --- a/app/views/jira_connect/subscriptions/index.html.haml +++ b/app/views/jira_connect/subscriptions/index.html.haml @@ -23,15 +23,16 @@ - else .js-jira-connect-app - %form#add-subscription-form.subscription-form{ action: jira_connect_subscriptions_path } - .ak-field-group - %label - GitLab namespace + - unless new_jira_connect_ui? + %form#add-subscription-form.subscription-form{ action: jira_connect_subscriptions_path } + .ak-field-group + %label + GitLab namespace - .ak-field-group.field-group-input - %input#namespace-input.ak-field-text{ type: 'text', required: true, placeholder: 'e.g. "MyCompany" or "MyCompany/GroupName"' } - %button.ak-button.ak-button__appearance-primary{ type: 'submit' } - Link namespace to Jira + .ak-field-group.field-group-input + %input#namespace-input.ak-field-text{ type: 'text', required: true, placeholder: 'e.g. "MyCompany" or "MyCompany/GroupName"' } + %button.ak-button.ak-button__appearance-primary{ type: 'submit' } + Link namespace to Jira - if @subscriptions.present? %table.subscriptions @@ -49,6 +50,7 @@ - else %h4.empty-subscriptions No linked namespaces + %p= s_('Integrations|Namespaces are your GitLab groups and subgroups that will be linked to this Jira instance.') %p.browser-limitations-notice %strong Browser limitations: diff --git a/app/views/layouts/jira_connect.html.haml b/app/views/layouts/jira_connect.html.haml index 0d4ecfc5a10..d996b3387a3 100644 --- a/app/views/layouts/jira_connect.html.haml +++ b/app/views/layouts/jira_connect.html.haml @@ -3,8 +3,9 @@ %meta{ content: "text/html; charset=utf-8", "http-equiv" => "Content-Type" } %title GitLab - = stylesheet_link_tag 'https://unpkg.com/@atlaskit/css-reset@3.0.6/dist/bundle.css' - = stylesheet_link_tag 'https://unpkg.com/@atlaskit/reduced-ui-pack@10.5.5/dist/bundle.css' + - unless new_jira_connect_ui? + = stylesheet_link_tag 'https://unpkg.com/@atlaskit/css-reset@3.0.6/dist/bundle.css' + = stylesheet_link_tag 'https://unpkg.com/@atlaskit/reduced-ui-pack@10.5.5/dist/bundle.css' = yield :page_specific_styles = javascript_include_tag 'https://connect-cdn.atl-paas.net/all.js' diff --git a/app/views/projects/protected_tags/shared/_create_protected_tag.html.haml b/app/views/projects/protected_tags/shared/_create_protected_tag.html.haml index c4bf2d20ecf..4d00762760e 100644 --- a/app/views/projects/protected_tags/shared/_create_protected_tag.html.haml +++ b/app/views/projects/protected_tags/shared/_create_protected_tag.html.haml @@ -16,7 +16,7 @@ %code v* or %code *-release - are supported + are supported. .form-group.row %label.col-md-2.text-right{ for: 'create_access_levels_attributes' } Allowed to create: diff --git a/app/views/projects/protected_tags/shared/_index.html.haml b/app/views/projects/protected_tags/shared/_index.html.haml index 4bf3ce09fc7..5734b7dc3c9 100644 --- a/app/views/projects/protected_tags/shared/_index.html.haml +++ b/app/views/projects/protected_tags/shared/_index.html.haml @@ -7,16 +7,14 @@ %button.btn.js-settings-toggle{ type: 'button' } = expanded ? 'Collapse' : 'Expand' %p - Limit access to creating and updating tags. + Limit access to creating and updating tags. #{link_to "What are protected tags?", help_page_path("user/project/protected_tags")} .settings-content %p - By default, protected tags are designed to: + By default, protected tags protect your code and: %ul - %li Prevent tag creation by everybody except Maintainers - %li Prevent anyone from updating the tag - %li Prevent anyone from deleting the tag - - %p Read more about #{link_to "protected tags", help_page_path("user/project/protected_tags")}. + %li Allow only users with Maintainer #{link_to "permissions", help_page_path("user/permissions")} to create tags. + %li Prevent anyone from updating tags. + %li Prevent anyone from deleting tags. - if can? current_user, :admin_project, @project = yield :create_protected_tag diff --git a/app/views/projects/protected_tags/shared/_tags_list.html.haml b/app/views/projects/protected_tags/shared/_tags_list.html.haml index 382ea848243..a5a43072744 100644 --- a/app/views/projects/protected_tags/shared/_tags_list.html.haml +++ b/app/views/projects/protected_tags/shared/_tags_list.html.haml @@ -1,9 +1,9 @@ .protected-tags-list.js-protected-tags-list - if @protected_tags.empty? .card-header - Protected tag (#{@protected_tags_count}) + Protected tags (#{@protected_tags_count}) %p.settings-message.text-center - There are currently no protected tags, protect a tag with the form above. + No tags are protected. - else - can_admin_project = can?(current_user, :admin_project, @project) @@ -16,7 +16,7 @@ %col %thead %tr - %th Protected tag (#{@protected_tags_count}) + %th Protected tags (#{@protected_tags_count}) %th Last commit %th Allowed to create - if can_admin_project diff --git a/app/views/shared/wikis/diff.html.haml b/app/views/shared/wikis/diff.html.haml index 68bbbd66f4a..19167f04855 100644 --- a/app/views/shared/wikis/diff.html.haml +++ b/app/views/shared/wikis/diff.html.haml @@ -5,12 +5,11 @@ .wiki-page-header.top-area.has-sidebar-toggle.flex-column.flex-lg-row = wiki_sidebar_toggle_button - .nav-text - %h2.wiki-page-title - = link_to_wiki_page @page - %span.light - · - = _('Changes') + %h3.page-title.gl-flex-fill-1 + = link_to_wiki_page @page + %span.light + · + = _('Changes') .nav-controls.pb-md-3.pb-lg-0 = link_to wiki_page_path(@wiki, @page, action: :history), class: 'btn gl-button', role: 'button', data: { qa_selector: 'page_history_button' } do diff --git a/app/views/shared/wikis/edit.html.haml b/app/views/shared/wikis/edit.html.haml index b289c018015..c2b0e474c03 100644 --- a/app/views/shared/wikis/edit.html.haml +++ b/app/views/shared/wikis/edit.html.haml @@ -6,15 +6,14 @@ .wiki-page-header.top-area.has-sidebar-toggle.flex-column.flex-lg-row = wiki_sidebar_toggle_button - .nav-text - %h2.wiki-page-title - - if @page.persisted? - = link_to_wiki_page @page - %span.light - · - = s_("Wiki|Edit Page") - - else - = s_("Wiki|Create New Page") + %h3.page-title.gl-flex-fill-1 + - if @page.persisted? + = link_to_wiki_page @page + %span.light + · + = s_("Wiki|Edit Page") + - else + = s_("Wiki|Create New Page") .nav-controls.pb-md-3.pb-lg-0 - if @page.persisted? diff --git a/app/views/shared/wikis/history.html.haml b/app/views/shared/wikis/history.html.haml index 50ccfdeabd5..b1dcd2cd400 100644 --- a/app/views/shared/wikis/history.html.haml +++ b/app/views/shared/wikis/history.html.haml @@ -4,12 +4,11 @@ .wiki-page-header.top-area.has-sidebar-toggle.flex-column.flex-lg-row = wiki_sidebar_toggle_button - .nav-text - %h2.wiki-page-title - = link_to_wiki_page @page - %span.light - · - = _('History') + %h3.page-title + = link_to_wiki_page @page + %span.light + · + = _('History') .prepend-top-default.gl-mb-3 .table-holder diff --git a/app/views/shared/wikis/pages.html.haml b/app/views/shared/wikis/pages.html.haml index 76fc9510740..f5ba1c83de4 100644 --- a/app/views/shared/wikis/pages.html.haml +++ b/app/views/shared/wikis/pages.html.haml @@ -6,9 +6,9 @@ .wiki-page-header.top-area.flex-column.flex-lg-row - .nav-text.flex-fill - %h2.wiki-page-title - = s_("Wiki|Wiki Pages") + + %h3.page-title.gl-flex-fill-1 + = s_("Wiki|Wiki Pages") .nav-controls.pb-md-3.pb-lg-0 = link_to wiki_path(@wiki, action: :git_access), class: 'btn gl-button' do diff --git a/changelogs/unreleased/223853-create-specialized-worker-for-refreshing-project-authorizations-du.yml b/changelogs/unreleased/223853-create-specialized-worker-for-refreshing-project-authorizations-du.yml new file mode 100644 index 00000000000..2c62828f41c --- /dev/null +++ b/changelogs/unreleased/223853-create-specialized-worker-for-refreshing-project-authorizations-du.yml @@ -0,0 +1,6 @@ +--- +title: During group deletion, only enqueue jobs for project_authorizations refresh + if the group being deleted has other groups shared with it +merge_request: 50617 +author: +type: performance diff --git a/changelogs/unreleased/296380-update-docker-from-19-03-to-20-10-on-ci-cd-of-gitlab.yml b/changelogs/unreleased/296380-update-docker-from-19-03-to-20-10-on-ci-cd-of-gitlab.yml new file mode 100644 index 00000000000..5765f032024 --- /dev/null +++ b/changelogs/unreleased/296380-update-docker-from-19-03-to-20-10-on-ci-cd-of-gitlab.yml @@ -0,0 +1,5 @@ +--- +title: Update Docker from 19.03.0 to 20.10.1 on CI/CD +merge_request: 50732 +author: Takuya Noguchi +type: other diff --git a/changelogs/unreleased/aqualls-okr-protected-tags.yml b/changelogs/unreleased/aqualls-okr-protected-tags.yml new file mode 100644 index 00000000000..b37a231a087 --- /dev/null +++ b/changelogs/unreleased/aqualls-okr-protected-tags.yml @@ -0,0 +1,5 @@ +--- +title: Updated UI text to match style guidelines +merge_request: 50476 +author: +type: other diff --git a/changelogs/unreleased/fix-canary-update-service-to-invalidate-cache.yml b/changelogs/unreleased/fix-canary-update-service-to-invalidate-cache.yml new file mode 100644 index 00000000000..aea2dec8268 --- /dev/null +++ b/changelogs/unreleased/fix-canary-update-service-to-invalidate-cache.yml @@ -0,0 +1,5 @@ +--- +title: Fix Canary Ingress weight is not reflected on UI immediately +merge_request: 50246 +author: +type: fixed diff --git a/changelogs/unreleased/maintenance-fast-mersenne-twister.yml b/changelogs/unreleased/maintenance-fast-mersenne-twister.yml new file mode 100644 index 00000000000..1fb389e333b --- /dev/null +++ b/changelogs/unreleased/maintenance-fast-mersenne-twister.yml @@ -0,0 +1,5 @@ +--- +title: Switch to 2x faster PRNG +merge_request: 50811 +author: +type: performance diff --git a/changelogs/unreleased/wiki-header-spacing.yml b/changelogs/unreleased/wiki-header-spacing.yml new file mode 100644 index 00000000000..032332aa9c1 --- /dev/null +++ b/changelogs/unreleased/wiki-header-spacing.yml @@ -0,0 +1,5 @@ +--- +title: Standardize page title styles on all wiki pages +merge_request: 49777 +author: +type: changed diff --git a/config/feature_flags/development/devops_adoption_feature.yml b/config/feature_flags/development/new_jira_connect_ui.yml similarity index 56% rename from config/feature_flags/development/devops_adoption_feature.yml rename to config/feature_flags/development/new_jira_connect_ui.yml index 34ade24cbc6..8783c1ed7fd 100644 --- a/config/feature_flags/development/devops_adoption_feature.yml +++ b/config/feature_flags/development/new_jira_connect_ui.yml @@ -1,8 +1,8 @@ --- -name: devops_adoption_feature -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/46005 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/271568 -milestone: '13.6' +name: new_jira_connect_ui +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/50692 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/295647 +milestone: '13.8' type: development -group: group::analytics -default_enabled: true +group: group::ecosystem +default_enabled: false diff --git a/danger/specs/Dangerfile b/danger/specs/Dangerfile index 72e0c8e92f4..26b52f64f2e 100644 --- a/danger/specs/Dangerfile +++ b/danger/specs/Dangerfile @@ -7,12 +7,12 @@ NO_SPECS_LABELS = [ 'documentation', 'QA' ].freeze -NO_NEW_SPEC_MESSAGE = <<~MSG +NO_NEW_SPEC_MESSAGE = <<~MSG.freeze You've made some app changes, but didn't add any tests. That's OK as long as you're refactoring existing code, but please consider adding any of the %s labels. MSG -EE_CHANGE_WITH_FOSS_SPEC_CHANGE_MESSAGE = <<~MSG +EE_CHANGE_WITH_FOSS_SPEC_CHANGE_MESSAGE = <<~MSG.freeze You've made some EE-specific changes, but only made changes to FOSS tests. This could be a sign that you're testing an EE-specific behavior in a FOSS test. @@ -24,6 +24,14 @@ Please make sure the spec files pass in AS-IF-FOSS mode either: MSG +CONTROLLER_SPEC_DEPRECATION_MESSAGE = <<~MSG.freeze +Do not add new controller specs. We are moving from controller specs to +request specs (and/or feature specs). Please add request specs under +`/spec/requests` and/or `/ee/spec/requests` instead. + +See https://gitlab.com/groups/gitlab-org/-/epics/5076 for information. +MSG + has_app_changes = helper.all_changed_files.grep(%r{\A(app|lib|db/(geo/)?(post_)?migrate)/}).any? has_ee_app_changes = helper.all_changed_files.grep(%r{\Aee/(app|lib|db/(geo/)?(post_)?migrate)/}).any? spec_changes = helper.all_changed_files.grep(%r{\Aspec/}) @@ -39,3 +47,8 @@ end if has_ee_app_changes && has_spec_changes && !(has_app_changes || has_ee_spec_changes) warn format(EE_CHANGE_WITH_FOSS_SPEC_CHANGE_MESSAGE, spec_files: spec_changes.join(" "), mr_title: gitlab.mr_json['title']), sticky: false end + +# Forbidding a new file addition under `/spec/controllers` or `/ee/spec/controllers` +if git.added_files.grep(%r{^(ee/)?spec/controllers/}).any? + warn CONTROLLER_SPEC_DEPRECATION_MESSAGE +end diff --git a/doc/user/admin_area/analytics/dev_ops_report.md b/doc/user/admin_area/analytics/dev_ops_report.md index 8f629fd4250..eb12dcbde9c 100644 --- a/doc/user/admin_area/analytics/dev_ops_report.md +++ b/doc/user/admin_area/analytics/dev_ops_report.md @@ -59,21 +59,3 @@ DevOps Adoption allows you to: - Find the groups that have adopted certain features and can provide guidance to other groups on how to use those features.  - -### Disable or enable DevOps Adoption - -DevOps Adoption is deployed behind a feature flag that is **enabled by default**. -[GitLab administrators with access to the GitLab Rails console](../../../administration/feature_flags.md) -can opt to disable it. - -To disable it: - -```ruby -Feature.disable(:devops_adoption_feature) -``` - -To enable it: - -```ruby -Feature.enable(:devops_adoption_feature) -``` diff --git a/jest.config.base.js b/jest.config.base.js index ffd365b9eaa..3ac6aa9091c 100644 --- a/jest.config.base.js +++ b/jest.config.base.js @@ -91,7 +91,7 @@ module.exports = (path) => { '^.+\\.(md|zip|png)$': 'jest-raw-loader', }, transformIgnorePatterns: [ - 'node_modules/(?!(@gitlab/ui|bootstrap-vue|three|monaco-editor|monaco-yaml)/)', + 'node_modules/(?!(@gitlab/ui|bootstrap-vue|three|monaco-editor|monaco-yaml|fast-mersenne-twister)/)', ], timers: 'fake', testEnvironment: '/spec/frontend/environment.js', diff --git a/lib/gitlab/quick_actions/issue_and_merge_request_actions.rb b/lib/gitlab/quick_actions/issue_and_merge_request_actions.rb index 6607c73a5c3..4934c12a339 100644 --- a/lib/gitlab/quick_actions/issue_and_merge_request_actions.rb +++ b/lib/gitlab/quick_actions/issue_and_merge_request_actions.rb @@ -26,7 +26,7 @@ module Gitlab end types Issue, MergeRequest condition do - current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project) + quick_action_target.supports_assignee? && current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project) end parse_params do |assignee_param| extract_users(assignee_param) diff --git a/locale/gitlab.pot b/locale/gitlab.pot index b4d3d0e23cf..48e5002c00c 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -15135,6 +15135,12 @@ msgstr "" msgid "Integrations|Issues created in Jira are shown here once you have created the issues in project setup in Jira." msgstr "" +msgid "Integrations|Linked namespaces" +msgstr "" + +msgid "Integrations|Namespaces are your GitLab groups and subgroups that will be linked to this Jira instance." +msgstr "" + msgid "Integrations|Projects using custom settings will not be affected." msgstr "" diff --git a/package.json b/package.json index b33944562f7..072c9efadfe 100644 --- a/package.json +++ b/package.json @@ -46,8 +46,8 @@ "@gitlab/tributejs": "1.0.0", "@gitlab/ui": "25.2.1", "@gitlab/visual-review-tools": "1.6.1", - "@rails/actioncable": "^6.0.3-3", - "@rails/ujs": "^6.0.3-2", + "@rails/actioncable": "^6.0.3-4", + "@rails/ujs": "^6.0.3-4", "@sourcegraph/code-host-integration": "0.0.52", "@toast-ui/editor": "^2.5.1", "@toast-ui/vue-editor": "^2.5.1", @@ -87,6 +87,7 @@ "emoji-regex": "^7.0.3", "emoji-unicode-version": "^0.2.1", "exports-loader": "^0.7.0", + "fast-mersenne-twister": "1.0.2", "file-loader": "^5.1.0", "font-awesome": "4.7.0", "fuzzaldrin-plus": "^0.6.0", @@ -110,7 +111,6 @@ "marked": "^0.3.12", "mathjax": "3", "mermaid": "^8.5.2", - "mersenne-twister": "1.1.0", "minimatch": "^3.0.4", "miragejs": "^0.1.40", "mock-apollo-client": "^0.5.0", diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock index 3ad9b4fed9e..2a5cd41a03c 100644 --- a/qa/Gemfile.lock +++ b/qa/Gemfile.lock @@ -2,7 +2,7 @@ GEM remote: https://rubygems.org/ specs: abstract_type (0.0.7) - activesupport (6.0.3.3) + activesupport (6.0.3.4) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 0.7, < 2) minitest (~> 5.1) @@ -127,7 +127,7 @@ GEM rubyzip (>= 1.2.2) thread_safe (0.3.6) timecop (0.9.1) - tzinfo (1.2.7) + tzinfo (1.2.9) thread_safe (~> 0.1) unf (0.1.4) unf_ext @@ -142,7 +142,7 @@ GEM procto (~> 0.0.2) xpath (3.2.0) nokogiri (~> 1.8) - zeitwerk (2.4.0) + zeitwerk (2.4.2) PLATFORMS ruby diff --git a/scripts/validate_migration_schema b/scripts/validate_migration_schema new file mode 100755 index 00000000000..95a9ae9f93f --- /dev/null +++ b/scripts/validate_migration_schema @@ -0,0 +1,119 @@ +#!/usr/bin/env ruby + +# frozen_string_literal: true + +require 'open3' + +class MigrationSchemaValidator + FILENAME = 'db/structure.sql' + + MIGRATION_DIRS = %w[db/migrate db/post_migrate].freeze + + SCHEMA_VERSION_DIR = 'db/schema_migrations' + + VERSION_DIGITS = 14 + + def validate! + if committed_migrations.empty? + puts "\e[32m No migrations found, skipping schema validation\e[0m" + return + end + + validate_schema_on_rollback! + validate_schema_on_migrate! + validate_schema_version_files! + end + + private + + def validate_schema_on_rollback! + committed_migrations.each do |filename| + version = find_migration_version(filename) + + run("bin/rails db:migrate:down VERSION=#{version}") + end + + git_command = "git diff #{diff_target} -- #{FILENAME}" + base_message = "rollback of added migrations does not revert #{FILENAME} to previous state" + + validate_clean_output!(git_command, base_message) + end + + def validate_schema_on_migrate! + run('bin/rails db:migrate') + + git_command = "git diff -- #{FILENAME}" + base_message = "the committed #{FILENAME} does not match the one generated by running added migrations" + + validate_clean_output!(git_command, base_message) + end + + def validate_schema_version_files! + git_command = "git add -A -n #{SCHEMA_VERSION_DIR}" + base_message = "the committed files in #{SCHEMA_VERSION_DIR} do not match those expected by the added migrations" + + validate_clean_output!(git_command, base_message) + end + + def committed_migrations + @committed_migrations ||= begin + git_command = "git diff --name-only --diff-filter=A #{diff_target} -- #{MIGRATION_DIRS.join(' ')}" + + run(git_command).split("\n") + end + end + + def diff_target + @diff_target ||= pipeline_for_merged_results? ? target_branch : merge_base + end + + def merge_base + run("git merge-base #{target_branch} #{source_ref}") + end + + def target_branch + ENV['CI_MERGE_REQUEST_TARGET_BRANCH_NAME'] || ENV['TARGET'] || 'master' + end + + def source_ref + ENV['CI_COMMIT_SHA'] || 'HEAD' + end + + def pipeline_for_merged_results? + ENV.key?('CI_MERGE_REQUEST_SOURCE_BRANCH_SHA') + end + + def find_migration_version(filename) + file_basename = File.basename(filename) + version_match = /\A(?\d{#{VERSION_DIGITS}})_/.match(file_basename) + + die "#{filename} has an invalid migration version" if version_match.nil? + + version_match[:version] + end + + def validate_clean_output!(command, base_message) + command_output = run(command) + + return if command_output.empty? + + die "#{base_message}:\n#{command_output}" + end + + def die(message, error_code: 1) + puts "\e[31mError: #{message}\e[0m" + exit error_code + end + + def run(cmd) + puts "\e[32m$ #{cmd}\e[37m" + stdout_str, stderr_str, status = Open3.capture3(cmd) + puts "#{stdout_str}#{stderr_str}\e[0m" + + die "command failed: #{stderr_str}" unless status.success? + + stdout_str.chomp + end +end + +MigrationSchemaValidator.new.validate! diff --git a/spec/features/protected_tags_spec.rb b/spec/features/protected_tags_spec.rb index 25447db3c8d..376f990f054 100644 --- a/spec/features/protected_tags_spec.rb +++ b/spec/features/protected_tags_spec.rb @@ -68,7 +68,7 @@ RSpec.describe 'Protected Tags', :js do click_on "Protect" within(".protected-tags-list") do - expect(page).to have_content("Protected tag (2)") + expect(page).to have_content("Protected tags (2)") expect(page).to have_content("2 matching tags") end end diff --git a/spec/frontend/jira_connect/api_spec.js b/spec/frontend/jira_connect/api_spec.js new file mode 100644 index 00000000000..e5a2484c827 --- /dev/null +++ b/spec/frontend/jira_connect/api_spec.js @@ -0,0 +1,75 @@ +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; +import httpStatus from '~/lib/utils/http_status'; + +import { addSubscription, removeSubscription } from '~/jira_connect/api'; + +describe('JiraConnect API', () => { + let mock; + let response; + + const mockAddPath = 'addPath'; + const mockRemovePath = 'removePath'; + const mockNamespace = 'namespace'; + const mockJwt = 'jwt'; + const mockResponse = { success: true }; + + const tokenSpy = jest.fn().mockReturnValue(mockJwt); + + window.AP = { + context: { + getToken: tokenSpy, + }, + }; + + beforeEach(() => { + mock = new MockAdapter(axios); + }); + + afterEach(() => { + mock.restore(); + response = null; + }); + + describe('addSubscription', () => { + const makeRequest = () => addSubscription(mockAddPath, mockNamespace); + + it('returns success response', async () => { + jest.spyOn(axios, 'post'); + mock + .onPost(mockAddPath, { + jwt: mockJwt, + namespace_path: mockNamespace, + }) + .replyOnce(httpStatus.OK, mockResponse); + + response = await makeRequest(); + + expect(tokenSpy).toHaveBeenCalled(); + expect(axios.post).toHaveBeenCalledWith(mockAddPath, { + jwt: mockJwt, + namespace_path: mockNamespace, + }); + expect(response.data).toEqual(mockResponse); + }); + }); + + describe('removeSubscription', () => { + const makeRequest = () => removeSubscription(mockRemovePath); + + it('returns success response', async () => { + jest.spyOn(axios, 'delete'); + mock.onDelete(mockRemovePath).replyOnce(httpStatus.OK, mockResponse); + + response = await makeRequest(); + + expect(tokenSpy).toHaveBeenCalled(); + expect(axios.delete).toHaveBeenCalledWith(mockRemovePath, { + params: { + jwt: mockJwt, + }, + }); + expect(response.data).toEqual(mockResponse); + }); + }); +}); diff --git a/spec/frontend/jira_connect/components/app_spec.js b/spec/frontend/jira_connect/components/app_spec.js new file mode 100644 index 00000000000..d2d0ced8d16 --- /dev/null +++ b/spec/frontend/jira_connect/components/app_spec.js @@ -0,0 +1,45 @@ +import { shallowMount } from '@vue/test-utils'; + +import JiraConnectApp from '~/jira_connect/components/app.vue'; + +describe('JiraConnectApp', () => { + let wrapper; + + const createComponent = (options = {}) => { + wrapper = shallowMount(JiraConnectApp, { + provide: { + glFeatures: { newJiraConnectUi: true }, + }, + ...options, + }); + }; + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); + + const findHeader = () => wrapper.find('h3'); + const findHeaderText = () => findHeader().text(); + + describe('template', () => { + it('renders new UI', () => { + createComponent(); + + expect(findHeader().exists()).toBe(true); + expect(findHeaderText()).toBe('Linked namespaces'); + }); + + describe('newJiraConnectUi is false', () => { + it('does not render new UI', () => { + createComponent({ + provide: { + glFeatures: { newJiraConnectUi: false }, + }, + }); + + expect(findHeader().exists()).toBe(false); + }); + }); + }); +}); diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb index 3e10ea847ba..0c7d8e2969d 100644 --- a/spec/models/environment_spec.rb +++ b/spec/models/environment_spec.rb @@ -1547,4 +1547,18 @@ RSpec.describe Environment, :use_clean_rails_memory_store_caching do end end end + + describe '#clear_all_caches' do + subject { environment.clear_all_caches } + + it 'clears all caches on the environment' do + expect_next_instance_of(Gitlab::EtagCaching::Store) do |store| + expect(store).to receive(:touch).with(environment.etag_cache_key) + end + + expect(environment).to receive(:clear_reactive_cache!) + + subject + end + end end diff --git a/spec/serializers/merge_request_poll_cached_widget_entity_spec.rb b/spec/serializers/merge_request_poll_cached_widget_entity_spec.rb index 031dc729a79..8c72430ff5c 100644 --- a/spec/serializers/merge_request_poll_cached_widget_entity_spec.rb +++ b/spec/serializers/merge_request_poll_cached_widget_entity_spec.rb @@ -8,6 +8,7 @@ RSpec.describe MergeRequestPollCachedWidgetEntity do let_it_be(:project, refind: true) { create :project, :repository } let_it_be(:resource, refind: true) { create(:merge_request, source_project: project, target_project: project) } let_it_be(:user) { create(:user) } + let(:pipeline) { create(:ci_empty_pipeline, project: project) } let(:request) { double('request', current_user: user, project: project) } @@ -25,6 +26,17 @@ RSpec.describe MergeRequestPollCachedWidgetEntity do expect(subject[:merge_status]).to eq 'checking' end + it 'has blob path data' do + allow(resource).to receive_messages( + base_pipeline: pipeline, + head_pipeline: pipeline + ) + + expect(subject).to include(:blob_path) + expect(subject[:blob_path]).to include(:base_path) + expect(subject[:blob_path]).to include(:head_path) + end + describe 'diverged_commits_count' do context 'when MR open and its diverging' do it 'returns diverged commits count' do diff --git a/spec/serializers/merge_request_widget_entity_spec.rb b/spec/serializers/merge_request_widget_entity_spec.rb index 9f734c08ef4..42d843af596 100644 --- a/spec/serializers/merge_request_widget_entity_spec.rb +++ b/spec/serializers/merge_request_widget_entity_spec.rb @@ -76,17 +76,6 @@ RSpec.describe MergeRequestWidgetEntity do .to eq("/#{resource.project.full_path}/-/merge_requests/#{resource.iid}.diff") end - it 'has blob path data' do - allow(resource).to receive_messages( - base_pipeline: pipeline, - head_pipeline: pipeline - ) - - expect(subject).to include(:blob_path) - expect(subject[:blob_path]).to include(:base_path) - expect(subject[:blob_path]).to include(:head_path) - end - describe 'codequality report artifacts', :request_store do let(:merge_base_pipeline) { create(:ci_pipeline, :with_codequality_report, project: project) } diff --git a/spec/services/environments/canary_ingress/update_service_spec.rb b/spec/services/environments/canary_ingress/update_service_spec.rb index 31d6f543817..5ba62e7104c 100644 --- a/spec/services/environments/canary_ingress/update_service_spec.rb +++ b/spec/services/environments/canary_ingress/update_service_spec.rb @@ -117,6 +117,12 @@ RSpec.describe Environments::CanaryIngress::UpdateService, :clean_gitlab_redis_c expect(subject[:status]).to eq(:success) expect(subject[:message]).to be_nil end + + it 'clears all caches' do + expect(environment).to receive(:clear_all_caches) + + subject + end end context 'when patch request does not succeed' do diff --git a/spec/services/groups/destroy_service_spec.rb b/spec/services/groups/destroy_service_spec.rb index e06f09d0463..2f9bb72939a 100644 --- a/spec/services/groups/destroy_service_spec.rb +++ b/spec/services/groups/destroy_service_spec.rb @@ -135,51 +135,120 @@ RSpec.describe Groups::DestroyService do end describe 'authorization updates', :sidekiq_inline do - context 'shared groups' do + context 'for solo groups' do + context 'group is deleted' do + it 'updates project authorization' do + expect { destroy_group(group, user, false) }.to( + change { user.can?(:read_project, project) }.from(true).to(false)) + end + + it 'does not make use of a specific service to update project_authorizations records' do + expect(UserProjectAccessChangedService) + .not_to receive(:new).with(group.user_ids_for_project_authorizations) + + destroy_group(group, user, false) + end + end + end + + context 'for shared groups within different hierarchies' do + let(:shared_with_group) { group } let!(:shared_group) { create(:group, :private) } let!(:shared_group_child) { create(:group, :private, parent: shared_group) } + let!(:shared_group_user) { create(:user) } let!(:project) { create(:project, group: shared_group) } let!(:project_child) { create(:project, group: shared_group_child) } before do - create(:group_group_link, shared_group: shared_group, shared_with_group: group) - group.refresh_members_authorized_projects + shared_group.add_user(shared_group_user, Gitlab::Access::OWNER) + + create(:group_group_link, shared_group: shared_group, shared_with_group: shared_with_group) + shared_with_group.refresh_members_authorized_projects end - it 'updates project authorization' do - expect(user.can?(:read_project, project)).to eq(true) - expect(user.can?(:read_project, project_child)).to eq(true) - - destroy_group(group, user, false) - - expect(user.can?(:read_project, project)).to eq(false) - expect(user.can?(:read_project, project_child)).to eq(false) - end - end - - context 'shared groups in the same group hierarchy' do - let!(:subgroup) { create(:group, :private, parent: group) } - let!(:subgroup_user) { create(:user) } - - before do - subgroup.add_user(subgroup_user, Gitlab::Access::MAINTAINER) - - create(:group_group_link, shared_group: group, shared_with_group: subgroup) - subgroup.refresh_members_authorized_projects - end - - context 'group is deleted' do + context 'the shared group is deleted' do it 'updates project authorization' do - expect { destroy_group(group, user, false) }.to( - change { subgroup_user.can?(:read_project, project) }.from(true).to(false)) + expect(shared_group_user.can?(:read_project, project)).to eq(true) + expect(shared_group_user.can?(:read_project, project_child)).to eq(true) + + destroy_group(shared_group, shared_group_user, false) + + expect(shared_group_user.can?(:read_project, project)).to eq(false) + expect(shared_group_user.can?(:read_project, project_child)).to eq(false) + end + + it 'does not make use of specific service to update project_authorizations records' do + expect(UserProjectAccessChangedService) + .not_to receive(:new).with(shared_group.user_ids_for_project_authorizations).and_call_original + + destroy_group(shared_group, shared_group_user, false) end end - context 'subgroup is deleted' do + context 'the shared_with group is deleted' do it 'updates project authorization' do - expect { destroy_group(subgroup, user, false) }.to( - change { subgroup_user.can?(:read_project, project) }.from(true).to(false)) + expect(user.can?(:read_project, project)).to eq(true) + expect(user.can?(:read_project, project_child)).to eq(true) + + destroy_group(shared_with_group, user, false) + + expect(user.can?(:read_project, project)).to eq(false) + expect(user.can?(:read_project, project_child)).to eq(false) + end + + it 'makes use of a specific service to update project_authorizations records' do + expect(UserProjectAccessChangedService) + .to receive(:new).with(shared_with_group.user_ids_for_project_authorizations).and_call_original + + destroy_group(shared_with_group, user, false) + end + end + end + + context 'for shared groups in the same group hierarchy' do + let(:shared_group) { group } + let(:shared_with_group) { nested_group } + let!(:shared_with_group_user) { create(:user) } + + before do + shared_with_group.add_user(shared_with_group_user, Gitlab::Access::MAINTAINER) + + create(:group_group_link, shared_group: shared_group, shared_with_group: shared_with_group) + shared_with_group.refresh_members_authorized_projects + end + + context 'the shared group is deleted' do + it 'updates project authorization' do + expect { destroy_group(shared_group, user, false) }.to( + change { shared_with_group_user.can?(:read_project, project) }.from(true).to(false)) + end + + it 'does not make use of a specific service to update project authorizations' do + # Due to the recursive nature of `Groups::DestroyService`, `UserProjectAccessChangedService` + # will still be executed for the nested group as they fall under the same hierarchy + # and hence we need to account for this scenario. + expect(UserProjectAccessChangedService) + .to receive(:new).with(shared_with_group.user_ids_for_project_authorizations).and_call_original + + expect(UserProjectAccessChangedService) + .not_to receive(:new).with(shared_group.user_ids_for_project_authorizations) + + destroy_group(shared_group, user, false) + end + end + + context 'the shared_with group is deleted' do + it 'updates project authorization' do + expect { destroy_group(shared_with_group, user, false) }.to( + change { shared_with_group_user.can?(:read_project, project) }.from(true).to(false)) + end + + it 'makes use of a specific service to update project authorizations' do + expect(UserProjectAccessChangedService) + .to receive(:new).with(shared_with_group.user_ids_for_project_authorizations).and_call_original + + destroy_group(shared_with_group, user, false) end end end diff --git a/spec/services/quick_actions/interpret_service_spec.rb b/spec/services/quick_actions/interpret_service_spec.rb index 14ff9684f3d..fb993a1ce17 100644 --- a/spec/services/quick_actions/interpret_service_spec.rb +++ b/spec/services/quick_actions/interpret_service_spec.rb @@ -777,6 +777,11 @@ RSpec.describe QuickActions::InterpretService do let(:issuable) { issue } end + it_behaves_like 'assign command' do + let(:content) { "/assign @#{developer.username}" } + let(:issuable) { create(:incident, project: project) } + end + it_behaves_like 'assign command' do let(:content) { "/assign @#{developer.username}" } let(:issuable) { merge_request } diff --git a/spec/support/shared_examples/features/wiki/user_creates_wiki_page_shared_examples.rb b/spec/support/shared_examples/features/wiki/user_creates_wiki_page_shared_examples.rb index 44d82d2e753..88bcf028b85 100644 --- a/spec/support/shared_examples/features/wiki/user_creates_wiki_page_shared_examples.rb +++ b/spec/support/shared_examples/features/wiki/user_creates_wiki_page_shared_examples.rb @@ -79,7 +79,7 @@ RSpec.shared_examples 'User creates wiki page' do expect(current_path).to eq(wiki_page_path(wiki, "test")) - page.within(:css, ".nav-text") do + page.within(:css, ".wiki-page-header") do expect(page).to have_content("Create New Page") end @@ -91,7 +91,7 @@ RSpec.shared_examples 'User creates wiki page' do expect(current_path).to eq(wiki_page_path(wiki, "api")) - page.within(:css, ".nav-text") do + page.within(:css, ".wiki-page-header") do expect(page).to have_content("Create") end @@ -103,7 +103,7 @@ RSpec.shared_examples 'User creates wiki page' do expect(current_path).to eq(wiki_page_path(wiki, "raketasks")) - page.within(:css, ".nav-text") do + page.within(:css, ".wiki-page-header") do expect(page).to have_content("Create") end end diff --git a/spec/support/shared_examples/features/wiki/user_uses_wiki_shortcuts_shared_examples.rb b/spec/support/shared_examples/features/wiki/user_uses_wiki_shortcuts_shared_examples.rb index 759cfaf6b1f..857d923785f 100644 --- a/spec/support/shared_examples/features/wiki/user_uses_wiki_shortcuts_shared_examples.rb +++ b/spec/support/shared_examples/features/wiki/user_uses_wiki_shortcuts_shared_examples.rb @@ -15,6 +15,6 @@ RSpec.shared_examples 'User uses wiki shortcuts' do it 'visit edit wiki page using "e" keyboard shortcut', :js do find('body').native.send_key('e') - expect(find('.wiki-page-title')).to have_content('Edit Page') + expect(find('.page-title')).to have_content('Edit Page') end end diff --git a/spec/support/shared_examples/features/wiki/user_views_wiki_page_shared_examples.rb b/spec/support/shared_examples/features/wiki/user_views_wiki_page_shared_examples.rb index af769be6d4b..61feeff57bb 100644 --- a/spec/support/shared_examples/features/wiki/user_views_wiki_page_shared_examples.rb +++ b/spec/support/shared_examples/features/wiki/user_views_wiki_page_shared_examples.rb @@ -44,7 +44,7 @@ RSpec.shared_examples 'User views a wiki page' do expect(current_path).to include('one/two/three-test') - page.within(:css, '.nav-text') do + page.within(:css, '.wiki-page-header') do expect(page).to have_content('History') end end @@ -69,7 +69,7 @@ RSpec.shared_examples 'User views a wiki page' do click_on('Page history') - within('.nav-text') do + within('.wiki-page-header') do expect(page).to have_content('History') end diff --git a/yarn.lock b/yarn.lock index 6eb8bc1ec73..edce7ab6dec 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1124,15 +1124,15 @@ consola "^2.10.1" node-fetch "^2.6.0" -"@rails/actioncable@^6.0.3-3": - version "6.0.3-3" - resolved "https://registry.yarnpkg.com/@rails/actioncable/-/actioncable-6.0.3-3.tgz#fb1a46d3d353512764d5fa3cea2f492391601b7a" - integrity sha512-+DEbtzvD2ITPKOGBAV0lLdUvImCsHtUYfxwW7TDKOrGNEFqgbVJij7aO6ZE8a3aDPQ7NnO/MlyrPwLVeiIZRSw== +"@rails/actioncable@^6.0.3-4": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@rails/actioncable/-/actioncable-6.1.0.tgz#f336f25450b1bc43b99bc60557a70b6e6bb1d3d2" + integrity sha512-eDgy+vcKN9RIzxmMBfSAe77rTj2cp6kJALiVQyKrW2O9EK2MdostOmP+99At/Dit3ur5+77NVnruxD7y14ZYFA== -"@rails/ujs@^6.0.3-2": - version "6.0.3-2" - resolved "https://registry.yarnpkg.com/@rails/ujs/-/ujs-6.0.3-2.tgz#e14c1f29086858215ce7ccd9ad6d8888c458b4a3" - integrity sha512-WcpIEftNCfGDEgk6KerOugiet75Mir5q/HT1yt3dDhpBI91BaZ15lfSQIsZwMw2nyeDz9A9QBz8dAFAd4gXIzg== +"@rails/ujs@^6.0.3-4": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@rails/ujs/-/ujs-6.1.0.tgz#9a48df6511cb2b472c9f596c1f37dc0af022e751" + integrity sha512-kQNKyM4ePAc4u9eR1c4OqrbAHH+3SJXt++8izIjeaZeg+P7yBtgoF/dogMD/JPPowNC74ACFpM/4op0Ggp/fPw== "@sindresorhus/is@^0.14.0": version "0.14.0" @@ -5009,6 +5009,11 @@ fast-levenshtein@~2.0.6: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= +fast-mersenne-twister@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/fast-mersenne-twister/-/fast-mersenne-twister-1.0.2.tgz#5ead7caf3ace592a5789d11767732bd81cbaaa56" + integrity sha512-IaClPxsoBu3MxGpcURyjV8otT5Bj4ARoK0KBCJGnEVnD1A/qclL5eIeYiUuwG/WWJPxL1jlK61HTm2T6SBmvBQ== + fault@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/fault/-/fault-1.0.2.tgz#c3d0fec202f172a3a4d414042ad2bb5e2a3ffbaa" @@ -8190,11 +8195,6 @@ mermaid@^8.5.2: moment-mini "^2.22.1" scope-css "^1.2.1" -mersenne-twister@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/mersenne-twister/-/mersenne-twister-1.1.0.tgz#f916618ee43d7179efcf641bec4531eb9670978a" - integrity sha1-+RZhjuQ9cXnvz2Qb7EUx65Zwl4o= - methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"