Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-01-12 15:10:37 +00:00
parent bbfd13e575
commit 84dd3070df
82 changed files with 615 additions and 429 deletions

View File

@ -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'

View File

@ -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)

View File

@ -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);

View File

@ -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 {
/**

View File

@ -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 } = {}) {

View File

@ -103,6 +103,7 @@ class GfmAutoComplete {
at: '/',
alias: 'commands',
searchKey: 'search',
limit: 100,
skipSpecialCharacterTest: true,
skipMarkdownCharacterTest: true,
data: GfmAutoComplete.defaultLoadingData,

View File

@ -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;
}

View File

@ -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);
},
});
});

View File

@ -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: {

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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: {

View File

@ -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}',
),

View File

@ -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,
});
},

View File

@ -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')

View File

@ -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)

View File

@ -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,

View File

@ -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')

View File

@ -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

View File

@ -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

View File

@ -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!

View File

@ -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,

View File

@ -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

View File

@ -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')

View File

@ -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

View File

@ -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) }

View File

@ -28,6 +28,14 @@
.form-group
= f.text_field :recaptcha_private_key, class: 'form-control'
.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'

View File

@ -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'

View File

@ -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

View File

@ -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 } }

View File

@ -0,0 +1,5 @@
---
title: Add configurable Gitpod button within projcet repository page
merge_request: 51197
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: Add version cache field to composer metadata
merge_request: 50906
author:
type: other

View File

@ -0,0 +1,5 @@
---
title: Show all quick actions in `/` autocomplete
merge_request: 51239
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: Control job status using exit codes
merge_request: 51439
author:
type: added

View File

@ -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

View File

@ -0,0 +1,5 @@
---
title: Add setting to enable Invisible Captcha
merge_request: 50650
author:
type: added

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -0,0 +1 @@
2278b1e4e19b5306e4b616eb87b622427ef2dcf73dae761739cb3106d5e64718

View File

@ -0,0 +1 @@
cf391e617ef16f70c0daa4584959d36eda4b29c7e211f3f90ad74b4ebbc7ebbd

View File

@ -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 (

View File

@ -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 |

View File

@ -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,16 +2310,10 @@ 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.
To enable it:
```ruby
Feature.enable(:ci_allow_failure_with_exit_codes)
```
can disable it.
To disable it:
@ -2327,6 +2321,12 @@ To disable it:
Feature.disable(:ci_allow_failure_with_exit_codes)
```
To enable it:
```ruby
Feature.enable(:ci_allow_failure_with_exit_codes)
```
### `when`
`when` is used to implement jobs that are run in case of failure or despite the

View File

@ -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)
```

View File

@ -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.

View File

@ -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'

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 ""

View File

@ -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",

View File

@ -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

View File

@ -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 }

View File

@ -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

View File

@ -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

View File

@ -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|

View File

@ -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();

View File

@ -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', () => {

View File

@ -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;

View File

@ -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';

View File

@ -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;

View File

@ -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"
>

View File

@ -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);

View File

@ -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);
});
});
});

View File

@ -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',

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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"