diff options
82 files changed, 613 insertions, 427 deletions
@@ -57,7 +57,7 @@ gem 'gssapi', group: :kerberos # Spam and anti-bot protection gem 'recaptcha', '~> 4.11', require: 'recaptcha/rails' gem 'akismet', '~> 3.0' -gem 'invisible_captcha', '~> 0.12.1' +gem 'invisible_captcha', '~> 1.1.0' # Two-factor authentication gem 'devise-two-factor', '~> 3.1.0' diff --git a/Gemfile.lock b/Gemfile.lock index 47538b82386..61583a27c72 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -598,8 +598,8 @@ GEM i18n_data (0.8.0) icalendar (2.4.1) ice_nine (0.11.2) - invisible_captcha (0.12.1) - rails (>= 3.2.0) + invisible_captcha (1.1.0) + rails (>= 4.2) ipaddress (0.8.3) jaeger-client (1.1.0) opentracing (~> 0.3) @@ -1395,7 +1395,7 @@ DEPENDENCIES html2text httparty (~> 0.16.4) icalendar - invisible_captcha (~> 0.12.1) + invisible_captcha (~> 1.1.0) ipaddress (~> 0.8.3) jira-ruby (~> 2.1.4) js_regex (~> 3.4) diff --git a/app/assets/javascripts/blob_edit/edit_blob.js b/app/assets/javascripts/blob_edit/edit_blob.js index e73e368fe01..c7f66a357f3 100644 --- a/app/assets/javascripts/blob_edit/edit_blob.js +++ b/app/assets/javascripts/blob_edit/edit_blob.js @@ -5,7 +5,7 @@ import { BLOB_EDITOR_ERROR, BLOB_PREVIEW_ERROR } from './constants'; import TemplateSelectorMediator from '../blob/file_template_mediator'; import { addEditorMarkdownListeners } from '~/lib/utils/text_markdown'; import EditorLite from '~/editor/editor_lite'; -import { FileTemplateExtension } from '~/editor/editor_file_template_ext'; +import { FileTemplateExtension } from '~/editor/extensions/editor_file_template_ext'; import { insertFinalNewline } from '~/lib/utils/text_utility'; export default class EditBlob { @@ -16,7 +16,7 @@ export default class EditBlob { this.configureMonacoEditor(); if (this.options.isMarkdown) { - import('~/editor/editor_markdown_ext') + import('~/editor/extensions/editor_markdown_ext') .then(({ EditorMarkdownExtension: MarkdownExtension } = {}) => { this.editor.use(new MarkdownExtension()); addEditorMarkdownListeners(this.editor); diff --git a/app/assets/javascripts/editor/editor_ci_schema_ext.js b/app/assets/javascripts/editor/extensions/editor_ci_schema_ext.js index 36e2fe22718..05d938c57ce 100644 --- a/app/assets/javascripts/editor/editor_ci_schema_ext.js +++ b/app/assets/javascripts/editor/extensions/editor_ci_schema_ext.js @@ -1,7 +1,7 @@ import Api from '~/api'; import { registerSchema } from '~/ide/utils'; import { EditorLiteExtension } from './editor_lite_extension_base'; -import { EXTENSION_CI_SCHEMA_FILE_NAME_MATCH } from './constants'; +import { EXTENSION_CI_SCHEMA_FILE_NAME_MATCH } from '../constants'; export class CiSchemaExtension extends EditorLiteExtension { /** diff --git a/app/assets/javascripts/editor/editor_file_template_ext.js b/app/assets/javascripts/editor/extensions/editor_file_template_ext.js index f5474318447..f5474318447 100644 --- a/app/assets/javascripts/editor/editor_file_template_ext.js +++ b/app/assets/javascripts/editor/extensions/editor_file_template_ext.js diff --git a/app/assets/javascripts/editor/editor_lite_extension_base.js b/app/assets/javascripts/editor/extensions/editor_lite_extension_base.js index b8d87fa4969..8d350068973 100644 --- a/app/assets/javascripts/editor/editor_lite_extension_base.js +++ b/app/assets/javascripts/editor/extensions/editor_lite_extension_base.js @@ -1,4 +1,4 @@ -import { ERROR_INSTANCE_REQUIRED_FOR_EXTENSION } from './constants'; +import { ERROR_INSTANCE_REQUIRED_FOR_EXTENSION } from '../constants'; export class EditorLiteExtension { constructor({ instance, ...options } = {}) { diff --git a/app/assets/javascripts/editor/editor_markdown_ext.js b/app/assets/javascripts/editor/extensions/editor_markdown_ext.js index 2ce003753f7..2ce003753f7 100644 --- a/app/assets/javascripts/editor/editor_markdown_ext.js +++ b/app/assets/javascripts/editor/extensions/editor_markdown_ext.js diff --git a/app/assets/javascripts/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js index 8a2b35752ae..cf9ff87f25e 100644 --- a/app/assets/javascripts/gfm_auto_complete.js +++ b/app/assets/javascripts/gfm_auto_complete.js @@ -103,6 +103,7 @@ class GfmAutoComplete { at: '/', alias: 'commands', searchKey: 'search', + limit: 100, skipSpecialCharacterTest: true, skipMarkdownCharacterTest: true, data: GfmAutoComplete.defaultLoadingData, diff --git a/app/assets/javascripts/pages/dashboard/todos/index/todos.js b/app/assets/javascripts/pages/dashboard/todos/index/todos.js index 6f8d954d798..bd283201eff 100644 --- a/app/assets/javascripts/pages/dashboard/todos/index/todos.js +++ b/app/assets/javascripts/pages/dashboard/todos/index/todos.js @@ -154,7 +154,7 @@ export default class Todos { goToTodoUrl(e) { const todoLink = this.dataset.url; - if (!todoLink || e.target.tagName === 'A' || e.target.tagName === 'IMG') { + if (!todoLink || e.target.closest('a')) { return; } diff --git a/app/assets/javascripts/pages/projects/pipeline_schedules/index/index.js b/app/assets/javascripts/pages/projects/pipeline_schedules/index/index.js index 90d2df50d5a..40730ec7e60 100644 --- a/app/assets/javascripts/pages/projects/pipeline_schedules/index/index.js +++ b/app/assets/javascripts/pages/projects/pipeline_schedules/index/index.js @@ -13,12 +13,12 @@ document.addEventListener('DOMContentLoaded', () => { // eslint-disable-next-line no-new new Vue({ el, - render(createElement) { - return createElement(PipelineSchedulesCallout); - }, provide: { docsUrl, illustrationUrl, }, + render(createElement) { + return createElement(PipelineSchedulesCallout); + }, }); }); diff --git a/app/assets/javascripts/pipeline_editor/components/text_editor.vue b/app/assets/javascripts/pipeline_editor/components/text_editor.vue index fae08ca89f4..b8d49d77ea9 100644 --- a/app/assets/javascripts/pipeline_editor/components/text_editor.vue +++ b/app/assets/javascripts/pipeline_editor/components/text_editor.vue @@ -1,6 +1,6 @@ <script> import EditorLite from '~/vue_shared/components/editor_lite.vue'; -import { CiSchemaExtension } from '~/editor/editor_ci_schema_ext'; +import { CiSchemaExtension } from '~/editor/extensions/editor_ci_schema_ext'; export default { components: { diff --git a/app/assets/javascripts/projects/pipelines/charts/components/app.vue b/app/assets/javascripts/projects/pipelines/charts/components/app.vue index 4d48468ce88..75f5edbfd2b 100644 --- a/app/assets/javascripts/projects/pipelines/charts/components/app.vue +++ b/app/assets/javascripts/projects/pipelines/charts/components/app.vue @@ -7,7 +7,8 @@ import { getDateInPast } from '~/lib/utils/datetime_utility'; import getPipelineCountByStatus from '../graphql/queries/get_pipeline_count_by_status.query.graphql'; import getProjectPipelineStatistics from '../graphql/queries/get_project_pipeline_statistics.query.graphql'; import StatisticsList from './statistics_list.vue'; -import PipelinesAreaChart from './pipelines_area_chart.vue'; +import CiCdAnalyticsAreaChart from './ci_cd_analytics_area_chart.vue'; + import { CHART_CONTAINER_HEIGHT, CHART_DATE_FORMAT, @@ -52,13 +53,19 @@ export default { GlColumnChart, GlSkeletonLoader, StatisticsList, - PipelinesAreaChart, + CiCdAnalyticsAreaChart, + DeploymentFrequencyCharts: () => + import('ee_component/projects/pipelines/charts/components/deployment_frequency_charts.vue'), }, inject: { projectPath: { type: String, default: '', }, + shouldRenderDeploymentFrequencyCharts: { + type: Boolean, + default: false, + }, }, data() { return { @@ -260,6 +267,15 @@ export default { lastYear: __('Pipelines for last year'), }; }, + areaChartOptions: { + xAxis: { + name: s__('Pipeline|Date'), + type: 'category', + }, + yAxis: { + name: s__('Pipeline|Pipelines'), + }, + }, }; </script> <template> @@ -292,12 +308,17 @@ export default { </div> <hr /> <h4 class="gl-my-4">{{ __('Pipelines charts') }}</h4> - <pipelines-area-chart + <ci-cd-analytics-area-chart v-for="(chart, index) in areaCharts" :key="index" :chart-data="chart.data" + :area-chart-options="$options.areaChartOptions" > {{ chart.title }} - </pipelines-area-chart> + </ci-cd-analytics-area-chart> + <template v-if="shouldRenderDeploymentFrequencyCharts"> + <hr /> + <deployment-frequency-charts /> + </template> </div> </template> diff --git a/app/assets/javascripts/projects/pipelines/charts/components/app_legacy.vue b/app/assets/javascripts/projects/pipelines/charts/components/app_legacy.vue index 1a102815e97..915d334f949 100644 --- a/app/assets/javascripts/projects/pipelines/charts/components/app_legacy.vue +++ b/app/assets/javascripts/projects/pipelines/charts/components/app_legacy.vue @@ -1,10 +1,11 @@ <script> import dateFormat from 'dateformat'; import { GlColumnChart } from '@gitlab/ui/dist/charts'; -import { __, sprintf } from '~/locale'; +import { __, s__, sprintf } from '~/locale'; import { getDateInPast } from '~/lib/utils/datetime_utility'; import StatisticsList from './statistics_list.vue'; -import PipelinesAreaChart from './pipelines_area_chart.vue'; +import CiCdAnalyticsAreaChart from './ci_cd_analytics_area_chart.vue'; + import { CHART_CONTAINER_HEIGHT, INNER_CHART_HEIGHT, @@ -19,7 +20,15 @@ export default { components: { StatisticsList, GlColumnChart, - PipelinesAreaChart, + CiCdAnalyticsAreaChart, + DeploymentFrequencyCharts: () => + import('ee_component/projects/pipelines/charts/components/deployment_frequency_charts.vue'), + }, + inject: { + shouldRenderDeploymentFrequencyCharts: { + type: Boolean, + default: false, + }, }, props: { counts: { @@ -112,6 +121,15 @@ export default { lastYear: __('Pipelines for last year'), }; }, + areaChartOptions: { + xAxis: { + name: s__('Pipeline|Date'), + type: 'category', + }, + yAxis: { + name: s__('Pipeline|Pipelines'), + }, + }, }; </script> <template> @@ -140,12 +158,17 @@ export default { </div> <hr /> <h4 class="my-4">{{ __('Pipelines charts') }}</h4> - <pipelines-area-chart + <ci-cd-analytics-area-chart v-for="(chart, index) in areaCharts" :key="index" :chart-data="chart.data" + :area-chart-options="$options.areaChartOptions" > {{ chart.title }} - </pipelines-area-chart> + </ci-cd-analytics-area-chart> + <template v-if="shouldRenderDeploymentFrequencyCharts"> + <hr /> + <deployment-frequency-charts /> + </template> </div> </template> diff --git a/app/assets/javascripts/projects/pipelines/charts/components/pipelines_area_chart.vue b/app/assets/javascripts/projects/pipelines/charts/components/ci_cd_analytics_area_chart.vue index d726196aadf..3590e2c4632 100644 --- a/app/assets/javascripts/projects/pipelines/charts/components/pipelines_area_chart.vue +++ b/app/assets/javascripts/projects/pipelines/charts/components/ci_cd_analytics_area_chart.vue @@ -1,10 +1,10 @@ <script> import { GlAreaChart } from '@gitlab/ui/dist/charts'; -import { s__ } from '~/locale'; import ResizableChartContainer from '~/vue_shared/components/resizable_chart/resizable_chart_container.vue'; import { CHART_CONTAINER_HEIGHT } from '../constants'; export default { + name: 'CiCdAnalyticsAreaChart', components: { GlAreaChart, ResizableChartContainer, @@ -14,14 +14,9 @@ export default { type: Array, required: true, }, - }, - areaChartOptions: { - xAxis: { - name: s__('Pipeline|Date'), - type: 'category', - }, - yAxis: { - name: s__('Pipeline|Pipelines'), + areaChartOptions: { + type: Object, + required: true, }, }, chartContainerHeight: CHART_CONTAINER_HEIGHT, @@ -39,7 +34,7 @@ export default { :height="$options.chartContainerHeight" :data="chartData" :include-legend-avg-max="false" - :option="$options.areaChartOptions" + :option="areaChartOptions" /> </resizable-chart-container> </div> diff --git a/app/assets/javascripts/projects/pipelines/charts/index.js b/app/assets/javascripts/projects/pipelines/charts/index.js index c94a4e6af99..12943e1c3c7 100644 --- a/app/assets/javascripts/projects/pipelines/charts/index.js +++ b/app/assets/javascripts/projects/pipelines/charts/index.js @@ -1,6 +1,7 @@ import Vue from 'vue'; import VueApollo from 'vue-apollo'; import createDefaultClient from '~/lib/graphql'; +import { parseBoolean } from '~/lib/utils/common_utils'; import ProjectPipelinesChartsLegacy from './components/app_legacy.vue'; import ProjectPipelinesCharts from './components/app.vue'; @@ -35,6 +36,10 @@ const mountPipelineChartsApp = (el) => { projectPath, } = el.dataset; + const shouldRenderDeploymentFrequencyCharts = parseBoolean( + el.dataset.shouldRenderDeploymentFrequencyCharts, + ); + const parseAreaChartData = (labels, totals, success) => { let parsedData = {}; @@ -61,6 +66,7 @@ const mountPipelineChartsApp = (el) => { apolloProvider, provide: { projectPath, + shouldRenderDeploymentFrequencyCharts, }, render: (createElement) => createElement(ProjectPipelinesCharts, {}), }); @@ -72,6 +78,10 @@ const mountPipelineChartsApp = (el) => { components: { ProjectPipelinesChartsLegacy, }, + provide: { + projectPath, + shouldRenderDeploymentFrequencyCharts, + }, render: (createElement) => createElement(ProjectPipelinesChartsLegacy, { props: { diff --git a/app/assets/javascripts/user_lists/components/user_list_form.vue b/app/assets/javascripts/user_lists/components/user_list_form.vue index 657acb51fee..a0364089d68 100644 --- a/app/assets/javascripts/user_lists/components/user_list_form.vue +++ b/app/assets/javascripts/user_lists/components/user_list_form.vue @@ -41,7 +41,7 @@ export default { ], }, translations: { - formLabel: s__('UserLists|Feature flag list'), + formLabel: s__('UserLists|Feature flag user list'), formSubtitle: s__( 'UserLists|Lists allow you to define a set of users to be used with feature flags. %{linkStart}Read more about feature flag lists.%{linkEnd}', ), diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue index cebbc7f2466..4beaa2509d9 100644 --- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue +++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue @@ -307,8 +307,7 @@ export default { callback: this.checkStatus, startingInterval: this.startingPollInterval, maxInterval: this.startingPollInterval + secondsToMilliseconds(4 * 60), - hiddenInterval: - window.gon?.features?.widgetVisibilityPolling && secondsToMilliseconds(6 * 60), + hiddenInterval: secondsToMilliseconds(6 * 60), incrementByFactorOf: 2, }); }, diff --git a/app/controllers/concerns/invisible_captcha_on_signup.rb b/app/controllers/concerns/invisible_captcha_on_signup.rb index 9bea6145ff3..c7fd6d08744 100644 --- a/app/controllers/concerns/invisible_captcha_on_signup.rb +++ b/app/controllers/concerns/invisible_captcha_on_signup.rb @@ -8,7 +8,7 @@ module InvisibleCaptchaOnSignup end def on_honeypot_spam_callback - return unless Feature.enabled?(:invisible_captcha) + return unless Gitlab::CurrentSettings.invisible_captcha_enabled invisible_captcha_honeypot_counter.increment log_request('Invisible_Captcha_Honeypot_Request') @@ -17,7 +17,7 @@ module InvisibleCaptchaOnSignup end def on_timestamp_spam_callback - return unless Feature.enabled?(:invisible_captcha) + return unless Gitlab::CurrentSettings.invisible_captcha_enabled invisible_captcha_timestamp_counter.increment log_request('Invisible_Captcha_Timestamp_Request') diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 1318c61509d..2a4e382e27a 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -28,7 +28,6 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo before_action :authenticate_user!, only: [:assign_related_issues] before_action :check_user_can_push_to_source_branch!, only: [:rebase] before_action only: [:show] do - push_frontend_feature_flag(:widget_visibility_polling, @project, default_enabled: true) push_frontend_feature_flag(:mr_commit_neighbor_nav, @project, default_enabled: true) push_frontend_feature_flag(:multiline_comments, @project, default_enabled: true) push_frontend_feature_flag(:file_identifier_hash) diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index cc91e2cf809..ed30adfabf0 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -242,6 +242,7 @@ module ApplicationSettingsHelper :housekeeping_incremental_repack_period, :html_emails_enabled, :import_sources, + :invisible_captcha_enabled, :max_artifacts_size, :max_attachment_size, :max_import_size, diff --git a/app/helpers/graph_helper.rb b/app/helpers/graph_helper.rb index 24072d1ab46..abb3c5a7af8 100644 --- a/app/helpers/graph_helper.rb +++ b/app/helpers/graph_helper.rb @@ -22,4 +22,10 @@ module GraphHelper ratio = (counts[:success].to_f / (counts[:success] + counts[:failed])) * 100 ratio.to_i end + + def should_render_deployment_frequency_charts + false + end end + +GraphHelper.prepend_if_ee('EE::GraphHelper') diff --git a/app/helpers/preferences_helper.rb b/app/helpers/preferences_helper.rb index 5310aef5bad..c2a77abb9c9 100644 --- a/app/helpers/preferences_helper.rb +++ b/app/helpers/preferences_helper.rb @@ -82,7 +82,7 @@ module PreferencesHelper def integration_views [].tap do |views| - views << { name: 'gitpod', message: gitpod_enable_description, message_url: 'https://gitpod.io/', help_link: help_page_path('integration/gitpod.md') } if Gitlab::Gitpod.feature_and_settings_enabled? + views << { name: 'gitpod', message: gitpod_enable_description, message_url: 'https://gitpod.io/', help_link: help_page_path('integration/gitpod.md') } if Gitlab::CurrentSettings.gitpod_enabled views << { name: 'sourcegraph', message: sourcegraph_url_message, message_url: Gitlab::CurrentSettings.sourcegraph_url, help_link: help_page_path('user/profile/preferences.md', anchor: 'sourcegraph') } if Gitlab::Sourcegraph.feature_available? && Gitlab::CurrentSettings.sourcegraph_enabled end end diff --git a/app/helpers/web_ide_button_helper.rb b/app/helpers/web_ide_button_helper.rb index 7aa0adc31bd..6c73d365e8e 100644 --- a/app/helpers/web_ide_button_helper.rb +++ b/app/helpers/web_ide_button_helper.rb @@ -26,7 +26,7 @@ module WebIdeButtonHelper end def show_gitpod_button? - show_web_ide_button? && Gitlab::Gitpod.feature_and_settings_enabled?(@project) + show_web_ide_button? && Gitlab::CurrentSettings.gitpod_enabled end def can_push_code? @@ -54,7 +54,7 @@ module WebIdeButtonHelper end def gitpod_url - return "" unless Gitlab::Gitpod.feature_and_settings_enabled?(@project) + return "" unless Gitlab::CurrentSettings.gitpod_enabled "#{Gitlab::CurrentSettings.gitpod_url}##{project_tree_url(@project, tree_join(@ref, @path || ''))}" end diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index fe4d9776ccb..5655ea4d4bf 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -171,7 +171,7 @@ class ApplicationSetting < ApplicationRecord validates :default_artifacts_expire_in, presence: true, duration: true validates :container_expiration_policies_enable_historic_entries, - inclusion: { in: [true, false], message: 'must be a boolean value' } + inclusion: { in: [true, false], message: _('must be a boolean value') } validates :container_registry_token_expire_delay, presence: true, @@ -309,6 +309,9 @@ class ApplicationSetting < ApplicationRecord validates :container_registry_expiration_policies_worker_capacity, numericality: { only_integer: true, greater_than_or_equal_to: 0 } + validates :invisible_captcha_enabled, + inclusion: { in: [true, false], message: _('must be a boolean value') } + SUPPORTED_KEY_TYPES.each do |type| validates :"#{type}_key_restriction", presence: true, key_restriction: { type: type } end @@ -469,7 +472,7 @@ class ApplicationSetting < ApplicationRecord attr_encrypted :cloud_license_auth_token, encryption_options_base_truncated_aes_256_gcm validates :disable_feed_token, - inclusion: { in: [true, false], message: 'must be a boolean value' } + inclusion: { in: [true, false], message: _('must be a boolean value') } before_validation :ensure_uuid! diff --git a/app/models/application_setting_implementation.rb b/app/models/application_setting_implementation.rb index b68125d529a..191f3e3ffa6 100644 --- a/app/models/application_setting_implementation.rb +++ b/app/models/application_setting_implementation.rb @@ -91,6 +91,7 @@ module ApplicationSettingImplementation housekeeping_gc_period: 200, housekeeping_incremental_repack_period: 10, import_sources: Settings.gitlab['import_sources'], + invisible_captcha_enabled: false, issues_create_limit: 300, local_markdown_version: 0, login_recaptcha_protection_enabled: false, diff --git a/app/models/concerns/boards/listable.rb b/app/models/concerns/boards/listable.rb new file mode 100644 index 00000000000..013669a1444 --- /dev/null +++ b/app/models/concerns/boards/listable.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +module Boards + module Listable + extend ActiveSupport::Concern + + included do + validates :label, :position, presence: true, if: :label? + validates :position, numericality: { only_integer: true, greater_than_or_equal_to: 0 }, if: :movable? + + before_destroy :can_be_destroyed + + scope :ordered, -> { order(:list_type, :position) } + scope :destroyable, -> { where(list_type: list_types.slice(*destroyable_types).values) } + scope :movable, -> { where(list_type: list_types.slice(*movable_types).values) } + end + + class_methods do + def destroyable_types + [:label] + end + + def movable_types + [:label] + end + end + + def destroyable? + self.class.destroyable_types.include?(list_type&.to_sym) + end + + def movable? + self.class.movable_types.include?(list_type&.to_sym) + end + + def title + label? ? label.name : list_type.humanize + end + + private + + def can_be_destroyed + throw(:abort) unless destroyable? # rubocop:disable Cop/BanCatchThrow + end + end +end diff --git a/app/models/list.rb b/app/models/list.rb index 1df565c83e6..49834af3dfb 100644 --- a/app/models/list.rb +++ b/app/models/list.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true class List < ApplicationRecord + include Boards::Listable include Importable belongs_to :board @@ -10,30 +11,13 @@ class List < ApplicationRecord enum list_type: { backlog: 0, label: 1, closed: 2, assignee: 3, milestone: 4, iteration: 5 } validates :board, :list_type, presence: true, unless: :importing? - validates :label, :position, presence: true, if: :label? validates :label_id, uniqueness: { scope: :board_id }, if: :label? - validates :position, numericality: { only_integer: true, greater_than_or_equal_to: 0 }, if: :movable? - - before_destroy :can_be_destroyed - - scope :destroyable, -> { where(list_type: list_types.slice(*destroyable_types).values) } - scope :movable, -> { where(list_type: list_types.slice(*movable_types).values) } scope :preload_associated_models, -> { preload(:board, label: :priorities) } - scope :ordered, -> { order(:list_type, :position) } - alias_method :preferences, :list_user_preferences class << self - def destroyable_types - [:label] - end - - def movable_types - [:label] - end - def preload_preferences_for_user(lists, user) return unless user @@ -60,18 +44,6 @@ class List < ApplicationRecord preferences_for(user).update(preferences) end - def destroyable? - self.class.destroyable_types.include?(list_type&.to_sym) - end - - def movable? - self.class.movable_types.include?(list_type&.to_sym) - end - - def title - label? ? label.name : list_type.humanize - end - def collapsed?(user) preferences = preferences_for(user) @@ -95,12 +67,6 @@ class List < ApplicationRecord end end end - - private - - def can_be_destroyed - throw(:abort) unless destroyable? # rubocop:disable Cop/BanCatchThrow - end end List.prepend_if_ee('::EE::List') diff --git a/app/presenters/packages/composer/packages_presenter.rb b/app/presenters/packages/composer/packages_presenter.rb index cce006cbb1a..ed0e9d3b731 100644 --- a/app/presenters/packages/composer/packages_presenter.rb +++ b/app/presenters/packages/composer/packages_presenter.rb @@ -20,33 +20,17 @@ module Packages end def package_versions(packages = @packages) - { 'packages' => { packages.first.name => package_versions_map(packages) } } + package_versions_index(packages).as_json end private - def package_versions_map(packages) - packages.each_with_object({}) do |package, map| - map[package.version] = package_metadata(package) - end + def package_versions_sha(packages = @packages) + package_versions_index(packages).sha end - def package_metadata(package) - json = package.composer_metadatum.composer_json - - json.merge('dist' => package_dist(package), 'uid' => package.id, 'version' => package.version) - end - - def package_dist(package) - sha = package.composer_metadatum.target_sha - archive_api_path = api_v4_projects_packages_composer_archives_package_name_path({ id: package.project_id, package_name: package.name, format: '.zip' }, true) - - { - 'type' => 'zip', - 'url' => expose_url(archive_api_path) + "?sha=#{sha}", - 'reference' => sha, - 'shasum' => '' - } + def package_versions_index(packages) + ::Gitlab::Composer::VersionIndex.new(packages) end def providers_map @@ -59,10 +43,6 @@ module Packages map end - def package_versions_sha(packages) - Digest::SHA256.hexdigest(package_versions(packages).to_json) - end - def provider_sha Digest::SHA256.hexdigest(provider.to_json) end diff --git a/app/views/admin/application_settings/_gitpod.html.haml b/app/views/admin/application_settings/_gitpod.html.haml index 1baec07fa25..7f78cce4575 100644 --- a/app/views/admin/application_settings/_gitpod.html.haml +++ b/app/views/admin/application_settings/_gitpod.html.haml @@ -1,4 +1,3 @@ -- return unless Gitlab::Gitpod.feature_available? - expanded = integration_expanded?('gitpod_') %section.settings.no-animate#js-gitpod-settings{ class: ('expanded' if expanded) } diff --git a/app/views/admin/application_settings/_spam.html.haml b/app/views/admin/application_settings/_spam.html.haml index b54f1d7c829..2b871d3693c 100644 --- a/app/views/admin/application_settings/_spam.html.haml +++ b/app/views/admin/application_settings/_spam.html.haml @@ -30,6 +30,14 @@ .form-group .form-check + = f.check_box :invisible_captcha_enabled, class: 'form-check-input' + = f.label :invisible_captcha_enabled, class: 'form-check-label' do + = _('Enable Invisible Captcha during sign up') + %span.form-text.text-muted + = _('Helps prevent bots from creating accounts.') + + .form-group + .form-check = f.check_box :akismet_enabled, class: 'form-check-input' = f.label :akismet_enabled, class: 'form-check-label' do Enable Akismet diff --git a/app/views/admin/application_settings/reporting.html.haml b/app/views/admin/application_settings/reporting.html.haml index c60e44b3864..6ea139844d4 100644 --- a/app/views/admin/application_settings/reporting.html.haml +++ b/app/views/admin/application_settings/reporting.html.haml @@ -11,7 +11,7 @@ %p - recaptcha_v2_link_url = 'https://developers.google.com/recaptcha/docs/versions' - recaptcha_v2_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: recaptcha_v2_link_url } - = _('Enable reCAPTCHA or Akismet and set IP limits. For reCAPTCHA, we currently only support %{recaptcha_v2_link_start}v2%{recaptcha_v2_link_end}').html_safe % { recaptcha_v2_link_start: recaptcha_v2_link_start, recaptcha_v2_link_end: '</a>'.html_safe } + = _('Enable reCAPTCHA, Invisible Captcha, Akismet and set IP limits. For reCAPTCHA, we currently only support %{recaptcha_v2_link_start}v2%{recaptcha_v2_link_end}').html_safe % { recaptcha_v2_link_start: recaptcha_v2_link_start, recaptcha_v2_link_end: '</a>'.html_safe } .settings-content = render 'spam' diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml index 3c4cbbbc3bd..acbf3b398b0 100644 --- a/app/views/devise/shared/_signup_box.html.haml +++ b/app/views/devise/shared/_signup_box.html.haml @@ -10,7 +10,7 @@ = form_for(resource, as: "new_#{resource_name}", url: url, html: { class: 'new_user gl-show-field-errors', 'aria-live' => 'assertive' }) do |f| .devise-errors = render 'devise/shared/error_messages', resource: resource - - if Feature.enabled?(:invisible_captcha) + - if Gitlab::CurrentSettings.invisible_captcha_enabled = invisible_captcha .name.form-row .col.form-group diff --git a/app/views/projects/pipelines/charts.html.haml b/app/views/projects/pipelines/charts.html.haml index f3360e150ad..711928c963b 100644 --- a/app/views/projects/pipelines/charts.html.haml +++ b/app/views/projects/pipelines/charts.html.haml @@ -1,10 +1,13 @@ - page_title _('CI / CD Analytics') - if Feature.enabled?(:graphql_pipeline_analytics) - #js-project-pipelines-charts-app{ data: { project_path: @project.full_path } } + #js-project-pipelines-charts-app{ data: { project_path: @project.full_path, + should_render_deployment_frequency_charts: should_render_deployment_frequency_charts.to_s } } - else #js-project-pipelines-charts-app{ data: { counts: @counts, success_ratio: success_ratio(@counts), times_chart: { labels: @charts[:pipeline_times].labels, values: @charts[:pipeline_times].pipeline_times }, last_week_chart: { labels: @charts[:week].labels, totals: @charts[:week].total, success: @charts[:week].success }, last_month_chart: { labels: @charts[:month].labels, totals: @charts[:month].total, success: @charts[:month].success }, - last_year_chart: { labels: @charts[:year].labels, totals: @charts[:year].total, success: @charts[:year].success } } } + last_year_chart: { labels: @charts[:year].labels, totals: @charts[:year].total, success: @charts[:year].success }, + project_path: @project.full_path, + should_render_deployment_frequency_charts: should_render_deployment_frequency_charts.to_s } } diff --git a/changelogs/unreleased/258206-remove-gitpod-feature-flag.yml b/changelogs/unreleased/258206-remove-gitpod-feature-flag.yml new file mode 100644 index 00000000000..1ca19a5431f --- /dev/null +++ b/changelogs/unreleased/258206-remove-gitpod-feature-flag.yml @@ -0,0 +1,5 @@ +--- +title: Add configurable Gitpod button within projcet repository page +merge_request: 51197 +author: +type: added diff --git a/changelogs/unreleased/290288-composer-cache-prep.yml b/changelogs/unreleased/290288-composer-cache-prep.yml new file mode 100644 index 00000000000..c6925a60910 --- /dev/null +++ b/changelogs/unreleased/290288-composer-cache-prep.yml @@ -0,0 +1,5 @@ +--- +title: Add version cache field to composer metadata +merge_request: 50906 +author: +type: other diff --git a/changelogs/unreleased/292393-include-all-available-quick-actions-in-autocomplete.yml b/changelogs/unreleased/292393-include-all-available-quick-actions-in-autocomplete.yml new file mode 100644 index 00000000000..a7e1f625817 --- /dev/null +++ b/changelogs/unreleased/292393-include-all-available-quick-actions-in-autocomplete.yml @@ -0,0 +1,5 @@ +--- +title: Show all quick actions in `/` autocomplete +merge_request: 51239 +author: +type: added diff --git a/changelogs/unreleased/ci-enable-allow-failure-exit-codes.yml b/changelogs/unreleased/ci-enable-allow-failure-exit-codes.yml new file mode 100644 index 00000000000..44e439b80d7 --- /dev/null +++ b/changelogs/unreleased/ci-enable-allow-failure-exit-codes.yml @@ -0,0 +1,5 @@ +--- +title: Control job status using exit codes +merge_request: 51439 +author: +type: added diff --git a/changelogs/unreleased/feat-flag-copy-change.yml b/changelogs/unreleased/feat-flag-copy-change.yml new file mode 100644 index 00000000000..48994845ee1 --- /dev/null +++ b/changelogs/unreleased/feat-flag-copy-change.yml @@ -0,0 +1,5 @@ +--- +title: Update copy on Feature Flags List view to be more descriptive for users +merge_request: 50813 +author: Sarah Rosenshine +type: other diff --git a/changelogs/unreleased/invisible-captcha-as-application-setting.yml b/changelogs/unreleased/invisible-captcha-as-application-setting.yml new file mode 100644 index 00000000000..4d9153cfb8a --- /dev/null +++ b/changelogs/unreleased/invisible-captcha-as-application-setting.yml @@ -0,0 +1,5 @@ +--- +title: Add setting to enable Invisible Captcha +merge_request: 50650 +author: +type: added diff --git a/changelogs/unreleased/run_dast_job_even_if_the_user_isnt_licensed_290958.yml b/changelogs/unreleased/run_dast_job_even_if_the_user_isnt_licensed_290958.yml new file mode 100644 index 00000000000..7878397fff9 --- /dev/null +++ b/changelogs/unreleased/run_dast_job_even_if_the_user_isnt_licensed_290958.yml @@ -0,0 +1,5 @@ +--- +title: Update the DAST latest template to run when configured even if the user doesn't have sufficient permission +merge_request: 51279 +author: +type: changed diff --git a/config/feature_flags/development/ci_allow_failure_with_exit_codes.yml b/config/feature_flags/development/ci_allow_failure_with_exit_codes.yml index c2701705616..1d33a162445 100644 --- a/config/feature_flags/development/ci_allow_failure_with_exit_codes.yml +++ b/config/feature_flags/development/ci_allow_failure_with_exit_codes.yml @@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/292024 milestone: '13.7' type: development group: group::pipeline authoring -default_enabled: false +default_enabled: true diff --git a/config/feature_flags/development/gitpod.yml b/config/feature_flags/development/gitpod.yml deleted file mode 100644 index 616aae4be45..00000000000 --- a/config/feature_flags/development/gitpod.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: gitpod -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/37985 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/258206 -milestone: '13.4' -type: development -group: group::editor -default_enabled: true diff --git a/config/feature_flags/development/invisible_captcha.yml b/config/feature_flags/development/invisible_captcha.yml deleted file mode 100644 index bfe118c1826..00000000000 --- a/config/feature_flags/development/invisible_captcha.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: invisible_captcha -introduced_by_url: https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/31625 -rollout_issue_url: -milestone: '12.2' -type: development -group: group::acquisition -default_enabled: false diff --git a/config/feature_flags/development/widget_visibility_polling.yml b/config/feature_flags/development/widget_visibility_polling.yml deleted file mode 100644 index 3021388b17f..00000000000 --- a/config/feature_flags/development/widget_visibility_polling.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: widget_visibility_polling -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/29318 -rollout_issue_url: -milestone: '12.10' -type: development -group: group::code review -default_enabled: true diff --git a/db/migrate/20201209193551_add_version_sha_cache_to_composer_metadata.rb b/db/migrate/20201209193551_add_version_sha_cache_to_composer_metadata.rb new file mode 100644 index 00000000000..62d0cdae8bc --- /dev/null +++ b/db/migrate/20201209193551_add_version_sha_cache_to_composer_metadata.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +class AddVersionShaCacheToComposerMetadata < ActiveRecord::Migration[6.0] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def up + with_lock_retries do + add_column :packages_composer_metadata, :version_cache_sha, :binary, null: true + end + end + + def down + with_lock_retries do + remove_column :packages_composer_metadata, :version_cache_sha, :binary + end + end +end diff --git a/db/migrate/20201229105948_add_invisible_captcha_enabled_to_settings.rb b/db/migrate/20201229105948_add_invisible_captcha_enabled_to_settings.rb new file mode 100644 index 00000000000..025fcba0729 --- /dev/null +++ b/db/migrate/20201229105948_add_invisible_captcha_enabled_to_settings.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class AddInvisibleCaptchaEnabledToSettings < ActiveRecord::Migration[6.0] + DOWNTIME = false + + def change + add_column :application_settings, :invisible_captcha_enabled, :boolean, null: false, default: false + end +end diff --git a/db/schema_migrations/20201209193551 b/db/schema_migrations/20201209193551 new file mode 100644 index 00000000000..2eb96840500 --- /dev/null +++ b/db/schema_migrations/20201209193551 @@ -0,0 +1 @@ +2278b1e4e19b5306e4b616eb87b622427ef2dcf73dae761739cb3106d5e64718
\ No newline at end of file diff --git a/db/schema_migrations/20201229105948 b/db/schema_migrations/20201229105948 new file mode 100644 index 00000000000..73da30d5392 --- /dev/null +++ b/db/schema_migrations/20201229105948 @@ -0,0 +1 @@ +cf391e617ef16f70c0daa4584959d36eda4b29c7e211f3f90ad74b4ebbc7ebbd
\ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 4d1c265a05f..1fe523e5138 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -9388,6 +9388,7 @@ CREATE TABLE application_settings ( disable_feed_token boolean DEFAULT false NOT NULL, personal_access_token_prefix text, rate_limiting_response_text text, + invisible_captcha_enabled boolean DEFAULT false NOT NULL, container_registry_cleanup_tags_service_max_list_size integer DEFAULT 200 NOT NULL, CONSTRAINT app_settings_container_reg_cleanup_tags_max_list_size_positive CHECK ((container_registry_cleanup_tags_service_max_list_size >= 0)), CONSTRAINT app_settings_registry_exp_policies_worker_capacity_positive CHECK ((container_registry_expiration_policies_worker_capacity >= 0)), @@ -14711,7 +14712,8 @@ ALTER SEQUENCE packages_build_infos_id_seq OWNED BY packages_build_infos.id; CREATE TABLE packages_composer_metadata ( package_id bigint NOT NULL, target_sha bytea NOT NULL, - composer_json jsonb DEFAULT '{}'::jsonb NOT NULL + composer_json jsonb DEFAULT '{}'::jsonb NOT NULL, + version_cache_sha bytea ); CREATE TABLE packages_conan_file_metadata ( diff --git a/doc/api/settings.md b/doc/api/settings.md index a71f49e724e..253381b3d50 100644 --- a/doc/api/settings.md +++ b/doc/api/settings.md @@ -300,6 +300,7 @@ listed in the descriptions of the relevant settings. | `housekeeping_incremental_repack_period` | integer | required by: `housekeeping_enabled` | Number of Git pushes after which an incremental `git repack` is run. | | `html_emails_enabled` | boolean | no | Enable HTML emails. | | `import_sources` | array of strings | no | Sources to allow project import from, possible values: `github`, `bitbucket`, `bitbucket_server`, `gitlab`, `fogbugz`, `git`, `gitlab_project`, `gitea`, `manifest`, and `phabricator`. | +| `invisible_captcha_enabled` | boolean | no | Enable Invisible Captcha spam detection during signup. Disabled by default. | | `issues_create_limit` | integer | no | Max number of issue creation requests per minute per user. Disabled by default.| | `local_markdown_version` | integer | no | Increase this value when any cached Markdown should be invalidated. | | `maintenance_mode_message` | string | no | **(PREMIUM)** Message displayed when instance is in maintenance mode | diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 20c6941686a..f47124d3c07 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -2278,10 +2278,10 @@ job3: #### `allow_failure:exit_codes` > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/273157) in GitLab 13.8. -> - It's [deployed behind a feature flag](../../user/feature_flags.md), disabled by default. -> - It's disabled on GitLab.com. -> - It's not recommended for production use. -> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-allow_failureexit_codes). **(CORE ONLY)** +> - It's [deployed behind a feature flag](../../user/feature_flags.md), enabled by default. +> - It's enabled on GitLab.com. +> - It's recommended for production use. +> - For GitLab self-managed instances, GitLab administrators can opt to [disable it](#enable-or-disable-allow_failureexit_codes). **(CORE ONLY)** WARNING: This feature might not be available to you. Check the **version history** note above for details. @@ -2310,21 +2310,21 @@ test_job_2: ##### Enable or disable `allow_failure:exit_codes` **(CORE ONLY)** -`allow_failure:exit_codes` is under development and not ready for production use. It is -deployed behind a feature flag that is **disabled by default**. +`allow_failure:exit_codes` is under development but ready for production use. It 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 enable it. +can disable it. -To enable it: +To disable it: ```ruby -Feature.enable(:ci_allow_failure_with_exit_codes) +Feature.disable(:ci_allow_failure_with_exit_codes) ``` -To disable it: +To enable it: ```ruby -Feature.disable(:ci_allow_failure_with_exit_codes) +Feature.enable(:ci_allow_failure_with_exit_codes) ``` ### `when` diff --git a/doc/integration/gitpod.md b/doc/integration/gitpod.md index 04274c1c015..05f129e6049 100644 --- a/doc/integration/gitpod.md +++ b/doc/integration/gitpod.md @@ -8,14 +8,7 @@ info: "To determine the technical writer assigned to the Stage/Group associated # Gitpod Integration > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/228893) in GitLab 13.4. -> - It was [deployed behind a feature flag](#enable-or-disable-the-gitpod-integration), disabled by default. -> - [Became enabled by default](https://gitlab.com/gitlab-org/gitlab/-/issues/258206) in GitLab 13.5. -> - It's enabled on GitLab.com. -> - It's recommended for production use. -> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](#configure-your-gitlab-instance-with-gitpod). **(CORE ONLY)** - -WARNING: -This feature might not be available to you. Check the **version history** note above for details. +> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/258206) in GitLab 13.8 With [Gitpod](https://gitpod.io/) you can describe your dev environment as code to get fully set up, compiled, and tested dev environments for any GitLab project. The dev environments are not only @@ -48,28 +41,14 @@ can follow the same steps once the integration has been enabled and configured b ## Configure your GitLab instance with Gitpod **(CORE ONLY)** -If you are new to Gitpod, head over to the [Gitpod documentation](https://www.gitpod.io/docs/self-hosted/latest/self-hosted/) -and get your instance up and running. +The integration of Gitpod with GitLab is enabled on GitLab.com and available to all users. +For GitLab self-managed instances, a GitLab administrator needs to enable it through the admin settings. + +First, you (GitLab admin) need to set up a Gitpod instance to integrate with GitLab. +Head over to the [Gitpod documentation](https://www.gitpod.io/docs/self-hosted/latest/self-hosted/) to +get your instance up and running. Once done: 1. In GitLab, go to **Admin Area > Settings > General**. 1. Expand the **Gitpod** configuration section. 1. Check **Enable Gitpod**. 1. Add your Gitpod instance URL (for example, `https://gitpod.example.com`). - -## Enable or disable the Gitpod integration **(CORE ONLY)** - -The Gitpod integration 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 enable or disable it. - -To disable it: - -```ruby -Feature.disable(:gitpod) -``` - -To enable it: - -```ruby -Feature.enable(:gitpod) -``` diff --git a/doc/user/project/quick_actions.md b/doc/user/project/quick_actions.md index 56299f80ef0..a8cbc5a5aff 100644 --- a/doc/user/project/quick_actions.md +++ b/doc/user/project/quick_actions.md @@ -9,8 +9,10 @@ info: To determine the technical writer assigned to the Stage/Group associated w > - Introduced in [GitLab 12.1](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/26672): > once an action is executed, an alert appears when a quick action is successfully applied. -> - In [GitLab 13.2](https://gitlab.com/gitlab-org/gitlab/-/issues/16877) and later, you can use +> - Introduced in [GitLab 13.2](https://gitlab.com/gitlab-org/gitlab/-/issues/16877): you can use > quick actions when updating the description of issues, epics, and merge requests. +> - Introduced in [GitLab 13.8](https://gitlab.com/gitlab-org/gitlab/-/issues/292393): when you enter +> `/` into a description or comment field, all available quick actions are displayed in a scrollable list. Quick actions are textual shortcuts for common actions on issues, epics, merge requests, and commits that are usually done by clicking buttons or dropdowns in the GitLab UI. diff --git a/lib/api/settings.rb b/lib/api/settings.rb index b3f09b431b0..f329a94adf2 100644 --- a/lib/api/settings.rb +++ b/lib/api/settings.rb @@ -91,6 +91,7 @@ module API optional :import_sources, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce, values: %w[github bitbucket bitbucket_server gitlab google_code fogbugz git gitlab_project gitea manifest phabricator], desc: 'Enabled sources for code import during project creation. OmniAuth must be configured for GitHub, Bitbucket, and GitLab.com' + optional :invisible_captcha_enabled, type: Boolean, desc: 'Enable Invisible Captcha spam detection during signup.' optional :max_artifacts_size, type: Integer, desc: "Set the maximum file size for each job's artifacts" optional :max_attachment_size, type: Integer, desc: 'Maximum attachment size in MB' optional :max_import_size, type: Integer, desc: 'Maximum import size in MB' diff --git a/lib/gitlab/ci/features.rb b/lib/gitlab/ci/features.rb index c96bc435957..e78e29422ea 100644 --- a/lib/gitlab/ci/features.rb +++ b/lib/gitlab/ci/features.rb @@ -64,7 +64,7 @@ module Gitlab end def self.allow_failure_with_exit_codes_enabled? - ::Feature.enabled?(:ci_allow_failure_with_exit_codes) + ::Feature.enabled?(:ci_allow_failure_with_exit_codes, default_enabled: :yaml) end def self.rules_variables_enabled?(project) diff --git a/lib/gitlab/ci/templates/Security/DAST.latest.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/DAST.latest.gitlab-ci.yml index dc3e1ab5ef8..fc1acd09714 100644 --- a/lib/gitlab/ci/templates/Security/DAST.latest.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Security/DAST.latest.gitlab-ci.yml @@ -38,8 +38,6 @@ dast: $CI_KUBERNETES_ACTIVE && $GITLAB_FEATURES =~ /\bdast\b/ - if: $CI_COMMIT_BRANCH && - $GITLAB_FEATURES =~ /\bdast\b/ && $DAST_WEBSITE - if: $CI_COMMIT_BRANCH && - $GITLAB_FEATURES =~ /\bdast\b/ && $DAST_API_SPECIFICATION diff --git a/lib/gitlab/composer/version_index.rb b/lib/gitlab/composer/version_index.rb new file mode 100644 index 00000000000..de9a17a453f --- /dev/null +++ b/lib/gitlab/composer/version_index.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +module Gitlab + module Composer + class VersionIndex + include API::Helpers::RelatedResourcesHelpers + + def initialize(packages) + @packages = packages + end + + def as_json(_options = nil) + { 'packages' => { @packages.first.name => package_versions_map } } + end + + def sha + Digest::SHA256.hexdigest(to_json) + end + + private + + def package_versions_map + @packages.each_with_object({}) do |package, map| + map[package.version] = package_metadata(package) + end + end + + def package_metadata(package) + json = package.composer_metadatum.composer_json + + json.merge('dist' => package_dist(package), 'uid' => package.id, 'version' => package.version) + end + + def package_dist(package) + sha = package.composer_metadatum.target_sha + archive_api_path = api_v4_projects_packages_composer_archives_package_name_path({ id: package.project_id, package_name: package.name, format: '.zip' }, true) + + { + 'type' => 'zip', + 'url' => expose_url(archive_api_path) + "?sha=#{sha}", + 'reference' => sha, + 'shasum' => '' + } + end + end + end +end diff --git a/lib/gitlab/gitpod.rb b/lib/gitlab/gitpod.rb deleted file mode 100644 index e35fb8fed02..00000000000 --- a/lib/gitlab/gitpod.rb +++ /dev/null @@ -1,26 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - class Gitpod - class << self - def feature_available? - # The gitpod_bundle feature could be conditionally applied, so check if `!off?` - !feature.off? || feature_enabled? - end - - def feature_enabled?(actor = nil) - Feature.enabled?(:gitpod, actor, default_enabled: true) - end - - def feature_and_settings_enabled?(actor = nil) - feature_enabled?(actor) && Gitlab::CurrentSettings.gitpod_enabled - end - - private - - def feature - Feature.get(:gitpod) # rubocop:disable Gitlab/AvoidFeatureGet - end - end - end -end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 34530f07f9e..7e3c89201bf 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -8827,6 +8827,9 @@ msgstr "" msgid "DastProfiles|Do you want to discard your changes?" msgstr "" +msgid "DastProfiles|Edit profile" +msgstr "" + msgid "DastProfiles|Edit scanner profile" msgstr "" @@ -8914,6 +8917,9 @@ msgstr "" msgid "DastProfiles|Scanner Profiles" msgstr "" +msgid "DastProfiles|Scanner name" +msgstr "" + msgid "DastProfiles|Show debug messages" msgstr "" @@ -8923,6 +8929,9 @@ msgstr "" msgid "DastProfiles|Site Profiles" msgstr "" +msgid "DastProfiles|Site name" +msgstr "" + msgid "DastProfiles|Spider timeout" msgstr "" @@ -8941,6 +8950,9 @@ msgstr "" msgid "DastProfiles|Turn on AJAX spider" msgstr "" +msgid "DastProfiles|URL" +msgstr "" + msgid "DastProfiles|Username" msgstr "" @@ -8950,6 +8962,9 @@ msgstr "" msgid "DastProfiles|Validated" msgstr "" +msgid "DastProfiles|Validation status" +msgstr "" + msgid "DastSiteValidation|Copy HTTP header to clipboard" msgstr "" @@ -8962,6 +8977,9 @@ msgstr "" msgid "DastSiteValidation|Header validation" msgstr "" +msgid "DastSiteValidation|Retry validation" +msgstr "" + msgid "DastSiteValidation|Step 1 - Choose site validation method" msgstr "" @@ -9604,6 +9622,27 @@ msgstr "" msgid "Deployment Frequency" msgstr "" +msgid "DeploymentFrequencyCharts|Date" +msgstr "" + +msgid "DeploymentFrequencyCharts|Deployments" +msgstr "" + +msgid "DeploymentFrequencyCharts|Deployments charts" +msgstr "" + +msgid "DeploymentFrequencyCharts|Deployments to production for last month (%{startDate} - %{endDate})" +msgstr "" + +msgid "DeploymentFrequencyCharts|Deployments to production for last week (%{startDate} - %{endDate})" +msgstr "" + +msgid "DeploymentFrequencyCharts|Deployments to production for the last 90 days (%{startDate} - %{endDate})" +msgstr "" + +msgid "DeploymentFrequencyCharts|Something went wrong while getting deployment frequency data" +msgstr "" + msgid "Deployment|API" msgstr "" @@ -10605,6 +10644,9 @@ msgstr "" msgid "Enable Incident Management inbound alert limit" msgstr "" +msgid "Enable Invisible Captcha during sign up" +msgstr "" + msgid "Enable Kroki" msgstr "" @@ -10692,7 +10734,7 @@ msgstr "" msgid "Enable proxy" msgstr "" -msgid "Enable reCAPTCHA or Akismet and set IP limits. For reCAPTCHA, we currently only support %{recaptcha_v2_link_start}v2%{recaptcha_v2_link_end}" +msgid "Enable reCAPTCHA, Invisible Captcha, Akismet and set IP limits. For reCAPTCHA, we currently only support %{recaptcha_v2_link_start}v2%{recaptcha_v2_link_end}" msgstr "" msgid "Enable shared runners" @@ -30677,7 +30719,7 @@ msgstr "" msgid "UserLists|Enter a comma separated list of user IDs. These IDs should be the users of the system in which the feature flag is set, not GitLab IDs" msgstr "" -msgid "UserLists|Feature flag list" +msgid "UserLists|Feature flag user list" msgstr "" msgid "UserLists|Lists allow you to define a set of users to be used with feature flags. %{linkStart}Read more about feature flag lists.%{linkEnd}" @@ -33849,6 +33891,9 @@ msgstr "" msgid "mrWidget|to start a merge train when the pipeline succeeds" msgstr "" +msgid "must be a boolean value" +msgstr "" + msgid "must be a root namespace" msgstr "" diff --git a/package.json b/package.json index 47669e7ed8d..14cb6d04d8f 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "@gitlab/favicon-overlay": "2.0.0", "@gitlab/svgs": "1.178.0", "@gitlab/tributejs": "1.0.0", - "@gitlab/ui": "25.7.3", + "@gitlab/ui": "25.8.0", "@gitlab/visual-review-tools": "1.6.1", "@rails/actioncable": "^6.0.3-4", "@rails/ujs": "^6.0.3-4", diff --git a/qa/qa/flow/sign_up.rb b/qa/qa/flow/sign_up.rb index 2fd42e8345a..b367b7976e8 100644 --- a/qa/qa/flow/sign_up.rb +++ b/qa/qa/flow/sign_up.rb @@ -14,14 +14,6 @@ module QA sign_up.fill_new_user_username_field(user.username) sign_up.fill_new_user_email_field(user.email) sign_up.fill_new_user_password_field(user.password) - - # Because invisible_captcha would prevent submitting this form - # within 4 seconds, sleep here. This can be removed once we - # implement invisible_captcha as an application setting instead - # of a feature flag, so we can turn it off while testing. - # Issue: https://gitlab.com/gitlab-org/gitlab/-/issues/284113 - sleep 5 - sign_up.click_new_user_register_button end diff --git a/spec/controllers/registrations_controller_spec.rb b/spec/controllers/registrations_controller_spec.rb index 2fb17e56f37..737ec4f95c5 100644 --- a/spec/controllers/registrations_controller_spec.rb +++ b/spec/controllers/registrations_controller_spec.rb @@ -6,7 +6,6 @@ RSpec.describe RegistrationsController do include TermsHelper before do - stub_feature_flags(invisible_captcha: false) stub_application_setting(require_admin_approval_after_user_signup: false) end @@ -193,15 +192,10 @@ RSpec.describe RegistrationsController do context 'when invisible captcha is enabled' do before do - stub_feature_flags(invisible_captcha: true) - InvisibleCaptcha.timestamp_enabled = true + stub_application_setting(invisible_captcha_enabled: true) InvisibleCaptcha.timestamp_threshold = treshold end - after do - InvisibleCaptcha.timestamp_enabled = false - end - let(:treshold) { 4 } let(:session_params) { { invisible_captcha_timestamp: form_rendered_time.iso8601 } } let(:form_rendered_time) { Time.current } diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb index 06d31b544ea..0c66775c323 100644 --- a/spec/features/admin/admin_settings_spec.rb +++ b/spec/features/admin/admin_settings_spec.rb @@ -17,10 +17,7 @@ RSpec.describe 'Admin updates settings' do end context 'General page' do - let(:gitpod_feature_enabled) { true } - before do - stub_feature_flags(gitpod: gitpod_feature_enabled) visit general_admin_application_settings_path end @@ -224,28 +221,16 @@ RSpec.describe 'Admin updates settings' do end context 'Configure Gitpod' do - context 'with feature disabled' do - let(:gitpod_feature_enabled) { false } - - it 'do not show settings' do - expect(page).not_to have_selector('#js-gitpod-settings') + it 'changes gitpod settings' do + page.within('#js-gitpod-settings') do + check 'Enable Gitpod integration' + fill_in 'Gitpod URL', with: 'https://gitpod.test/' + click_button 'Save changes' end - end - - context 'with feature enabled' do - let(:gitpod_feature_enabled) { true } - it 'changes gitpod settings' do - page.within('#js-gitpod-settings') do - check 'Enable Gitpod integration' - fill_in 'Gitpod URL', with: 'https://gitpod.test/' - click_button 'Save changes' - end - - expect(page).to have_content 'Application settings saved successfully' - expect(current_settings.gitpod_url).to eq('https://gitpod.test/') - expect(current_settings.gitpod_enabled).to be(true) - end + expect(page).to have_content 'Application settings saved successfully' + expect(current_settings.gitpod_url).to eq('https://gitpod.test/') + expect(current_settings.gitpod_enabled).to be(true) end end end diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb index 64c056deca5..9b2a11c4b0e 100644 --- a/spec/features/issues/gfm_autocomplete_spec.rb +++ b/spec/features/issues/gfm_autocomplete_spec.rb @@ -294,6 +294,15 @@ RSpec.describe 'GFM autocomplete', :js do user_item = find('.atwho-view li', text: user.username) expect(user_item).to have_content(user.username) end + + it 'does not limit quick actions autocomplete list to 5' do + note = find('#note-body') + page.within '.timeline-content-form' do + note.native.send_keys('/') + end + + expect(page).to have_selector('.atwho-view li', minimum: 6, visible: true) + end end context 'assignees' do diff --git a/spec/features/users/signup_spec.rb b/spec/features/users/signup_spec.rb index bfdd1e1bdb7..5f70517224e 100644 --- a/spec/features/users/signup_spec.rb +++ b/spec/features/users/signup_spec.rb @@ -294,17 +294,13 @@ RSpec.describe 'Signup' do context 'when reCAPTCHA and invisible captcha are enabled' do before do - InvisibleCaptcha.timestamp_enabled = true + stub_application_setting(invisible_captcha_enabled: true) stub_application_setting(recaptcha_enabled: true) allow_next_instance_of(RegistrationsController) do |instance| allow(instance).to receive(:verify_recaptcha).and_return(true) end end - after do - InvisibleCaptcha.timestamp_enabled = false - end - context 'when reCAPTCHA detects malicious behaviour' do before do allow_next_instance_of(RegistrationsController) do |instance| diff --git a/spec/frontend/blob_edit/edit_blob_spec.js b/spec/frontend/blob_edit/edit_blob_spec.js index f1f22010a2a..7927c14d2ac 100644 --- a/spec/frontend/blob_edit/edit_blob_spec.js +++ b/spec/frontend/blob_edit/edit_blob_spec.js @@ -1,12 +1,12 @@ import waitForPromises from 'helpers/wait_for_promises'; import EditBlob from '~/blob_edit/edit_blob'; import EditorLite from '~/editor/editor_lite'; -import { EditorMarkdownExtension } from '~/editor/editor_markdown_ext'; -import { FileTemplateExtension } from '~/editor/editor_file_template_ext'; +import { EditorMarkdownExtension } from '~/editor/extensions/editor_markdown_ext'; +import { FileTemplateExtension } from '~/editor/extensions/editor_file_template_ext'; jest.mock('~/editor/editor_lite'); -jest.mock('~/editor/editor_markdown_ext'); -jest.mock('~/editor/editor_file_template_ext'); +jest.mock('~/editor/extensions/editor_markdown_ext'); +jest.mock('~/editor/extensions/editor_file_template_ext'); describe('Blob Editing', () => { const useMock = jest.fn(); diff --git a/spec/frontend/editor/editor_ci_schema_ext_spec.js b/spec/frontend/editor/editor_ci_schema_ext_spec.js index f9a1070f601..4d65b77cdfc 100644 --- a/spec/frontend/editor/editor_ci_schema_ext_spec.js +++ b/spec/frontend/editor/editor_ci_schema_ext_spec.js @@ -1,6 +1,6 @@ import { languages } from 'monaco-editor'; import EditorLite from '~/editor/editor_lite'; -import { CiSchemaExtension } from '~/editor/editor_ci_schema_ext'; +import { CiSchemaExtension } from '~/editor/extensions/editor_ci_schema_ext'; import { EXTENSION_CI_SCHEMA_FILE_NAME_MATCH } from '~/editor/constants'; describe('~/editor/editor_ci_config_ext', () => { diff --git a/spec/frontend/editor/editor_lite_extension_base_spec.js b/spec/frontend/editor/editor_lite_extension_base_spec.js index 8261864b7a2..5490e9dc7b5 100644 --- a/spec/frontend/editor/editor_lite_extension_base_spec.js +++ b/spec/frontend/editor/editor_lite_extension_base_spec.js @@ -1,5 +1,5 @@ import { ERROR_INSTANCE_REQUIRED_FOR_EXTENSION } from '~/editor/constants'; -import { EditorLiteExtension } from '~/editor/editor_lite_extension_base'; +import { EditorLiteExtension } from '~/editor/extensions/editor_lite_extension_base'; describe('The basis for an Editor Lite extension', () => { let ext; diff --git a/spec/frontend/editor/editor_lite_spec.js b/spec/frontend/editor/editor_lite_spec.js index 7d8acd8599a..c3099997287 100644 --- a/spec/frontend/editor/editor_lite_spec.js +++ b/spec/frontend/editor/editor_lite_spec.js @@ -2,7 +2,7 @@ import { editor as monacoEditor, languages as monacoLanguages, Uri } from 'monaco-editor'; import waitForPromises from 'helpers/wait_for_promises'; import Editor from '~/editor/editor_lite'; -import { EditorLiteExtension } from '~/editor/editor_lite_extension_base'; +import { EditorLiteExtension } from '~/editor/extensions/editor_lite_extension_base'; import { DEFAULT_THEME, themes } from '~/ide/lib/themes'; import { EDITOR_LITE_INSTANCE_ERROR_NO_EL, URI_PREFIX } from '~/editor/constants'; diff --git a/spec/frontend/editor/editor_markdown_ext_spec.js b/spec/frontend/editor/editor_markdown_ext_spec.js index f3b5add0b70..3f64dcfd7a0 100644 --- a/spec/frontend/editor/editor_markdown_ext_spec.js +++ b/spec/frontend/editor/editor_markdown_ext_spec.js @@ -1,6 +1,6 @@ import { Range, Position } from 'monaco-editor'; import EditorLite from '~/editor/editor_lite'; -import { EditorMarkdownExtension } from '~/editor/editor_markdown_ext'; +import { EditorMarkdownExtension } from '~/editor/extensions/editor_markdown_ext'; describe('Markdown Extension for Editor Lite', () => { let editor; diff --git a/spec/frontend/projects/pipelines/charts/components/__snapshots__/pipelines_area_chart_spec.js.snap b/spec/frontend/projects/pipelines/charts/components/__snapshots__/ci_cd_analytics_area_chart_spec.js.snap index d68e009f46e..fc51825f15b 100644 --- a/spec/frontend/projects/pipelines/charts/components/__snapshots__/pipelines_area_chart_spec.js.snap +++ b/spec/frontend/projects/pipelines/charts/components/__snapshots__/ci_cd_analytics_area_chart_spec.js.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`PipelinesAreaChart matches the snapshot 1`] = ` +exports[`CiCdAnalyticsAreaChart matches the snapshot 1`] = ` <div class="gl-mt-3" > diff --git a/spec/frontend/projects/pipelines/charts/components/app_legacy_spec.js b/spec/frontend/projects/pipelines/charts/components/app_legacy_spec.js index c03b571eb26..99d0e910485 100644 --- a/spec/frontend/projects/pipelines/charts/components/app_legacy_spec.js +++ b/spec/frontend/projects/pipelines/charts/components/app_legacy_spec.js @@ -2,7 +2,7 @@ import { shallowMount } from '@vue/test-utils'; import { GlColumnChart } from '@gitlab/ui/dist/charts'; import Component from '~/projects/pipelines/charts/components/app_legacy.vue'; import StatisticsList from '~/projects/pipelines/charts/components/statistics_list.vue'; -import PipelinesAreaChart from '~/projects/pipelines/charts/components/pipelines_area_chart.vue'; +import CiCdAnalyticsAreaChart from '~/projects/pipelines/charts/components/ci_cd_analytics_area_chart.vue'; import { counts, timesChartData, @@ -23,6 +23,13 @@ describe('ProjectsPipelinesChartsApp', () => { lastMonthChartData, lastYearChartData, }, + provide: { + projectPath: 'test/project', + shouldRenderDeploymentFrequencyCharts: true, + }, + stubs: { + DeploymentFrequencyCharts: true, + }, }); }); @@ -52,12 +59,12 @@ describe('ProjectsPipelinesChartsApp', () => { describe('pipelines charts', () => { it('displays 3 area charts', () => { - expect(wrapper.findAll(PipelinesAreaChart).length).toBe(3); + expect(wrapper.findAll(CiCdAnalyticsAreaChart).length).toBe(3); }); describe('displays individual correctly', () => { it('renders with the correct data', () => { - const charts = wrapper.findAll(PipelinesAreaChart); + const charts = wrapper.findAll(CiCdAnalyticsAreaChart); for (let i = 0; i < charts.length; i += 1) { const chart = charts.at(i); diff --git a/spec/frontend/projects/pipelines/charts/components/app_spec.js b/spec/frontend/projects/pipelines/charts/components/app_spec.js index 977d1caa2b6..1313a549f02 100644 --- a/spec/frontend/projects/pipelines/charts/components/app_spec.js +++ b/spec/frontend/projects/pipelines/charts/components/app_spec.js @@ -1,10 +1,11 @@ +import { merge } from 'lodash'; import { createLocalVue, shallowMount } from '@vue/test-utils'; import VueApollo from 'vue-apollo'; import createMockApollo from 'jest/helpers/mock_apollo_helper'; import { GlColumnChart } from '@gitlab/ui/dist/charts'; import Component from '~/projects/pipelines/charts/components/app.vue'; import StatisticsList from '~/projects/pipelines/charts/components/statistics_list.vue'; -import PipelinesAreaChart from '~/projects/pipelines/charts/components/pipelines_area_chart.vue'; +import CiCdAnalyticsAreaChart from '~/projects/pipelines/charts/components/ci_cd_analytics_area_chart.vue'; import getPipelineCountByStatus from '~/projects/pipelines/charts/graphql/queries/get_pipeline_count_by_status.query.graphql'; import getProjectPipelineStatistics from '~/projects/pipelines/charts/graphql/queries/get_project_pipeline_statistics.query.graphql'; import { mockPipelineCount, mockPipelineStatistics } from '../mock_data'; @@ -13,6 +14,8 @@ const projectPath = 'gitlab-org/gitlab'; const localVue = createLocalVue(); localVue.use(VueApollo); +const DeploymentFrequencyChartsStub = { name: 'DeploymentFrequencyCharts', render: () => {} }; + describe('ProjectsPipelinesChartsApp', () => { let wrapper; @@ -25,21 +28,29 @@ describe('ProjectsPipelinesChartsApp', () => { return createMockApollo(requestHandlers); } - function createComponent(options = {}) { - const { fakeApollo } = options; - - return shallowMount(Component, { - provide: { - projectPath, - }, - localVue, - apolloProvider: fakeApollo, - }); + function createComponent(mountOptions = {}) { + wrapper = shallowMount( + Component, + merge( + {}, + { + provide: { + projectPath, + shouldRenderDeploymentFrequencyCharts: false, + }, + localVue, + apolloProvider: createMockApolloProvider(), + stubs: { + DeploymentFrequencyCharts: DeploymentFrequencyChartsStub, + }, + }, + mountOptions, + ), + ); } beforeEach(() => { - const fakeApollo = createMockApolloProvider(); - wrapper = createComponent({ fakeApollo }); + createComponent(); }); afterEach(() => { @@ -73,12 +84,12 @@ describe('ProjectsPipelinesChartsApp', () => { describe('pipelines charts', () => { it('displays 3 area charts', () => { - expect(wrapper.findAll(PipelinesAreaChart)).toHaveLength(3); + expect(wrapper.findAll(CiCdAnalyticsAreaChart)).toHaveLength(3); }); describe('displays individual correctly', () => { it('renders with the correct data', () => { - const charts = wrapper.findAll(PipelinesAreaChart); + const charts = wrapper.findAll(CiCdAnalyticsAreaChart); for (let i = 0; i < charts.length; i += 1) { const chart = charts.at(i); @@ -92,4 +103,26 @@ describe('ProjectsPipelinesChartsApp', () => { }); }); }); + + const findDeploymentFrequencyCharts = () => wrapper.find(DeploymentFrequencyChartsStub); + + describe('when shouldRenderDeploymentFrequencyCharts is true', () => { + beforeEach(() => { + createComponent({ provide: { shouldRenderDeploymentFrequencyCharts: true } }); + }); + + it('renders the deployment frequency charts', () => { + expect(findDeploymentFrequencyCharts().exists()).toBe(true); + }); + }); + + describe('when shouldRenderDeploymentFrequencyCharts is false', () => { + beforeEach(() => { + createComponent({ provide: { shouldRenderDeploymentFrequencyCharts: false } }); + }); + + it('does not render the deployment frequency charts', () => { + expect(findDeploymentFrequencyCharts().exists()).toBe(false); + }); + }); }); diff --git a/spec/frontend/projects/pipelines/charts/components/pipelines_area_chart_spec.js b/spec/frontend/projects/pipelines/charts/components/ci_cd_analytics_area_chart_spec.js index aea25903023..64f80300237 100644 --- a/spec/frontend/projects/pipelines/charts/components/pipelines_area_chart_spec.js +++ b/spec/frontend/projects/pipelines/charts/components/ci_cd_analytics_area_chart_spec.js @@ -1,14 +1,23 @@ import { mount } from '@vue/test-utils'; -import Component from '~/projects/pipelines/charts/components/pipelines_area_chart.vue'; +import CiCdAnalyticsAreaChart from '~/projects/pipelines/charts/components/ci_cd_analytics_area_chart.vue'; import { transformedAreaChartData } from '../mock_data'; -describe('PipelinesAreaChart', () => { +describe('CiCdAnalyticsAreaChart', () => { let wrapper; beforeEach(() => { - wrapper = mount(Component, { + wrapper = mount(CiCdAnalyticsAreaChart, { propsData: { chartData: transformedAreaChartData, + areaChartOptions: { + xAxis: { + name: 'X axis title', + type: 'category', + }, + yAxis: { + name: 'Y axis title', + }, + }, }, slots: { default: 'Some title', diff --git a/spec/helpers/graph_helper_spec.rb b/spec/helpers/graph_helper_spec.rb index 3c7e4f970c3..682f6365481 100644 --- a/spec/helpers/graph_helper_spec.rb +++ b/spec/helpers/graph_helper_spec.rb @@ -15,4 +15,16 @@ RSpec.describe GraphHelper do expect(refs).to match('master') end end + + describe '#should_render_deployment_frequency_charts' do + let(:project) { create(:project, :private) } + + before do + self.instance_variable_set(:@project, project) + end + + it 'always returns false' do + expect(should_render_deployment_frequency_charts).to be(false) + end + end end diff --git a/spec/helpers/tree_helper_spec.rb b/spec/helpers/tree_helper_spec.rb index 136ec07e73d..6cb9894e306 100644 --- a/spec/helpers/tree_helper_spec.rb +++ b/spec/helpers/tree_helper_spec.rb @@ -330,9 +330,8 @@ RSpec.describe TreeHelper do end end - context 'gitpod feature is enabled' do + context 'gitpod settings is enabled' do before do - stub_feature_flags(gitpod: true) allow(Gitlab::CurrentSettings) .to receive(:gitpod_enabled) .and_return(true) diff --git a/spec/lib/gitlab/composer/version_index_spec.rb b/spec/lib/gitlab/composer/version_index_spec.rb new file mode 100644 index 00000000000..4c4742d9f59 --- /dev/null +++ b/spec/lib/gitlab/composer/version_index_spec.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Composer::VersionIndex do + let_it_be(:package_name) { 'sample-project' } + let_it_be(:json) { { 'name' => package_name } } + let_it_be(:group) { create(:group) } + let_it_be(:project) { create(:project, :custom_repo, files: { 'composer.json' => json.to_json }, group: group) } + let_it_be(:package1) { create(:composer_package, :with_metadatum, project: project, name: package_name, version: '1.0.0', json: json) } + let_it_be(:package2) { create(:composer_package, :with_metadatum, project: project, name: package_name, version: '2.0.0', json: json) } + + let(:branch) { project.repository.find_branch('master') } + + let(:packages) { [package1, package2] } + + describe '#as_json' do + subject(:index) { described_class.new(packages).as_json } + + def expected_json(package) + { + 'dist' => { + 'reference' => branch.target, + 'shasum' => '', + 'type' => 'zip', + 'url' => "http://localhost/api/v4/projects/#{project.id}/packages/composer/archives/#{package.name}.zip?sha=#{branch.target}" + }, + 'name' => package.name, + 'uid' => package.id, + 'version' => package.version + } + end + + it 'returns the packages json' do + packages = index['packages'][package_name] + + expect(packages['1.0.0']).to eq(expected_json(package1)) + expect(packages['2.0.0']).to eq(expected_json(package2)) + end + end + + describe '#sha' do + subject(:sha) { described_class.new(packages).sha } + + it 'returns the json SHA' do + expect(sha).to match /^[A-Fa-f0-9]{64}$/ + end + end +end diff --git a/spec/lib/gitlab/gitpod_spec.rb b/spec/lib/gitlab/gitpod_spec.rb deleted file mode 100644 index 717e396f942..00000000000 --- a/spec/lib/gitlab/gitpod_spec.rb +++ /dev/null @@ -1,73 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Gitlab::Gitpod do - let_it_be(:user) { create(:user) } - - before do - stub_feature_flags(gitpod: feature_scope) - end - - describe '.feature_available?' do - subject { described_class.feature_available? } - - context 'when feature has not been set' do - let(:feature_scope) { nil } - - it { is_expected.to be_truthy } - end - - context 'when feature is disabled' do - let(:feature_scope) { false } - - it { is_expected.to be_falsey } - end - - context 'when feature is enabled globally' do - let(:feature_scope) { true } - - it { is_expected.to be_truthy } - end - - context 'when feature is enabled only to a resource' do - let(:feature_scope) { user } - - it { is_expected.to be_truthy } - end - end - - describe '.feature_enabled?' do - let(:current_user) { nil } - - subject { described_class.feature_enabled?(current_user) } - - context 'when feature has not been set' do - let(:feature_scope) { nil } - - it { is_expected.to be_truthy } - end - - context 'when feature is enabled globally' do - let(:feature_scope) { true } - - it { is_expected.to be_truthy } - end - - context 'when feature is enabled only to a resource' do - let(:feature_scope) { user } - - context 'for the same resource' do - let(:current_user) { user } - - it { is_expected.to be_truthy } - end - - context 'for a different resource' do - let(:current_user) { create(:user) } - - it { is_expected.to be_falsey } - end - end - end -end diff --git a/spec/models/list_spec.rb b/spec/models/list_spec.rb index 37158584062..f0b1bc33e84 100644 --- a/spec/models/list_spec.rb +++ b/spec/models/list_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' RSpec.describe List do it_behaves_like 'having unique enum values' + it_behaves_like 'boards listable model', :list describe 'relationships' do it { is_expected.to belong_to(:board) } @@ -14,72 +15,6 @@ RSpec.describe List do it { is_expected.to validate_presence_of(:board) } it { is_expected.to validate_presence_of(:label) } it { is_expected.to validate_presence_of(:list_type) } - it { is_expected.to validate_presence_of(:position) } - it { is_expected.to validate_numericality_of(:position).only_integer.is_greater_than_or_equal_to(0) } - - context 'when list_type is set to closed' do - subject { described_class.new(list_type: :closed) } - - it { is_expected.not_to validate_presence_of(:label) } - it { is_expected.not_to validate_presence_of(:position) } - end - end - - describe '#destroy' do - it 'can be destroyed when list_type is set to label' do - subject = create(:list) - - expect(subject.destroy).to be_truthy - end - - it 'can not be destroyed when list_type is set to closed' do - subject = create(:closed_list) - - expect(subject.destroy).to be_falsey - end - end - - describe '#destroyable?' do - it 'returns true when list_type is set to label' do - subject.list_type = :label - - expect(subject).to be_destroyable - end - - it 'returns false when list_type is set to closed' do - subject.list_type = :closed - - expect(subject).not_to be_destroyable - end - end - - describe '#movable?' do - it 'returns true when list_type is set to label' do - subject.list_type = :label - - expect(subject).to be_movable - end - - it 'returns false when list_type is set to closed' do - subject.list_type = :closed - - expect(subject).not_to be_movable - end - end - - describe '#title' do - it 'returns label name when list_type is set to label' do - subject.list_type = :label - subject.label = Label.new(name: 'Development') - - expect(subject.title).to eq 'Development' - end - - it 'returns Closed when list_type is set to closed' do - subject.list_type = :closed - - expect(subject.title).to eq 'Closed' - end end describe '#update_preferences_for' do diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index c5560ea462f..9fac6c8e192 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -369,8 +369,5 @@ end # Prevent Rugged from picking up local developer gitconfig. Rugged::Settings['search_path_global'] = Rails.root.join('tmp/tests').to_s -# Disable timestamp checks for invisible_captcha -InvisibleCaptcha.timestamp_enabled = false - # Initialize FactoryDefault to use create_default helper TestProf::FactoryDefault.init diff --git a/spec/support/shared_examples/models/boards/listable_shared_examples.rb b/spec/support/shared_examples/models/boards/listable_shared_examples.rb new file mode 100644 index 00000000000..49b00076a46 --- /dev/null +++ b/spec/support/shared_examples/models/boards/listable_shared_examples.rb @@ -0,0 +1,91 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'boards listable model' do |list_factory| + subject { build(list_factory) } + + describe 'associations' do + it { is_expected.to validate_presence_of(:position) } + it { is_expected.to validate_numericality_of(:position).only_integer.is_greater_than_or_equal_to(0) } + + context 'when list_type is set to closed' do + subject { build(list_factory, list_type: :closed) } + + it { is_expected.not_to validate_presence_of(:label) } + it { is_expected.not_to validate_presence_of(:position) } + end + end + + describe 'scopes' do + describe '.ordered' do + it 'returns lists ordered by type and position' do + # rubocop:disable Rails/SaveBang + lists = [ + create(list_factory, list_type: :backlog), + create(list_factory, list_type: :closed), + create(list_factory, position: 1), + create(list_factory, position: 2) + ] + # rubocop:enable Rails/SaveBang + + expect(described_class.where(id: lists).ordered).to eq([lists[0], lists[2], lists[3], lists[1]]) + end + end + end + + describe '#destroyable?' do + it 'returns true when list_type is set to label' do + subject.list_type = :label + + expect(subject).to be_destroyable + end + + it 'returns false when list_type is set to closed' do + subject.list_type = :closed + + expect(subject).not_to be_destroyable + end + end + + describe '#movable?' do + it 'returns true when list_type is set to label' do + subject.list_type = :label + + expect(subject).to be_movable + end + + it 'returns false when list_type is set to closed' do + subject.list_type = :closed + + expect(subject).not_to be_movable + end + end + + describe '#title' do + it 'returns label name when list_type is set to label' do + subject.list_type = :label + subject.label = Label.new(name: 'Development') + + expect(subject.title).to eq 'Development' + end + + it 'returns Closed when list_type is set to closed' do + subject.list_type = :closed + + expect(subject.title).to eq 'Closed' + end + end + + describe '#destroy' do + it 'can be destroyed when list_type is set to label' do + subject = create(list_factory) # rubocop:disable Rails/SaveBang + + expect(subject.destroy).to be_truthy + end + + it 'can not be destroyed when list_type is set to closed' do + subject = create(list_factory, list_type: :closed) # rubocop:disable Rails/SaveBang + + expect(subject.destroy).to be_falsey + end + end +end diff --git a/yarn.lock b/yarn.lock index 9e2fbe129f3..21d2280c7da 100644 --- a/yarn.lock +++ b/yarn.lock @@ -876,10 +876,10 @@ resolved "https://registry.yarnpkg.com/@gitlab/tributejs/-/tributejs-1.0.0.tgz#672befa222aeffc83e7d799b0500a7a4418e59b8" integrity sha512-nmKw1+hB6MHvlmPz63yPwVs1qQkycHwsKgxpEbzmky16Y6mL4EJMk3w1b8QlOAF/AIAzjCERPhe/R4MJiohbZw== -"@gitlab/ui@25.7.3": - version "25.7.3" - resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-25.7.3.tgz#abc5caa585c307f38e01a7f061d9da0bc238acd0" - integrity sha512-Ia+Pqmy4tQ5oztxXSjViM6YMt13MGyrQ6dqOn3zsiKG9RLh9kSBMtCif9jA0dDT17P+HMIdBYxYc6rTRjbfC0w== +"@gitlab/ui@25.8.0": + version "25.8.0" + resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-25.8.0.tgz#038090bc56215d2b0e5526097e1a16b0089ba5f4" + integrity sha512-h84StVkrviIm1cMDmGb2+Q8R+U6wCjddz7IXKpgkTNitxYzAcwPSIp7cS1FkZ6eWEG9dVeB6uj7JpUhGqAzvfw== dependencies: "@babel/standalone" "^7.0.0" "@gitlab/vue-toasted" "^1.3.0" |