diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8a111b1f695..d56ff9bab7a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -17,7 +17,7 @@ stages: # in cases where jobs require Docker-in-Docker, the job # definition must be extended with `.use-docker-in-docker` default: - image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.2-golang-1.14-git-2.28-lfs-2.9-chrome-85-node-12.18-yarn-1.22-postgresql-11-graphicsmagick-1.3.34" + image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.2-golang-1.14-git-2.29-lfs-2.9-chrome-85-node-12.18-yarn-1.22-postgresql-11-graphicsmagick-1.3.34" tags: - gitlab-org # All jobs are interruptible by default diff --git a/.gitlab/ci/frontend.gitlab-ci.yml b/.gitlab/ci/frontend.gitlab-ci.yml index 1b78996282c..3d48c872d52 100644 --- a/.gitlab/ci/frontend.gitlab-ci.yml +++ b/.gitlab/ci/frontend.gitlab-ci.yml @@ -15,7 +15,7 @@ extends: - .frontend-base - .assets-compile-cache - image: registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.2-git-2.28-lfs-2.9-node-12.18-yarn-1.22-graphicsmagick-1.3.34 + image: registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.2-git-2.29-lfs-2.9-node-12.18-yarn-1.22-graphicsmagick-1.3.34 variables: WEBPACK_VENDOR_DLL: "true" stage: prepare diff --git a/.gitlab/ci/global.gitlab-ci.yml b/.gitlab/ci/global.gitlab-ci.yml index 3ff17ce05d2..0fafd5869d9 100644 --- a/.gitlab/ci/global.gitlab-ci.yml +++ b/.gitlab/ci/global.gitlab-ci.yml @@ -71,7 +71,7 @@ policy: pull .use-pg11: - image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.2-golang-1.14-git-2.28-lfs-2.9-chrome-85-node-12.18-yarn-1.22-postgresql-11-graphicsmagick-1.3.34" + image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.2-golang-1.14-git-2.29-lfs-2.9-chrome-85-node-12.18-yarn-1.22-postgresql-11-graphicsmagick-1.3.34" services: - name: postgres:11.6 command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"] @@ -80,7 +80,7 @@ POSTGRES_HOST_AUTH_METHOD: trust .use-pg12: - image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.2-golang-1.14-git-2.28-lfs-2.9-chrome-85-node-12.18-yarn-1.22-postgresql-12-graphicsmagick-1.3.34" + image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.2-golang-1.14-git-2.29-lfs-2.9-chrome-85-node-12.18-yarn-1.22-postgresql-12-graphicsmagick-1.3.34" services: - name: postgres:12 command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"] @@ -89,7 +89,7 @@ POSTGRES_HOST_AUTH_METHOD: trust .use-pg11-ee: - image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.2-golang-1.14-git-2.28-lfs-2.9-chrome-85-node-12.18-yarn-1.22-postgresql-11-graphicsmagick-1.3.34" + image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.2-golang-1.14-git-2.29-lfs-2.9-chrome-85-node-12.18-yarn-1.22-postgresql-11-graphicsmagick-1.3.34" services: - name: postgres:11.6 command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"] @@ -100,7 +100,7 @@ POSTGRES_HOST_AUTH_METHOD: trust .use-pg12-ee: - image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.2-golang-1.14-git-2.28-lfs-2.9-chrome-85-node-12.18-yarn-1.22-postgresql-12-graphicsmagick-1.3.34" + image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.2-golang-1.14-git-2.29-lfs-2.9-chrome-85-node-12.18-yarn-1.22-postgresql-12-graphicsmagick-1.3.34" services: - name: postgres:12 command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"] diff --git a/app/assets/javascripts/editor/editor_lite.js b/app/assets/javascripts/editor/editor_lite.js index e52e64d4c2d..e7535c211db 100644 --- a/app/assets/javascripts/editor/editor_lite.js +++ b/app/assets/javascripts/editor/editor_lite.js @@ -6,6 +6,7 @@ import { registerLanguages } from '~/ide/utils'; import { joinPaths } from '~/lib/utils/url_utility'; import { clearDomElement } from './utils'; import { EDITOR_LITE_INSTANCE_ERROR_NO_EL, URI_PREFIX } from './constants'; +import { uuids } from '~/diffs/utils/uuids'; export default class Editor { constructor(options = {}) { @@ -72,7 +73,7 @@ export default class Editor { el = undefined, blobPath = '', blobContent = '', - blobGlobalId = '', + blobGlobalId = uuids()[0], extensions = [], ...instanceOptions } = {}) { diff --git a/app/assets/javascripts/users_select/index.js b/app/assets/javascripts/users_select/index.js index 20d1a3c1fcd..dccd6807f13 100644 --- a/app/assets/javascripts/users_select/index.js +++ b/app/assets/javascripts/users_select/index.js @@ -14,6 +14,7 @@ import ModalStore from '../boards/stores/modal_store'; import { parseBoolean, spriteIcon } from '../lib/utils/common_utils'; import { getAjaxUsersSelectOptions, getAjaxUsersSelectParams } from './utils'; import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown'; +import { fixTitle, dispose } from '~/tooltips'; // TODO: remove eventHub hack after code splitting refactor window.emitSidebarEvent = window.emitSidebarEvent || $.noop; @@ -229,7 +230,9 @@ function UsersSelect(currentUser, els, options = {}) { tooltipTitle = s__('UsersSelect|Assignee'); } $value.html(assigneeTemplate(user)); - $collapsedSidebar.attr('title', tooltipTitle).tooltip('_fixTitle'); + $collapsedSidebar.attr('title', tooltipTitle); + fixTitle($collapsedSidebar); + return $collapsedSidebar.html(collapsedAssigneeTemplate(user)); }); }; @@ -423,7 +426,7 @@ function UsersSelect(currentUser, els, options = {}) { const { $el, e, isMarking } = options; const user = options.selectedObj; - $el.tooltip('dispose'); + dispose($el); if ($dropdown.hasClass('js-multiselect')) { const isActive = $el.hasClass('is-active'); diff --git a/app/models/diff_viewer/image.rb b/app/models/diff_viewer/image.rb index 62a3446a7b6..fca6c664196 100644 --- a/app/models/diff_viewer/image.rb +++ b/app/models/diff_viewer/image.rb @@ -10,5 +10,13 @@ module DiffViewer self.binary = true self.switcher_icon = 'doc-image' self.switcher_title = _('image diff') + + def self.can_render?(diff_file, verify_binary: true) + # When both blobs are missing, we often still have a textual diff that can + # be displayed + return false if diff_file.old_blob.nil? && diff_file.new_blob.nil? + + super + end end end diff --git a/app/views/devise/shared/_tabs_ldap.html.haml b/app/views/devise/shared/_tabs_ldap.html.haml index acd41fb011a..27057d023b1 100644 --- a/app/views/devise/shared/_tabs_ldap.html.haml +++ b/app/views/devise/shared/_tabs_ldap.html.haml @@ -4,17 +4,17 @@ %ul.nav-links.new-session-tabs.nav-tabs.nav{ class: ('custom-provider-tabs' if any_form_based_providers_enabled?) } - if crowd_enabled? %li.nav-item - = link_to "Crowd", "#crowd", class: "nav-link #{active_when(form_based_auth_provider_has_active_class?(:crowd))}", 'data-toggle' => 'tab' + = link_to "Crowd", "#crowd", class: "nav-link #{active_when(form_based_auth_provider_has_active_class?(:crowd))}", 'data-toggle' => 'tab', role: 'tab' = render_if_exists "devise/shared/kerberos_tab" - ldap_servers.each_with_index do |server, i| %li.nav-item - = link_to server['label'], "##{server['provider_name']}", class: "nav-link #{active_when(i == 0 && form_based_auth_provider_has_active_class?(:ldapmain))}", data: { toggle: 'tab', qa_selector: 'ldap_tab' } + = link_to server['label'], "##{server['provider_name']}", class: "nav-link #{active_when(i == 0 && form_based_auth_provider_has_active_class?(:ldapmain))}", data: { toggle: 'tab', qa_selector: 'ldap_tab' }, role: 'tab' = render_if_exists 'devise/shared/tab_smartcard' - if show_password_form %li.nav-item - = link_to _('Standard'), '#login-pane', class: 'nav-link', data: { toggle: 'tab', qa_selector: 'standard_tab' } + = link_to _('Standard'), '#login-pane', class: 'nav-link', data: { toggle: 'tab', qa_selector: 'standard_tab' }, role: 'tab' - if render_signup_link && allow_signup? %li.nav-item - = link_to 'Register', '#register-pane', class: 'nav-link', data: { toggle: 'tab', qa_selector: 'register_tab' } + = link_to 'Register', '#register-pane', class: 'nav-link', data: { toggle: 'tab', qa_selector: 'register_tab' }, role: 'tab' diff --git a/app/workers/concerns/limited_capacity/worker.rb b/app/workers/concerns/limited_capacity/worker.rb index c0d6bfff2f5..b5a97e49300 100644 --- a/app/workers/concerns/limited_capacity/worker.rb +++ b/app/workers/concerns/limited_capacity/worker.rb @@ -67,6 +67,7 @@ module LimitedCapacity return unless has_capacity? job_tracker.register(jid) + report_running_jobs_metrics perform_work(*args) rescue => exception raise @@ -108,11 +109,15 @@ module LimitedCapacity end def report_prometheus_metrics(*args) - running_jobs_gauge.set(prometheus_labels, running_jobs_count) + report_running_jobs_metrics remaining_work_gauge.set(prometheus_labels, remaining_work_count(*args)) max_running_jobs_gauge.set(prometheus_labels, max_running_jobs) end + def report_running_jobs_metrics + running_jobs_gauge.set(prometheus_labels, running_jobs_count) + end + def required_jobs_count(*args) [ remaining_work_count(*args), diff --git a/bin/feature-flag b/bin/feature-flag index b5e7889be1d..43e273be1fa 100755 --- a/bin/feature-flag +++ b/bin/feature-flag @@ -126,6 +126,8 @@ class FeatureFlagOptionParser $stdout.puts ">> Specify the feature flag type:" $stdout.puts TYPES.each do |type, data| + next if data[:deprecated] + $stdout.puts "#{type.to_s.rjust(15)}#{' '*6}#{data[:description]}" end @@ -133,7 +135,7 @@ class FeatureFlagOptionParser $stdout.print "?> " type = $stdin.gets.strip.to_sym - return type if TYPES[type] + return type if TYPES[type] && !TYPES[type][:deprecated] $stderr.puts "Invalid type specified '#{type}'" end diff --git a/changelogs/unreleased/244831-split-pipelines-for-merged-results-and-merge-train-check-boxes.yml b/changelogs/unreleased/244831-split-pipelines-for-merged-results-and-merge-train-check-boxes.yml new file mode 100644 index 00000000000..2aac8e603aa --- /dev/null +++ b/changelogs/unreleased/244831-split-pipelines-for-merged-results-and-merge-train-check-boxes.yml @@ -0,0 +1,5 @@ +--- +title: Add merge trains enabled setting to project ci cd settings +merge_request: 45834 +author: +type: other diff --git a/changelogs/unreleased/26952-fix-viewing-legacy-diff-notes-in-discussion.yml b/changelogs/unreleased/26952-fix-viewing-legacy-diff-notes-in-discussion.yml new file mode 100644 index 00000000000..f6ec87f1332 --- /dev/null +++ b/changelogs/unreleased/26952-fix-viewing-legacy-diff-notes-in-discussion.yml @@ -0,0 +1,5 @@ +--- +title: Fix viewing GitHub-imported diff notes in discussions +merge_request: 45920 +author: +type: fixed diff --git a/config/feature_flags/development/admin_approval_for_new_user_signups.yml b/config/feature_flags/development/admin_approval_for_new_user_signups.yml deleted file mode 100644 index 0cde210e6a0..00000000000 --- a/config/feature_flags/development/admin_approval_for_new_user_signups.yml +++ /dev/null @@ -1,7 +0,0 @@ ---- -name: admin_approval_for_new_user_signups -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/43827 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/258980 -type: development -group: group::access -default_enabled: true diff --git a/config/initializers/0_inject_feature_flags.rb b/config/initializers/0_inject_feature_flags.rb index 5b33b3bb4ea..1e0c3c01aba 100644 --- a/config/initializers/0_inject_feature_flags.rb +++ b/config/initializers/0_inject_feature_flags.rb @@ -4,3 +4,49 @@ Feature.register_feature_groups Feature.register_definitions Feature.register_hot_reloader unless Rails.configuration.cache_classes + +# This disallows usage of licensed feature names with the same name +# as feature flags. This naming collision creates confusion and it was +# decided to be removed in favor of explicit check. +# https://gitlab.com/gitlab-org/gitlab/-/issues/259611 +if Gitlab.ee? && Gitlab.dev_or_test_env? + # These are the names of feature flags that do violate the constraint of + # being unique to licensed names. These feature flags should be reworked to + # be "development" with explicit check + IGNORED_FEATURE_FLAGS = %i[ + resource_access_token + ci_secrets_management + feature_flags_related_issues + group_coverage_reports + group_wikis + incident_sla + swimlanes + minimal_access_role + ].to_set + + # First, we validate a list of overrides to ensure that these overrides + # are removed if feature flag is gone + missing_feature_flags = IGNORED_FEATURE_FLAGS.reject do |feature_flag| + Feature::Definition.definitions[feature_flag] + end + + if missing_feature_flags.any? + raise "The following feature flags were added as an override for discovering licensed features. " \ + "Since these feature flags seems to be gone, ensure to remove them from \`IGNORED_FEATURE_FLAGS\` " \ + "in \`#{__FILE__}'`: #{missing_feature_flags.join(", ")}" + end + + # Second, we validate that there's no feature flag under the name as licensed feature + # flag, to ensure that the name used, is unique + licensed_features = License::PLANS_BY_FEATURE.keys.select do |licensed_feature_name| + IGNORED_FEATURE_FLAGS.exclude?(licensed_feature_name) && + Feature::Definition.definitions[licensed_feature_name] + end + + if licensed_features.any? + raise "The following feature flags do use a licensed feature. " \ + "To avoid the confusion between their usage it is disallowed to use feature flag " \ + "with exact the same name as licensed feature name. Use a different name to create " \ + "a distinction: #{licensed_features.join(", ")}" + end +end diff --git a/danger/documentation/Dangerfile b/danger/documentation/Dangerfile index 37fad668521..240c374435c 100644 --- a/danger/documentation/Dangerfile +++ b/danger/documentation/Dangerfile @@ -5,7 +5,9 @@ def gitlab_danger end def feature_mr? - (gitlab.mr_labels & %w[feature::addition feature::enhancement]).any? + return false unless helper.gitlab_helper&.mr_labels + + (helper.gitlab_helper.mr_labels & %w[feature::addition feature::enhancement]).any? end DOCUMENTATION_UPDATE_MISSING = <<~MSG diff --git a/db/migrate/20201021220101_add_merge_trains_enabled.rb b/db/migrate/20201021220101_add_merge_trains_enabled.rb new file mode 100644 index 00000000000..88a71897435 --- /dev/null +++ b/db/migrate/20201021220101_add_merge_trains_enabled.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class AddMergeTrainsEnabled < ActiveRecord::Migration[6.0] + DOWNTIME = false + + def change + add_column :project_ci_cd_settings, :merge_trains_enabled, :boolean, default: false, allow_null: false + end +end diff --git a/db/migrate/20201026200736_seed_merge_trains_enabled.rb b/db/migrate/20201026200736_seed_merge_trains_enabled.rb new file mode 100644 index 00000000000..c22c2a408bc --- /dev/null +++ b/db/migrate/20201026200736_seed_merge_trains_enabled.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +class SeedMergeTrainsEnabled < ActiveRecord::Migration[6.0] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + update_column_in_batches(:project_ci_cd_settings, :merge_trains_enabled, true) do |table, query| + query.where(table[:merge_pipelines_enabled].eq(true)) + end + end + + def down + # no-op + end +end diff --git a/db/schema_migrations/20201021220101 b/db/schema_migrations/20201021220101 new file mode 100644 index 00000000000..cda2e4076a5 --- /dev/null +++ b/db/schema_migrations/20201021220101 @@ -0,0 +1 @@ +72580665fcb0fca332ede450690902c0a7afb6159feff41df1bc10a3cf6607f2 \ No newline at end of file diff --git a/db/schema_migrations/20201026200736 b/db/schema_migrations/20201026200736 new file mode 100644 index 00000000000..7ed57505c3e --- /dev/null +++ b/db/schema_migrations/20201026200736 @@ -0,0 +1 @@ +691fe3335de3e072bc5612705c4d16744ff17e334025ddd78eb37309f87441e3 \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index c08fc55fef0..c117864ff81 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -14822,7 +14822,8 @@ CREATE TABLE project_ci_cd_settings ( group_runners_enabled boolean DEFAULT true NOT NULL, merge_pipelines_enabled boolean, default_git_depth integer, - forward_deployment_enabled boolean + forward_deployment_enabled boolean, + merge_trains_enabled boolean DEFAULT false ); CREATE SEQUENCE project_ci_cd_settings_id_seq diff --git a/doc/development/fe_guide/graphql.md b/doc/development/fe_guide/graphql.md index ad3958d4496..a6e5a1b9b12 100644 --- a/doc/development/fe_guide/graphql.md +++ b/doc/development/fe_guide/graphql.md @@ -910,3 +910,99 @@ const defaultClient = createDefaultClient( }, ); ``` + +## Making initial queries early with GraphQL startup calls + +To improve performance, sometimes we want to make initial GraphQL queries early. In order to do this, we can add them to **startup calls** with the following steps: + +- Move all the queries you need initially in your application to `app/graphql/queries`; +- Add `__typename` property to every nested query level: + + ```javascript + query getPermissions($projectPath: ID!) { + project(fullPath: $projectPath) { + __typename + userPermissions { + __typename + pushCode + forkProject + createMergeRequestIn + } + } + } + ``` + +- If queries contain fragments, you need to move fragments to the query file directly instead of importing them: + + ```javascript + fragment PageInfo on PageInfo { + __typename + hasNextPage + hasPreviousPage + startCursor + endCursor + } + + query getFiles( + $projectPath: ID! + $path: String + $ref: String! + ) { + project(fullPath: $projectPath) { + __typename + repository { + __typename + tree(path: $path, ref: $ref) { + __typename + pageInfo { + ...PageInfo + } + } + } + } + } + } + ``` + +- If the fragment is used only once, we can also remove the fragment altogether: + + ```javascript + query getFiles( + $projectPath: ID! + $path: String + $ref: String! + ) { + project(fullPath: $projectPath) { + __typename + repository { + __typename + tree(path: $path, ref: $ref) { + __typename + pageInfo { + __typename + hasNextPage + hasPreviousPage + startCursor + endCursor + } + } + } + } + } + } + ``` + +- Add startup call(s) with correct variables to the HAML file that serves as a view +for your application. To add GraphQL startup calls, we use +`add_page_startup_graphql_call` helper where the first parameter is a path to the +query, the second one is an object containing query variables. Path to the query is +relative to `app/graphql/queries` folder: for example, if we need a +`app/graphql/queries/repository/files.query.graphql` query, the path will be +`repository/files`. + + ```yaml + - current_route_path = request.fullpath.match(/-\/tree\/[^\/]+\/(.+$)/).to_a[1] + - add_page_startup_graphql_call('repository/path_last_commit', { projectPath: @project.full_path, ref: current_ref, path: current_route_path || "" }) + - add_page_startup_graphql_call('repository/permissions', { projectPath: @project.full_path }) + - add_page_startup_graphql_call('repository/files', { nextPageCursor: "", pageSize: 100, projectPath: @project.full_path, ref: current_ref, path: current_route_path || "/"}) + ``` diff --git a/doc/development/feature_flags/development.md b/doc/development/feature_flags/development.md index e6668b0fd03..6c672663bae 100644 --- a/doc/development/feature_flags/development.md +++ b/doc/development/feature_flags/development.md @@ -61,30 +61,6 @@ Feature.disabled?(:my_ops_flag, project, type: :ops) push_frontend_feature_flag(:my_ops_flag, project, type: :ops) ``` -### `licensed` type - -`licensed` feature flags are used to temporarily disable licensed features. There -should be a one-to-one mapping of `licensed` feature flags to licensed features. - -`licensed` feature flags must be `default_enabled: true`, because that's the only -supported option in the current implementation. This is under development as per -the [related issue](https://gitlab.com/gitlab-org/gitlab/-/issues/218667). - -The `licensed` type has a dedicated set of functions to check if a licensed -feature is available for a project or namespace. This check validates -if the license is assigned to the namespace and feature flag itself. -The `licensed` feature flag has the same name as a licensed feature name: - -```ruby -# Good: checks if feature flag is enabled -project.feature_available?(:my_licensed_feature) -namespace.feature_available?(:my_licensed_feature) - -# Bad: licensed flag must be accessed via `feature_available?` -Feature.enabled?(:my_licensed_feature, type: :licensed) -push_frontend_feature_flag(:my_licensed_feature, type: :licensed) -``` - ## Feature flag definition and validation > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/229161) in GitLab 13.3. @@ -309,25 +285,16 @@ used as an actor for `Feature.enabled?`. ### Feature flags for licensed features -The [`Project#feature_available?`](https://gitlab.com/gitlab-org/gitlab/blob/4cc1c62918aa4c31750cb21dfb1a6c3492d71080/app/models/project_feature.rb#L63-68), -[`Namespace#feature_available?`](https://gitlab.com/gitlab-org/gitlab/blob/4cc1c62918aa4c31750cb21dfb1a6c3492d71080/ee/app/models/ee/namespace.rb#L71-85) (EE), and -[`License.feature_available?`](https://gitlab.com/gitlab-org/gitlab/blob/4cc1c62918aa4c31750cb21dfb1a6c3492d71080/ee/app/models/license.rb#L293-300) (EE) methods all implicitly check for -a by default enabled feature flag with the same name as the provided argument. +You can't use a feature flag with the same name as a licensed feature name, because +it would cause a naming collision. This was [widely discussed and removed](https://gitlab.com/gitlab-org/gitlab/-/issues/259611) +because it is confusing. -**An important side-effect of the implicit feature flags mentioned above is that -unless the feature is explicitly disabled or limited to a percentage of users, -the feature flag check defaults to `true`.** - -NOTE: **Note:** -Due to limitations with `feature_available?`, the YAML definition for `licensed` feature -flags accepts only `default_enabled: true`. This is under development as per the -[related issue](https://gitlab.com/gitlab-org/gitlab/-/issues/218667). - -If you want a licensed feature to be disabled by default or enabled only for a given gate, you can use a feature flag with a different name. The feature checks would then -look like: +To check for licensed features, add a dedicated feature flag under a different name +and check it explicitly, for example: ```ruby -Feature.enabled?(:licensed_feature_feature_flag, project) && project.feature_available?(:licensed_feature) +Feature.enabled?(:licensed_feature_feature_flag, project) && + project.feature_available?(:licensed_feature) ``` ### Feature groups diff --git a/doc/install/installation.md b/doc/install/installation.md index 597357f1349..4b93d02815f 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -181,9 +181,9 @@ sudo make install # Download and compile from source cd /tmp -curl --remote-name --location --progress https://www.kernel.org/pub/software/scm/git/git-2.28.0.tar.gz -echo 'f914c60a874d466c1e18467c864a910dd4ea22281ba6d4d58077cb0c3f115170 git-2.28.0.tar.gz' | shasum -a256 -c - && tar -xzf git-2.28.0.tar.gz -cd git-2.28.0/ +curl --remote-name --location --progress https://www.kernel.org/pub/software/scm/git/git-2.29.0.tar.gz +echo 'fa08dc8424ef80c0f9bf307877f9e2e49f1a6049e873530d6747c2be770742ff git-2.29.0.tar.gz' | shasum -a256 -c - && tar -xzf git-2.29.0.tar.gz +cd git-2.29.0/ ./configure --with-libpcre make prefix=/usr/local all diff --git a/doc/update/upgrading_from_source.md b/doc/update/upgrading_from_source.md index a0f042acab2..e570042e607 100644 --- a/doc/update/upgrading_from_source.md +++ b/doc/update/upgrading_from_source.md @@ -152,9 +152,9 @@ make install # Download and compile from source cd /tmp -curl --remote-name --location --progress https://www.kernel.org/pub/software/scm/git/git-2.28.0.tar.gz -echo 'f914c60a874d466c1e18467c864a910dd4ea22281ba6d4d58077cb0c3f115170 git-2.28.0.tar.gz' | shasum -a256 -c - && tar -xzf git-2.28.0.tar.gz -cd git-2.28.0/ +curl --remote-name --location --progress https://www.kernel.org/pub/software/scm/git/git-2.29.0.tar.gz +echo 'fa08dc8424ef80c0f9bf307877f9e2e49f1a6049e873530d6747c2be770742ff git-2.29.0.tar.gz' | shasum -a256 -c - && tar -xzf git-2.29.0.tar.gz +cd git-2.29.0/ ./configure --with-libpcre make prefix=/usr/local all diff --git a/lib/api/features.rb b/lib/api/features.rb index 5d2e545abd6..70feef12ceb 100644 --- a/lib/api/features.rb +++ b/lib/api/features.rb @@ -61,6 +61,8 @@ module API mutually_exclusive :key, :project end post ':name' do + validate_feature_flag_name!(params[:name]) + feature = Feature.get(params[:name]) # rubocop:disable Gitlab/AvoidFeatureGet targets = gate_targets(params) value = gate_value(params) @@ -97,5 +99,13 @@ module API no_content! end end + + helpers do + def validate_feature_flag_name!(name) + # no-op + end + end end end + +API::Features.prepend_if_ee('EE::API::Features') diff --git a/lib/feature/shared.rb b/lib/feature/shared.rb index 9ec56ee6b52..5716bc007a6 100644 --- a/lib/feature/shared.rb +++ b/lib/feature/shared.rb @@ -10,6 +10,8 @@ class Feature # rollout_issue: defines if `bin/feature-flag` asks for rollout issue # default_enabled: defines a default state of a feature flag when created by `bin/feature-flag` # ee_only: defines that a feature flag can only be created in a context of EE + # deprecated: defines if a feature flag type that is deprecated and to be removed, + # the deprecated types are hidden from all interfaces # example: usage being shown when exception is raised TYPES = { development: { @@ -37,6 +39,7 @@ class Feature }, licensed: { description: 'Permanent feature flags used to temporarily disable licensed features.', + deprecated: true, optional: true, rollout_issue: false, ee_only: true, diff --git a/spec/bin/feature_flag_spec.rb b/spec/bin/feature_flag_spec.rb index 185a03fc587..8e0bed5b769 100644 --- a/spec/bin/feature_flag_spec.rb +++ b/spec/bin/feature_flag_spec.rb @@ -123,6 +123,29 @@ RSpec.describe 'bin/feature-flag' do end end + context 'when there is deprecated feature flag type' do + before do + stub_const('FeatureFlagOptionParser::TYPES', + development: { description: 'short' }, + deprecated: { description: 'deprecated', deprecated: true } + ) + end + + context 'and deprecated type is given' do + let(:type) { 'deprecated' } + + it 'shows error message and retries' do + expect($stdin).to receive(:gets).and_return(type) + expect($stdin).to receive(:gets).and_raise('EOF') + + expect do + expect { described_class.read_type }.to raise_error(/EOF/) + end.to output(/Specify the feature flag type/).to_stdout + .and output(/Invalid type specified/).to_stderr + end + end + end + context 'when there are many types defined' do before do stub_const('FeatureFlagOptionParser::TYPES', diff --git a/spec/features/users/login_spec.rb b/spec/features/users/login_spec.rb index 853c381fe6b..f56c6f28f9d 100644 --- a/spec/features/users/login_spec.rb +++ b/spec/features/users/login_spec.rb @@ -594,41 +594,78 @@ RSpec.describe 'Login' do describe 'UI tabs and panes' do context 'when no defaults are changed' do it 'correctly renders tabs and panes' do - ensure_tab_pane_correctness + visit new_user_session_path + + ensure_tab_pane_correctness(['Sign in', 'Register']) end end context 'when signup is disabled' do before do stub_application_setting(signup_enabled: false) + + visit new_user_session_path end it 'correctly renders tabs and panes' do - ensure_tab_pane_correctness + ensure_tab_pane_correctness(['Sign in']) end end context 'when ldap is enabled' do + include LdapHelpers + + let(:provider) { 'ldapmain' } + let(:ldap_server_config) do + { + 'label' => 'Main LDAP', + 'provider_name' => provider, + 'attributes' => {}, + 'encryption' => 'plain', + 'uid' => 'uid', + 'base' => 'dc=example,dc=com' + } + end + before do + stub_ldap_setting(enabled: true) + allow(::Gitlab::Auth::Ldap::Config).to receive_messages(enabled: true, servers: [ldap_server_config]) + allow(Gitlab::Auth::OAuth::Provider).to receive_messages(providers: [provider.to_sym]) + + Ldap::OmniauthCallbacksController.define_providers! + Rails.application.reload_routes! + + allow_next_instance_of(ActionDispatch::Routing::RoutesProxy) do |instance| + allow(instance).to receive(:"user_#{provider}_omniauth_callback_path") + .and_return("/users/auth/#{provider}/callback") + end + visit new_user_session_path - allow(page).to receive(:form_based_providers).and_return([:ldapmain]) - allow(page).to receive(:ldap_enabled).and_return(true) end it 'correctly renders tabs and panes' do - ensure_tab_pane_correctness(false) + ensure_tab_pane_correctness(['Main LDAP', 'Standard', 'Register']) end end context 'when crowd is enabled' do before do + allow(Gitlab::Auth::OAuth::Provider).to receive_messages(providers: [:crowd]) + stub_application_setting(crowd_enabled: true) + + Ldap::OmniauthCallbacksController.define_providers! + Rails.application.reload_routes! + + allow_next_instance_of(ActionDispatch::Routing::RoutesProxy) do |instance| + allow(instance).to receive(:user_crowd_omniauth_authorize_path) + .and_return("/users/auth/crowd/callback") + end + visit new_user_session_path - allow(page).to receive(:form_based_providers).and_return([:crowd]) - allow(page).to receive(:crowd_enabled?).and_return(true) end it 'correctly renders tabs and panes' do - ensure_tab_pane_correctness(false) + ensure_tab_pane_correctness(%w(Crowd Standard Register)) end end end diff --git a/spec/frontend/editor/editor_lite_spec.js b/spec/frontend/editor/editor_lite_spec.js index bc17435c6d4..2968984df01 100644 --- a/spec/frontend/editor/editor_lite_spec.js +++ b/spec/frontend/editor/editor_lite_spec.js @@ -64,7 +64,7 @@ describe('Base editor', () => { }); it('creates model to be supplied to Monaco editor', () => { - editor.createInstance({ el: editorEl, blobPath, blobContent }); + editor.createInstance({ el: editorEl, blobPath, blobContent, blobGlobalId: '' }); expect(modelSpy).toHaveBeenCalledWith(blobContent, undefined, createUri(blobPath)); expect(setModel).toHaveBeenCalledWith(fakeModel); diff --git a/spec/migrations/seed_merge_trains_enabled_spec.rb b/spec/migrations/seed_merge_trains_enabled_spec.rb new file mode 100644 index 00000000000..2abb064a111 --- /dev/null +++ b/spec/migrations/seed_merge_trains_enabled_spec.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require 'spec_helper' +require Rails.root.join('db', 'migrate', '20201026200736_seed_merge_trains_enabled.rb') + +RSpec.describe SeedMergeTrainsEnabled do + describe 'migrate' do + let(:project_ci_cd_settings) { table(:project_ci_cd_settings) } + let(:projects) { table(:projects) } + let(:namespaces) { table(:namespaces) } + + context 'when on Gitlab.com' do + before do + namespace = namespaces.create!(name: 'hello', path: 'hello/') + project1 = projects.create!(namespace_id: namespace.id) + project2 = projects.create!(namespace_id: namespace.id) + project_ci_cd_settings.create!(project_id: project1.id, merge_pipelines_enabled: true) + project_ci_cd_settings.create!(project_id: project2.id, merge_pipelines_enabled: false) + end + + it 'updates merge_trains_enabled to true for where merge_pipelines_enabled is true' do + migrate! + + expect(project_ci_cd_settings.where(merge_trains_enabled: true).count).to be(1) + end + end + end +end diff --git a/spec/models/diff_viewer/image_spec.rb b/spec/models/diff_viewer/image_spec.rb new file mode 100644 index 00000000000..e959a7d5eb2 --- /dev/null +++ b/spec/models/diff_viewer/image_spec.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe DiffViewer::Image do + describe '.can_render?' do + let(:diff_file) { double(Gitlab::Diff::File) } + let(:blob) { double(Gitlab::Git::Blob, binary_in_repo?: true, extension: 'png') } + + subject { described_class.can_render?(diff_file, verify_binary: false) } + + it 'returns false if both old and new blob are absent' do + allow(diff_file).to receive(:old_blob) { nil } + allow(diff_file).to receive(:new_blob) { nil } + + is_expected.to be_falsy + end + + it 'returns true if the old blob is present' do + allow(diff_file).to receive(:old_blob) { blob } + allow(diff_file).to receive(:new_blob) { nil } + + is_expected.to be_truthy + end + + it 'returns true if the new blob is present' do + allow(diff_file).to receive(:old_blob) { nil } + allow(diff_file).to receive(:new_blob) { blob } + + is_expected.to be_truthy + end + + it 'returns true if both old and new blobs are present' do + allow(diff_file).to receive(:old_blob) { blob } + allow(diff_file).to receive(:new_blob) { blob } + + is_expected.to be_truthy + end + end +end diff --git a/spec/support/helpers/user_login_helper.rb b/spec/support/helpers/user_login_helper.rb index 66606832883..925576119bb 100644 --- a/spec/support/helpers/user_login_helper.rb +++ b/spec/support/helpers/user_login_helper.rb @@ -1,18 +1,21 @@ # frozen_string_literal: true module UserLoginHelper - def ensure_tab_pane_correctness(visit_path = true) - if visit_path - visit new_user_session_path - end - - ensure_tab_pane_counts + def ensure_tab_pane_correctness(tab_names) + ensure_tab_pane_counts(tab_names.size) + ensure_tab_labels(tab_names) ensure_one_active_tab ensure_one_active_pane end - def ensure_tab_pane_counts - tabs_count = page.all('[role="tab"]').size + def ensure_tab_labels(tab_names) + tab_labels = page.all('[role="tab"]').map(&:text) + + expect(tab_names).to match_array(tab_labels) + end + + def ensure_tab_pane_counts(tabs_count) + expect(page.all('[role="tab"]').size).to eq(tabs_count) expect(page).to have_selector('[role="tabpanel"]', visible: :all, count: tabs_count) end diff --git a/spec/workers/concerns/limited_capacity/worker_spec.rb b/spec/workers/concerns/limited_capacity/worker_spec.rb index 8a15675c04d..2c33c8666ec 100644 --- a/spec/workers/concerns/limited_capacity/worker_spec.rb +++ b/spec/workers/concerns/limited_capacity/worker_spec.rb @@ -121,7 +121,8 @@ RSpec.describe LimitedCapacity::Worker, :clean_gitlab_redis_queues, :aggregate_f it 'reports prometheus metrics' do allow(worker).to receive(:perform_work) - expect(worker).to receive(:report_prometheus_metrics) + expect(worker).to receive(:report_prometheus_metrics).once.and_call_original + expect(worker).to receive(:report_running_jobs_metrics).twice.and_call_original perform end