Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
bb01b338bf
commit
09acddd7fd
|
|
@ -5,9 +5,6 @@ include:
|
|||
- local: .gitlab/ci/templates/gem.gitlab-ci.yml
|
||||
inputs:
|
||||
gem_name: "click_house-client"
|
||||
- local: .gitlab/ci/templates/gem.gitlab-ci.yml
|
||||
inputs:
|
||||
gem_name: "gitlab-ipynbdiff"
|
||||
- local: .gitlab/ci/templates/gem.gitlab-ci.yml
|
||||
inputs:
|
||||
gem_name: "gitlab-rspec"
|
||||
|
|
@ -17,6 +14,9 @@ include:
|
|||
- local: .gitlab/ci/templates/gem.gitlab-ci.yml
|
||||
inputs:
|
||||
gem_name: "gitlab-utils"
|
||||
- local: .gitlab/ci/templates/gem.gitlab-ci.yml
|
||||
inputs:
|
||||
gem_name: "ipynbdiff"
|
||||
- local: .gitlab/ci/templates/gem.gitlab-ci.yml
|
||||
inputs:
|
||||
gem_name: "rspec_flaky"
|
||||
|
|
|
|||
|
|
@ -275,7 +275,7 @@ rspec:deprecations:
|
|||
script:
|
||||
- grep -h -R "keyword" deprecations/ | awk '{$1=$1};1' | sort | uniq -c | sort
|
||||
- grep -R "keyword" deprecations/ | wc
|
||||
- run_timed_command "fail_on_warnings bundle exec rubocop --only Lint/LastKeywordArgument --parallel"
|
||||
- run_timed_command "fail_on_warnings bundle exec rubocop --config .rubocop.yml --only Lint/LastKeywordArgument --parallel"
|
||||
artifacts:
|
||||
expire_in: 31d
|
||||
when: always
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ rubocop:
|
|||
select_existing_files < "${RSPEC_CHANGED_FILES_PATH}" > "${RUBOCOP_TARGET_FILES}"
|
||||
# Skip running RuboCop if there's no target files
|
||||
if [ -s "${RUBOCOP_TARGET_FILES}" ]; then
|
||||
run_timed_command "fail_on_warnings bundle exec rubocop --parallel --force-exclusion $(cat ${RUBOCOP_TARGET_FILES})"
|
||||
run_timed_command "fail_on_warnings bundle exec rubocop --config .rubocop.yml --parallel --force-exclusion $(cat ${RUBOCOP_TARGET_FILES})"
|
||||
else
|
||||
echoinfo "Nothing interesting changed for RuboCop. Skipping."
|
||||
fi
|
||||
|
|
@ -177,7 +177,7 @@ feature-flags-usage:
|
|||
script:
|
||||
# We need to disable the cache for this cop since it creates files under tmp/feature_flags/*.used,
|
||||
# the cache would prevent these files from being created.
|
||||
- run_timed_command "fail_on_warnings bundle exec rubocop --only Gitlab/MarkUsedFeatureFlags --cache false"
|
||||
- run_timed_command "fail_on_warnings bundle exec rubocop --config .rubocop.yml --only Gitlab/MarkUsedFeatureFlags --cache false"
|
||||
artifacts:
|
||||
expire_in: 31d
|
||||
when: always
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ spec:
|
|||
- if: '$CI_MERGE_REQUEST_EVENT_TYPE == "merged_result" || $CI_MERGE_REQUEST_EVENT_TYPE == "detached"'
|
||||
changes:
|
||||
- "$[[inputs.gem_path_prefix]]$[[inputs.gem_name]]/**/*"
|
||||
- ".gitlab/ci/gitlab-gems.gitlab-ci.yml"
|
||||
- ".gitlab/ci/templates/gem.gitlab-ci.yml"
|
||||
- "gems/gem.gitlab-ci.yml"
|
||||
|
||||
|
|
|
|||
2
Gemfile
2
Gemfile
|
|
@ -184,7 +184,7 @@ gem 'elasticsearch-rails', '~> 7.2', require: 'elasticsearch/rails/instrumentati
|
|||
gem 'elasticsearch-api', '7.13.3'
|
||||
gem 'aws-sdk-core', '~> 3.178.0'
|
||||
gem 'aws-sdk-cloudformation', '~> 1'
|
||||
gem 'aws-sdk-s3', '~> 1.128.0'
|
||||
gem 'aws-sdk-s3', '~> 1.129.0'
|
||||
gem 'faraday_middleware-aws-sigv4', '~>0.3.0'
|
||||
gem 'typhoeus', '~> 1.4.0' # Used with Elasticsearch to support http keep-alive connections
|
||||
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@
|
|||
{"name":"aws-sdk-cloudformation","version":"1.41.0","platform":"ruby","checksum":"31e47539719734413671edf9b1a31f8673fbf9688549f50c41affabbcb1c6b26"},
|
||||
{"name":"aws-sdk-core","version":"3.178.0","platform":"ruby","checksum":"192485a032536ff8c8eb037f1204b432129a612f4de13e36c0d2cf0dec8165cb"},
|
||||
{"name":"aws-sdk-kms","version":"1.64.0","platform":"ruby","checksum":"40de596c95047bfc6e1aacea24f3df6241aa716b6f7ce08ac4c5f7e3120395ad"},
|
||||
{"name":"aws-sdk-s3","version":"1.128.0","platform":"ruby","checksum":"5b1420d5be9654a9b1b5c8309d75ce72592f3a1e29def15ea07a853b96999d85"},
|
||||
{"name":"aws-sdk-s3","version":"1.129.0","platform":"ruby","checksum":"82b8eab53d22754e5855dbec3e7a9a53c348de2bbf202774b4483f9b06cb0f1a"},
|
||||
{"name":"aws-sigv4","version":"1.6.0","platform":"ruby","checksum":"ca9e6a15cd424f1f32b524b9760995331459bc22e67d3daad4fcf0c0084b087d"},
|
||||
{"name":"axe-core-api","version":"4.6.0","platform":"ruby","checksum":"1b0ddec3353f108dc10363baf2282f43a5ff7f13d4e25f99071294e78f8a6c62"},
|
||||
{"name":"axe-core-rspec","version":"4.6.0","platform":"ruby","checksum":"11c25bc9dd388c137ba4e5e63d64d20092bf22c884d8ffc829a22acfbacd747f"},
|
||||
|
|
|
|||
|
|
@ -262,7 +262,7 @@ GEM
|
|||
aws-sdk-kms (1.64.0)
|
||||
aws-sdk-core (~> 3, >= 3.165.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-s3 (1.128.0)
|
||||
aws-sdk-s3 (1.129.0)
|
||||
aws-sdk-core (~> 3, >= 3.177.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.6)
|
||||
|
|
@ -1740,7 +1740,7 @@ DEPENDENCIES
|
|||
awesome_print
|
||||
aws-sdk-cloudformation (~> 1)
|
||||
aws-sdk-core (~> 3.178.0)
|
||||
aws-sdk-s3 (~> 1.128.0)
|
||||
aws-sdk-s3 (~> 1.129.0)
|
||||
axe-core-rspec
|
||||
babosa (~> 2.0)
|
||||
base32 (~> 0.3.0)
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
import $ from 'jquery';
|
||||
import Vue from 'vue';
|
||||
import initDatePicker from '~/behaviors/date_picker';
|
||||
import GLForm from '~/gl_form';
|
||||
import { BV_SHOW_MODAL } from '~/lib/utils/constants';
|
||||
import Milestone from '~/milestones/milestone';
|
||||
import { renderGFM } from '~/behaviors/markdown/render_gfm';
|
||||
import { mountMarkdownEditor } from '~/vue_shared/components/markdown/mount_markdown_editor';
|
||||
import Sidebar from '~/right_sidebar';
|
||||
import MountMilestoneSidebar from '~/sidebar/mount_milestone_sidebar';
|
||||
import Translate from '~/vue_shared/translate';
|
||||
|
|
@ -22,22 +21,10 @@ export const MILESTONE_DESCRIPTION_ELEMENT = '.milestone-detail .description';
|
|||
export const MILESTONE_DESCRIPTION_TASK_LIST_CONTAINER_ELEMENT = `${MILESTONE_DESCRIPTION_ELEMENT}.js-task-list-container`;
|
||||
export const MILESTONE_DETAIL_ELEMENT = '.milestone-detail';
|
||||
|
||||
export function initForm(initGFM = true) {
|
||||
export function initForm() {
|
||||
mountMarkdownEditor();
|
||||
new ZenMode(); // eslint-disable-line no-new
|
||||
initDatePicker();
|
||||
|
||||
// eslint-disable-next-line no-new
|
||||
new GLForm($('.milestone-form'), {
|
||||
emojis: true,
|
||||
members: initGFM,
|
||||
issues: initGFM,
|
||||
mergeRequests: initGFM,
|
||||
epics: initGFM,
|
||||
milestones: initGFM,
|
||||
labels: initGFM,
|
||||
snippets: initGFM,
|
||||
vulnerabilities: initGFM,
|
||||
});
|
||||
}
|
||||
|
||||
export function initShow() {
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ export default {
|
|||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
},
|
||||
inject: ['isGroupPage'],
|
||||
inject: ['isGroupPage', 'canDeletePackages'],
|
||||
props: {
|
||||
packageEntity: {
|
||||
type: Object,
|
||||
|
|
@ -122,7 +122,7 @@ export default {
|
|||
<list-item data-testid="package-row" :selected="selected" v-bind="$attrs">
|
||||
<template #left-action>
|
||||
<gl-form-checkbox
|
||||
v-if="packageEntity.canDestroy"
|
||||
v-if="canDeletePackages"
|
||||
class="gl-m-0"
|
||||
:checked="selected"
|
||||
@change="$emit('select')"
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ export default {
|
|||
RegistryList,
|
||||
},
|
||||
mixins: [Tracking.mixin()],
|
||||
inject: ['canDeletePackages'],
|
||||
props: {
|
||||
list: {
|
||||
type: Array,
|
||||
|
|
@ -175,6 +176,7 @@ export default {
|
|||
>
|
||||
<registry-list
|
||||
data-testid="packages-table"
|
||||
:hidden-delete="!canDeletePackages"
|
||||
:is-loading="isLoading"
|
||||
:items="list"
|
||||
:pagination="pageInfo"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import Vue from 'vue';
|
||||
import Translate from '~/vue_shared/translate';
|
||||
import { parseBoolean } from '~/lib/utils/common_utils';
|
||||
import { apolloProvider } from '~/packages_and_registries/package_registry/graphql/index';
|
||||
import PackageRegistry from '~/packages_and_registries/package_registry/pages/index.vue';
|
||||
import RegistryBreadcrumb from '~/packages_and_registries/shared/components/registry_breadcrumb.vue';
|
||||
|
|
@ -20,6 +21,7 @@ export default () => {
|
|||
projectListUrl,
|
||||
groupListUrl,
|
||||
settingsPath,
|
||||
canDeletePackages,
|
||||
} = el.dataset;
|
||||
|
||||
const isGroupPage = pageType === 'groups';
|
||||
|
|
@ -50,6 +52,7 @@ export default () => {
|
|||
groupListUrl,
|
||||
breadCrumbState,
|
||||
settingsPath,
|
||||
canDeletePackages: parseBoolean(canDeletePackages),
|
||||
},
|
||||
render(createElement) {
|
||||
return createElement(PackageRegistry);
|
||||
|
|
|
|||
|
|
@ -78,6 +78,7 @@ export default {
|
|||
</gl-alert>
|
||||
|
||||
<packages-settings
|
||||
class="settings-section-no-bottom"
|
||||
:package-settings="packageSettings"
|
||||
:is-loading="isLoading"
|
||||
@success="handleSuccess(2)"
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ export default {
|
|||
<p v-if="value.nextRunAt" data-testid="next-run-at">
|
||||
{{ nextCleanupMessage }}
|
||||
</p>
|
||||
<div class="gl-mt-7 gl-display-flex gl-align-items-center">
|
||||
<div class="gl-mt-6 gl-display-flex gl-align-items-center">
|
||||
<gl-button
|
||||
data-testid="save-button"
|
||||
type="submit"
|
||||
|
|
|
|||
|
|
@ -1,17 +1,15 @@
|
|||
<template>
|
||||
<section class="settings gl-py-7">
|
||||
<div class="row">
|
||||
<div class="col-lg-4">
|
||||
<h4>
|
||||
<section class="settings-section">
|
||||
<div class="settings-sticky-header">
|
||||
<div class="settings-sticky-header-inner">
|
||||
<h4 class="gl-my-0">
|
||||
<slot name="title"></slot>
|
||||
</h4>
|
||||
<p>
|
||||
<slot name="description"></slot>
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-lg-8 gl-pt-3">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
<p class="gl-text-secondary">
|
||||
<slot name="description"></slot>
|
||||
</p>
|
||||
<slot></slot>
|
||||
</section>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -79,6 +79,7 @@ export function mountMarkdownEditor(options = {}) {
|
|||
const supportsQuickActions = parseBoolean(el.dataset.supportsQuickActions ?? true);
|
||||
const enableAutocomplete = parseBoolean(el.dataset.enableAutocomplete ?? true);
|
||||
const disableAttachments = parseBoolean(el.dataset.disableAttachments ?? false);
|
||||
const autofocus = parseBoolean(el.dataset.autofocus ?? true);
|
||||
const hiddenInput = el.querySelector('input[type="hidden"]');
|
||||
const formFieldName = hiddenInput.getAttribute('name');
|
||||
const formFieldId = hiddenInput.getAttribute('id');
|
||||
|
|
@ -128,7 +129,7 @@ export function mountMarkdownEditor(options = {}) {
|
|||
autocompleteDataSources: gl.GfmAutoComplete?.dataSources,
|
||||
supportsQuickActions,
|
||||
disableAttachments,
|
||||
autofocus: true,
|
||||
autofocus,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
|
|
|||
|
|
@ -76,6 +76,33 @@
|
|||
}
|
||||
}
|
||||
|
||||
.settings-section {
|
||||
@include gl-pt-6;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
display: block;
|
||||
@include gl-pb-7;
|
||||
}
|
||||
}
|
||||
|
||||
.settings-section:first-of-type,
|
||||
.settings-section-no-bottom + .settings-section {
|
||||
@include gl-pt-0;
|
||||
}
|
||||
|
||||
.settings-section:not(.settings-section-no-bottom) + .settings-section {
|
||||
@include gl-border-t;
|
||||
}
|
||||
|
||||
.settings-section-no-bottom::after {
|
||||
@include gl-pb-0;
|
||||
|
||||
@include media-breakpoint-up(sm) {
|
||||
@include gl-pb-5;
|
||||
}
|
||||
}
|
||||
|
||||
$sticky-header-z-index: 98;
|
||||
|
||||
.settings-sticky-header,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,10 @@ class Groups::MilestonesController < Groups::ApplicationController
|
|||
feature_category :team_planning
|
||||
urgency :low
|
||||
|
||||
before_action do
|
||||
push_frontend_feature_flag(:content_editor_on_issues, group)
|
||||
end
|
||||
|
||||
def index
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
|
|
|
|||
|
|
@ -24,6 +24,10 @@ class Projects::MilestonesController < Projects::ApplicationController
|
|||
feature_category :team_planning
|
||||
urgency :low
|
||||
|
||||
before_action do
|
||||
push_frontend_feature_flag(:content_editor_on_issues, @project)
|
||||
end
|
||||
|
||||
def index
|
||||
@sort = params[:sort] || 'due_date_asc'
|
||||
@milestones = milestones.sort_by_attribute(@sort)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,84 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Projects
|
||||
module ServiceDesk
|
||||
class CustomEmailController < Projects::ApplicationController
|
||||
before_action :check_feature_flag_enabled
|
||||
before_action :authorize_admin_project!
|
||||
|
||||
feature_category :service_desk
|
||||
urgency :low
|
||||
|
||||
def create
|
||||
response = ::ServiceDesk::CustomEmails::CreateService.new(
|
||||
project: project,
|
||||
current_user: current_user,
|
||||
params: params
|
||||
).execute
|
||||
|
||||
json_response(service_response: response)
|
||||
end
|
||||
|
||||
def update
|
||||
response = ServiceDeskSettings::UpdateService.new(project, current_user, update_setting_params).execute
|
||||
|
||||
if response.error?
|
||||
json_response(
|
||||
error_message: s_("ServiceDesk|Cannot update custom email"),
|
||||
status: :unprocessable_entity
|
||||
)
|
||||
return
|
||||
end
|
||||
|
||||
json_response
|
||||
end
|
||||
|
||||
def destroy
|
||||
response = ::ServiceDesk::CustomEmails::DestroyService.new(
|
||||
project: project,
|
||||
current_user: current_user
|
||||
).execute
|
||||
|
||||
json_response(service_response: response)
|
||||
end
|
||||
|
||||
def show
|
||||
json_response
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def update_setting_params
|
||||
params.permit(:custom_email_enabled)
|
||||
end
|
||||
|
||||
def json_response(error_message: nil, status: :ok, service_response: nil)
|
||||
if service_response.present?
|
||||
status = service_response.success? ? :ok : :unprocessable_entity
|
||||
error_message = service_response.message
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.json { render json: custom_email_attributes(error_message: error_message), status: status }
|
||||
end
|
||||
end
|
||||
|
||||
def custom_email_attributes(error_message:)
|
||||
setting = project.service_desk_setting
|
||||
|
||||
{
|
||||
custom_email: setting&.custom_email,
|
||||
custom_email_enabled: setting&.custom_email_enabled || false,
|
||||
custom_email_verification_state: setting&.custom_email_verification&.state,
|
||||
custom_email_verification_error: setting&.custom_email_verification&.error,
|
||||
custom_email_smtp_address: setting&.custom_email_credential&.smtp_address,
|
||||
error_message: error_message
|
||||
}
|
||||
end
|
||||
|
||||
def check_feature_flag_enabled
|
||||
render_404 unless Feature.enabled?(:service_desk_custom_email, @project)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -74,6 +74,16 @@ module PackagesHelper
|
|||
Ability.allowed?(current_user, :admin_group, group)
|
||||
end
|
||||
|
||||
def can_delete_packages?(project)
|
||||
Gitlab.config.packages.enabled &&
|
||||
Ability.allowed?(current_user, :destroy_package, project)
|
||||
end
|
||||
|
||||
def can_delete_group_packages?(group)
|
||||
group.packages_feature_enabled? &&
|
||||
Ability.allowed?(current_user, :destroy_package, group)
|
||||
end
|
||||
|
||||
def cleanup_settings_data
|
||||
{
|
||||
project_id: @project.id,
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ module Ml
|
|||
|
||||
belongs_to :project
|
||||
belongs_to :user
|
||||
belongs_to :model, optional: true, inverse_of: :default_experiment
|
||||
has_many :candidates, class_name: 'Ml::Candidate'
|
||||
has_many :metadata, class_name: 'Ml::ExperimentMetadata'
|
||||
|
||||
|
|
@ -22,10 +23,21 @@ module Ml
|
|||
|
||||
has_internal_id :iid, scope: :project
|
||||
|
||||
before_destroy :stop_destroy
|
||||
|
||||
def package_name
|
||||
"#{PACKAGE_PREFIX}#{iid}"
|
||||
end
|
||||
|
||||
def stop_destroy
|
||||
return unless model_id
|
||||
|
||||
errors[:base] << "Cannot delete an experiment associated to a model"
|
||||
# According to docs, throw is the correct way to stop on a callback
|
||||
# https://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html#module-ActiveRecord::Callbacks-label-Canceling+callbacks
|
||||
throw :abort # rubocop:disable Cop/BanCatchThrow
|
||||
end
|
||||
|
||||
class << self
|
||||
def by_project_id_and_iid(project_id, iid)
|
||||
find_by(project_id: project_id, iid: iid)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Ml
|
||||
class Model < ApplicationRecord
|
||||
validates :project, :default_experiment, presence: true
|
||||
validates :name,
|
||||
format: Gitlab::Regex.ml_model_name_regex,
|
||||
uniqueness: { scope: :project },
|
||||
presence: true,
|
||||
length: { maximum: 255 }
|
||||
|
||||
validate :valid_default_experiment?
|
||||
|
||||
has_one :default_experiment, class_name: 'Ml::Experiment'
|
||||
belongs_to :project
|
||||
|
||||
def valid_default_experiment?
|
||||
return unless default_experiment
|
||||
|
||||
errors.add(:default_experiment) unless default_experiment.name == name
|
||||
errors.add(:default_experiment) unless default_experiment.project_id == project_id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -10,12 +10,16 @@
|
|||
= render "shared/milestones/form_dates", f: f
|
||||
.form-group
|
||||
= f.label :description, _("Description")
|
||||
= render layout: 'shared/md_preview', locals: { url: group_preview_markdown_path } do
|
||||
= render 'shared/zen', f: f, attr: :description,
|
||||
classes: 'note-textarea',
|
||||
qa_selector: 'milestone_description_field',
|
||||
supports_autocomplete: true,
|
||||
placeholder: _('Write milestone description...')
|
||||
- @gfm_form = true
|
||||
.js-markdown-editor{ data: { render_markdown_path: group_preview_markdown_path,
|
||||
markdown_docs_path: help_page_path('user/markdown'),
|
||||
qa_selector: 'milestone_description_field',
|
||||
form_field_placeholder: _('Write milestone description...'),
|
||||
supports_quick_actions: 'false',
|
||||
enable_autocomplete: 'true',
|
||||
autofocus: 'false',
|
||||
form_field_classes: 'note-textarea js-gfm-input markdown-area' } }
|
||||
= f.hidden_field :description
|
||||
.clearfix
|
||||
.error-alert
|
||||
|
||||
|
|
|
|||
|
|
@ -10,4 +10,5 @@
|
|||
npm_instance_url: package_registry_instance_url(:npm),
|
||||
project_list_url: '',
|
||||
settings_path: show_group_package_registry_settings(@group) ? group_settings_packages_and_registries_path(@group) : '',
|
||||
can_delete_packages: can_delete_group_packages?(@group).to_s,
|
||||
group_list_url: group_packages_path(@group) } }
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
.js-user-profile
|
||||
- else
|
||||
= gitlab_ui_form_for @user, url: profile_path, method: :put, html: { multipart: true, class: 'edit-user js-edit-user js-quick-submit gl-show-field-errors js-password-prompt-form', remote: true }, authenticity_token: true do |f|
|
||||
.js-search-settings-section
|
||||
.settings-section.js-search-settings-section
|
||||
.settings-sticky-header
|
||||
.settings-sticky-header-inner
|
||||
%h4.gl-my-0
|
||||
|
|
@ -44,9 +44,8 @@
|
|||
button_options: { class: 'gl-mt-3', data: { confirm: s_("Profiles|Avatar will be removed. Are you sure?") } },
|
||||
method: :delete) do
|
||||
= s_("Profiles|Remove avatar")
|
||||
.gl-pb-8
|
||||
|
||||
.js-search-settings-section.gl-border-t.gl-pt-6
|
||||
.settings-section.js-search-settings-section.gl-border-t.gl-pt-6
|
||||
.settings-sticky-header
|
||||
.settings-sticky-header-inner
|
||||
%h4.gl-my-0= s_("Profiles|Current status")
|
||||
|
|
@ -60,18 +59,16 @@
|
|||
= status_form.hidden_field :clear_status_after,
|
||||
value: user_clear_status_at(@user),
|
||||
data: { js_name: 'clearStatusAfter' }
|
||||
.gl-pb-7
|
||||
|
||||
.user-time-preferences.js-search-settings-section.gl-border-t.gl-pt-6
|
||||
.settings-section.user-time-preferences.js-search-settings-section.gl-border-t.gl-pt-6
|
||||
.settings-sticky-header
|
||||
.settings-sticky-header-inner
|
||||
%h4.gl-my-0= s_("Profiles|Time settings")
|
||||
%p.gl-text-secondary= s_("Profiles|Set your local time zone.")
|
||||
= f.label :user_timezone, _("Time zone")
|
||||
.js-timezone-dropdown{ data: { timezone_data: timezone_data.to_json, initial_value: @user.timezone, name: 'user[timezone]' } }
|
||||
.gl-pb-7
|
||||
|
||||
.js-search-settings-section.gl-border-t.gl-pt-6
|
||||
.settings-section.js-search-settings-section.gl-border-t.gl-pt-6
|
||||
.settings-sticky-header
|
||||
.settings-sticky-header-inner
|
||||
%h4.gl-my-0
|
||||
|
|
@ -168,7 +165,6 @@
|
|||
= s_("Profiles|Achievements")
|
||||
= f.gitlab_ui_checkbox_component :achievements_enabled,
|
||||
s_('Profiles|Display achievements on your profile')
|
||||
.gl-pb-7
|
||||
|
||||
.js-hide-when-nothing-matches-search.settings-sticky-footer
|
||||
= f.submit s_("Profiles|Update profile settings"), class: 'gl-mr-3 js-password-prompt-btn', pajamas_button: true
|
||||
|
|
|
|||
|
|
@ -12,13 +12,16 @@
|
|||
= render 'shared/milestones/form_dates', f: f
|
||||
.form-group
|
||||
= f.label :description, _('Description')
|
||||
= render layout: 'shared/md_preview', locals: { url: preview_markdown_path(@project) } do
|
||||
= render 'shared/zen', f: f, attr: :description,
|
||||
classes: 'note-textarea',
|
||||
qa_selector: 'milestone_description_field',
|
||||
supports_autocomplete: true,
|
||||
placeholder: _('Write milestone description...')
|
||||
= render 'shared/notes/hints'
|
||||
- @gfm_form = true
|
||||
.js-markdown-editor{ data: { render_markdown_path: preview_markdown_path(@project),
|
||||
markdown_docs_path: help_page_path('user/markdown'),
|
||||
qa_selector: 'milestone_description_field',
|
||||
form_field_placeholder: _('Write milestone description...'),
|
||||
supports_quick_actions: 'false',
|
||||
enable_autocomplete: 'true',
|
||||
autofocus: 'false',
|
||||
form_field_classes: 'note-textarea js-gfm-input markdown-area' } }
|
||||
= f.hidden_field :description
|
||||
.clearfix
|
||||
.error-alert
|
||||
|
||||
|
|
|
|||
|
|
@ -10,4 +10,5 @@
|
|||
npm_instance_url: package_registry_instance_url(:npm),
|
||||
project_list_url: project_packages_path(@project),
|
||||
settings_path: show_package_registry_settings(@project) ? project_settings_packages_and_registries_path(@project) : '',
|
||||
can_delete_packages: can_delete_packages?(@project).to_s,
|
||||
group_list_url: '' } }
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
name: load_merge_request_via_links
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/117321
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/412177
|
||||
milestone: '16.1'
|
||||
type: development
|
||||
group: group::threat insights
|
||||
default_enabled: false
|
||||
|
|
@ -489,6 +489,10 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
|
|||
resources :candidates, only: [:show, :destroy], controller: 'candidates', param: :iid
|
||||
resources :models, only: [:index], controller: 'models'
|
||||
end
|
||||
|
||||
namespace :service_desk do
|
||||
resource :custom_email, only: [:show, :create, :update, :destroy], controller: 'custom_email'
|
||||
end
|
||||
end
|
||||
# End of the /-/ scope.
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
table_name: ml_models
|
||||
classes:
|
||||
- Ml::Model
|
||||
feature_categories:
|
||||
- mlops
|
||||
description: A machine learning model for the model registry
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/125302
|
||||
milestone: '16.2'
|
||||
gitlab_schema: gitlab_main
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CreateMlModels < Gitlab::Database::Migration[2.1]
|
||||
enable_lock_retries!
|
||||
|
||||
def up
|
||||
create_table :ml_models do |t|
|
||||
t.timestamps_with_timezone null: false
|
||||
t.references :project, foreign_key: true, index: true, on_delete: :cascade, null: false
|
||||
t.text :name, limit: 255, null: false
|
||||
|
||||
t.index [:project_id, :name], unique: true
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
drop_table :ml_models
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddColumnModelIdToMlExperiments < Gitlab::Database::Migration[2.1]
|
||||
def change
|
||||
# rubocop:disable Migration/AddReference
|
||||
add_reference :ml_experiments,
|
||||
:model,
|
||||
index: true,
|
||||
null: true,
|
||||
unique: true,
|
||||
foreign_key: { on_delete: :cascade, to_table: :ml_models }
|
||||
# rubocop:enable Migration/AddReference
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
5954829dd244b4536beeb9b92a157539feb5207bc1309e177310895f269f54d8
|
||||
|
|
@ -0,0 +1 @@
|
|||
3a355ebb2299786d9aa5ce9bf1f07b2bbd3b6aca719c12c78c4a945c4a96cfe3
|
||||
|
|
@ -18592,6 +18592,7 @@ CREATE TABLE ml_experiments (
|
|||
user_id bigint,
|
||||
name text NOT NULL,
|
||||
deleted_on timestamp with time zone,
|
||||
model_id bigint,
|
||||
CONSTRAINT check_ee07a0be2c CHECK ((char_length(name) <= 255))
|
||||
);
|
||||
|
||||
|
|
@ -18604,6 +18605,24 @@ CREATE SEQUENCE ml_experiments_id_seq
|
|||
|
||||
ALTER SEQUENCE ml_experiments_id_seq OWNED BY ml_experiments.id;
|
||||
|
||||
CREATE TABLE ml_models (
|
||||
id bigint NOT NULL,
|
||||
created_at timestamp with time zone NOT NULL,
|
||||
updated_at timestamp with time zone NOT NULL,
|
||||
project_id bigint NOT NULL,
|
||||
name text NOT NULL,
|
||||
CONSTRAINT check_1fd2cc7d93 CHECK ((char_length(name) <= 255))
|
||||
);
|
||||
|
||||
CREATE SEQUENCE ml_models_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
ALTER SEQUENCE ml_models_id_seq OWNED BY ml_models.id;
|
||||
|
||||
CREATE TABLE namespace_admin_notes (
|
||||
id bigint NOT NULL,
|
||||
created_at timestamp with time zone NOT NULL,
|
||||
|
|
@ -25629,6 +25648,8 @@ ALTER TABLE ONLY ml_experiment_metadata ALTER COLUMN id SET DEFAULT nextval('ml_
|
|||
|
||||
ALTER TABLE ONLY ml_experiments ALTER COLUMN id SET DEFAULT nextval('ml_experiments_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY ml_models ALTER COLUMN id SET DEFAULT nextval('ml_models_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY namespace_admin_notes ALTER COLUMN id SET DEFAULT nextval('namespace_admin_notes_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY namespace_bans ALTER COLUMN id SET DEFAULT nextval('namespace_bans_id_seq'::regclass);
|
||||
|
|
@ -27848,6 +27869,9 @@ ALTER TABLE ONLY ml_experiment_metadata
|
|||
ALTER TABLE ONLY ml_experiments
|
||||
ADD CONSTRAINT ml_experiments_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY ml_models
|
||||
ADD CONSTRAINT ml_models_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY namespace_admin_notes
|
||||
ADD CONSTRAINT namespace_admin_notes_pkey PRIMARY KEY (id);
|
||||
|
||||
|
|
@ -31974,12 +31998,18 @@ CREATE INDEX index_ml_candidates_on_user_id ON ml_candidates USING btree (user_i
|
|||
|
||||
CREATE UNIQUE INDEX index_ml_experiment_metadata_on_experiment_id_and_name ON ml_experiment_metadata USING btree (experiment_id, name);
|
||||
|
||||
CREATE INDEX index_ml_experiments_on_model_id ON ml_experiments USING btree (model_id);
|
||||
|
||||
CREATE UNIQUE INDEX index_ml_experiments_on_project_id_and_iid ON ml_experiments USING btree (project_id, iid);
|
||||
|
||||
CREATE UNIQUE INDEX index_ml_experiments_on_project_id_and_name ON ml_experiments USING btree (project_id, name);
|
||||
|
||||
CREATE INDEX index_ml_experiments_on_user_id ON ml_experiments USING btree (user_id);
|
||||
|
||||
CREATE INDEX index_ml_models_on_project_id ON ml_models USING btree (project_id);
|
||||
|
||||
CREATE UNIQUE INDEX index_ml_models_on_project_id_and_name ON ml_models USING btree (project_id, name);
|
||||
|
||||
CREATE UNIQUE INDEX index_mr_blocks_on_blocking_and_blocked_mr_ids ON merge_request_blocks USING btree (blocking_merge_request_id, blocked_merge_request_id);
|
||||
|
||||
CREATE INDEX index_mr_cleanup_schedules_timestamps_status ON merge_request_cleanup_schedules USING btree (scheduled_at) WHERE ((completed_at IS NULL) AND (status = 0));
|
||||
|
|
@ -36969,6 +36999,9 @@ ALTER TABLE ONLY project_repository_storage_moves
|
|||
ALTER TABLE ONLY ml_candidate_metadata
|
||||
ADD CONSTRAINT fk_rails_5117dddf22 FOREIGN KEY (candidate_id) REFERENCES ml_candidates(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY ml_models
|
||||
ADD CONSTRAINT fk_rails_51e87f7c50 FOREIGN KEY (project_id) REFERENCES projects(id);
|
||||
|
||||
ALTER TABLE ONLY elastic_group_index_statuses
|
||||
ADD CONSTRAINT fk_rails_52b9969b12 FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE;
|
||||
|
||||
|
|
@ -37428,6 +37461,9 @@ ALTER TABLE ONLY boards_epic_board_recent_visits
|
|||
ALTER TABLE ONLY packages_dependency_links
|
||||
ADD CONSTRAINT fk_rails_96ef1c00d3 FOREIGN KEY (package_id) REFERENCES packages_packages(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY ml_experiments
|
||||
ADD CONSTRAINT fk_rails_97194a054e FOREIGN KEY (model_id) REFERENCES ml_models(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY group_repository_storage_moves
|
||||
ADD CONSTRAINT fk_rails_982bb5daf1 FOREIGN KEY (group_id) REFERENCES namespaces(id) ON DELETE CASCADE;
|
||||
|
||||
|
|
|
|||
|
|
@ -60,8 +60,8 @@ concepts to look at as an example.
|
|||
|
||||
App Continuum:
|
||||
|
||||
An illustration of how an application can evolve from a small, unstructed app, through various
|
||||
stages including a modular well-structured, monolith, all the way to a microservices architecture.
|
||||
An illustration of how an application can evolve from a small, unstructured app, through various
|
||||
stages including a modular well-structured monolith, all the way to a microservices architecture.
|
||||
|
||||
Includes discussion of why you might want to stop at various stages, and specifically the
|
||||
challenges/concerns with making the jump to microservices, and why sticking with a
|
||||
|
|
|
|||
|
|
@ -13,6 +13,12 @@ to AWS. You can reference these images in your CI/CD pipeline.
|
|||
If you're using GitLab.com and deploying to the [Amazon Elastic Container Service](https://aws.amazon.com/ecs/) (ECS),
|
||||
read about [deploying to ECS](ecs/deploy_to_aws_ecs.md).
|
||||
|
||||
NOTE:
|
||||
If you are comfortable configuring a deployment yourself and just need to retrieve
|
||||
AWS credentials, consider using [ID tokens and OpenID Connect](../cloud_services/aws/index.md).
|
||||
ID tokens are more secure than storing credentials in CI/CD variables, but do not
|
||||
work with the guidance on this page.
|
||||
|
||||
## Authenticate GitLab with AWS
|
||||
|
||||
To use GitLab CI/CD to connect to AWS, you must authenticate.
|
||||
|
|
|
|||
|
|
@ -55,18 +55,16 @@ RSpec.describe "with feature flag enabled", feature_flag: {
|
|||
|
||||
let(:project) { Resource::Project.fabricate_via_api! }
|
||||
|
||||
before do
|
||||
around do |example|
|
||||
Runtime::Feature.enable(:feature_flag_name, project: project)
|
||||
example.run
|
||||
Runtime::Feature.disable(:feature_flag_name, project: project)
|
||||
end
|
||||
|
||||
it "feature flag test" do
|
||||
# Execute the test with the feature flag enabled.
|
||||
# It will only affect the project created in this test.
|
||||
end
|
||||
|
||||
after do
|
||||
Runtime::Feature.disable(:feature_flag_name, project: project)
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -108,3 +108,12 @@ Check [`production.log`](../../administration/logs/index.md#productionlog) to se
|
|||
```
|
||||
|
||||
If that's the case, ensure the **Due date** field is visible for issues in the integrated Jira project.
|
||||
|
||||
## `An error occurred while requesting data from Jira` when viewing the Jira issues list in GitLab
|
||||
|
||||
You might see a `An error occurred while requesting data from Jira` message when you attempt to view the Jira issues list in GitLab.
|
||||
|
||||
You can see this error when the authentication details in the Jira integration settings are incomplete or incorrect.
|
||||
|
||||
To attempt to resolve this error, try [configuring the integration](configure.md#configure-the-integration) again. Verify that the
|
||||
authentication details are correct, re-enter your API token or password, and save your changes.
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ inherit_mode:
|
|||
AllCops:
|
||||
# Target the current Ruby version. For example, "3.0" or "3.1".
|
||||
TargetRubyVersion: <%= RUBY_VERSION[/^\d+\.\d+/, 0] %>
|
||||
SuggestExtensions: false
|
||||
|
||||
# This cop doesn't make sense in the context of gems
|
||||
CodeReuse/ActiveRecord:
|
||||
|
|
@ -59,6 +60,18 @@ Naming/FileName:
|
|||
Exclude:
|
||||
- spec/**/*.rb
|
||||
|
||||
RSpec/ContextWording:
|
||||
Prefixes:
|
||||
- 'when'
|
||||
- 'with'
|
||||
- 'without'
|
||||
- 'for'
|
||||
- 'and'
|
||||
- 'on'
|
||||
- 'in'
|
||||
- 'as'
|
||||
- 'if'
|
||||
|
||||
# This cop doesn't make sense in the context of gems
|
||||
RSpec/MissingFeatureCategory:
|
||||
Enabled: false
|
||||
|
|
|
|||
|
|
@ -4,8 +4,25 @@ inherit_from:
|
|||
CodeReuse/ActiveRecord:
|
||||
Enabled: false
|
||||
|
||||
Gitlab/Json:
|
||||
Enabled: false
|
||||
|
||||
# FIXME
|
||||
Gitlab/RSpec/AvoidSetup:
|
||||
Enabled: false
|
||||
|
||||
Naming/FileName:
|
||||
Exclude:
|
||||
- spec/**/*.rb
|
||||
- lib/gitlab/rspec.rb
|
||||
- lib/gitlab/rspec/all.rb
|
||||
|
||||
Rails/Pluck:
|
||||
Enabled: false
|
||||
|
||||
RSpec/AvoidConditionalStatements:
|
||||
Enabled: false
|
||||
|
||||
RSpec/MultipleMemoizedHelpers:
|
||||
Max: 6
|
||||
AllowSubject: true
|
||||
|
|
|
|||
|
|
@ -1,26 +1,46 @@
|
|||
PATH
|
||||
remote: .
|
||||
specs:
|
||||
gitlab-ipynbdiff (0.4.7)
|
||||
ipynbdiff (0.4.7)
|
||||
diffy (~> 3.4)
|
||||
oj (~> 3.13.16)
|
||||
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
activesupport (7.0.6)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 1.6, < 2)
|
||||
minitest (>= 5.1)
|
||||
tzinfo (~> 2.0)
|
||||
ast (2.4.2)
|
||||
benchmark-memory (0.2.0)
|
||||
memory_profiler (~> 1)
|
||||
binding_ninja (0.2.3)
|
||||
binding_of_caller (1.0.0)
|
||||
debug_inspector (>= 0.0.1)
|
||||
coderay (1.1.3)
|
||||
concurrent-ruby (1.2.2)
|
||||
debug_inspector (1.1.0)
|
||||
diff-lcs (1.5.0)
|
||||
diffy (3.4.2)
|
||||
docile (1.4.0)
|
||||
gitlab-styles (10.1.0)
|
||||
rubocop (~> 1.50.2)
|
||||
rubocop-graphql (~> 0.18)
|
||||
rubocop-performance (~> 1.15)
|
||||
rubocop-rails (~> 2.17)
|
||||
rubocop-rspec (~> 2.22)
|
||||
i18n (1.14.1)
|
||||
concurrent-ruby (~> 1.0)
|
||||
json (2.6.3)
|
||||
memory_profiler (1.0.0)
|
||||
method_source (1.0.0)
|
||||
minitest (5.18.1)
|
||||
oj (3.13.23)
|
||||
parser (3.1.2.0)
|
||||
parallel (1.23.0)
|
||||
parser (3.2.2.3)
|
||||
ast (~> 2.4.1)
|
||||
racc
|
||||
proc_to_ast (0.1.0)
|
||||
coderay
|
||||
parser
|
||||
|
|
@ -28,7 +48,12 @@ GEM
|
|||
pry (0.14.1)
|
||||
coderay (~> 1.1)
|
||||
method_source (~> 1.0)
|
||||
racc (1.7.1)
|
||||
rack (3.0.8)
|
||||
rainbow (3.1.1)
|
||||
rake (13.0.6)
|
||||
regexp_parser (2.8.1)
|
||||
rexml (3.2.5)
|
||||
rspec (3.11.0)
|
||||
rspec-core (~> 3.11.0)
|
||||
rspec-expectations (~> 3.11.0)
|
||||
|
|
@ -41,22 +66,60 @@ GEM
|
|||
rspec-mocks (3.11.1)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.11.0)
|
||||
rspec-parameterized (0.5.2)
|
||||
binding_ninja (>= 0.2.3)
|
||||
rspec-parameterized (1.0.0)
|
||||
rspec-parameterized-core (< 2)
|
||||
rspec-parameterized-table_syntax (< 2)
|
||||
rspec-parameterized-core (1.0.0)
|
||||
parser
|
||||
proc_to_ast
|
||||
rspec (>= 2.13, < 4)
|
||||
unparser
|
||||
rspec-parameterized-table_syntax (1.0.0)
|
||||
binding_of_caller
|
||||
rspec-parameterized-core (< 2)
|
||||
rspec-support (3.11.0)
|
||||
rubocop (1.50.2)
|
||||
json (~> 2.3)
|
||||
parallel (~> 1.10)
|
||||
parser (>= 3.2.0.0)
|
||||
rainbow (>= 2.2.2, < 4.0)
|
||||
regexp_parser (>= 1.8, < 3.0)
|
||||
rexml (>= 3.2.5, < 4.0)
|
||||
rubocop-ast (>= 1.28.0, < 2.0)
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (>= 2.4.0, < 3.0)
|
||||
rubocop-ast (1.29.0)
|
||||
parser (>= 3.2.1.0)
|
||||
rubocop-capybara (2.18.0)
|
||||
rubocop (~> 1.41)
|
||||
rubocop-factory_bot (2.23.1)
|
||||
rubocop (~> 1.33)
|
||||
rubocop-graphql (0.19.0)
|
||||
rubocop (>= 0.87, < 2)
|
||||
rubocop-performance (1.18.0)
|
||||
rubocop (>= 1.7.0, < 2.0)
|
||||
rubocop-ast (>= 0.4.0)
|
||||
rubocop-rails (2.20.2)
|
||||
activesupport (>= 4.2.0)
|
||||
rack (>= 1.1)
|
||||
rubocop (>= 1.33.0, < 2.0)
|
||||
rubocop-rspec (2.22.0)
|
||||
rubocop (~> 1.33)
|
||||
rubocop-capybara (~> 2.17)
|
||||
rubocop-factory_bot (~> 2.22)
|
||||
ruby-progressbar (1.13.0)
|
||||
simplecov (0.22.0)
|
||||
docile (~> 1.1)
|
||||
simplecov-html (~> 0.11)
|
||||
simplecov_json_formatter (~> 0.1)
|
||||
simplecov-html (0.12.3)
|
||||
simplecov_json_formatter (0.1.4)
|
||||
unparser (0.6.5)
|
||||
tzinfo (2.0.6)
|
||||
concurrent-ruby (~> 1.0)
|
||||
unicode-display_width (2.4.2)
|
||||
unparser (0.6.8)
|
||||
diff-lcs (~> 1.3)
|
||||
parser (>= 3.1.0)
|
||||
parser (>= 3.2.0)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
|
@ -64,12 +127,13 @@ PLATFORMS
|
|||
DEPENDENCIES
|
||||
benchmark-memory (~> 0.2.0)
|
||||
bundler (~> 2.2)
|
||||
gitlab-ipynbdiff!
|
||||
gitlab-styles (~> 10.1.0)
|
||||
ipynbdiff!
|
||||
pry (~> 0.14)
|
||||
rake (~> 13.0)
|
||||
rspec (~> 3.10)
|
||||
rspec-parameterized (~> 0.5.1)
|
||||
simplecov
|
||||
rspec-parameterized (~> 1.0)
|
||||
simplecov (~> 0.22.0)
|
||||
|
||||
BUNDLED WITH
|
||||
2.3.16
|
||||
|
|
|
|||
|
|
@ -24,9 +24,10 @@ Gem::Specification.new do |s|
|
|||
|
||||
s.add_development_dependency 'benchmark-memory', '~>0.2.0'
|
||||
s.add_development_dependency 'bundler', '~> 2.2'
|
||||
s.add_development_dependency 'gitlab-styles', '~> 10.1.0'
|
||||
s.add_development_dependency 'pry', '~> 0.14'
|
||||
s.add_development_dependency 'rake', '~> 13.0'
|
||||
s.add_development_dependency 'rspec', '~> 3.10'
|
||||
s.add_development_dependency 'rspec-parameterized', '~> 0.5.1'
|
||||
s.add_development_dependency 'simplecov', '~> 0.12.0'
|
||||
s.add_development_dependency 'rspec-parameterized', '~> 1.0'
|
||||
s.add_development_dependency 'simplecov', '~> 0.22.0'
|
||||
end
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ describe IpynbDiff::SymbolMap do
|
|||
end
|
||||
|
||||
describe '.parse' do
|
||||
subject { described_class.parse(JSON.pretty_generate(source)) }
|
||||
subject(:symbol_map) { described_class.parse(JSON.pretty_generate(source)) }
|
||||
|
||||
context 'when object has blank key' do
|
||||
let(:source) { { "": { "": 5 } } }
|
||||
|
|
@ -37,8 +37,8 @@ describe IpynbDiff::SymbolMap do
|
|||
context 'when object has inner object and number, string and array with object' do
|
||||
let(:source) { { obj1: { obj2: [123, 2, true], obj3: "hel\nlo", obj4: true, obj5: 123, obj6: 'a' } } }
|
||||
|
||||
it do
|
||||
is_expected.to match_array(
|
||||
specify do
|
||||
expect(symbol_map).to match_array(
|
||||
res(['.obj1', 2],
|
||||
['.obj1.obj2', 3],
|
||||
['.obj1.obj2.0', 4],
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ require_relative 'test_helper'
|
|||
|
||||
describe IpynbDiff do
|
||||
def diff_signs(diff)
|
||||
diff.to_s(:text).scan(/.*\n/).map { |l| l[0] }.join('') # rubocop:disable Rails/Pluck
|
||||
diff.to_s(:text).scan(/.*\n/).map { |l| l[0] }.join('')
|
||||
end
|
||||
|
||||
describe '.diff' do
|
||||
|
|
@ -18,9 +18,7 @@ describe IpynbDiff do
|
|||
subject { described_class.diff(from, to, include_frontmatter: include_frontmatter, hide_images: hide_images) }
|
||||
|
||||
context 'if preprocessing is active' do
|
||||
it 'html tables are stripped' do
|
||||
is_expected.not_to include('<td>')
|
||||
end
|
||||
it { is_expected.not_to include('<td>') }
|
||||
end
|
||||
|
||||
context 'when to is nil' do
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ pre-push:
|
|||
tags: backend style
|
||||
files: git diff --name-only --diff-filter=d $(git merge-base origin/master HEAD)..HEAD
|
||||
glob: '*.{rb,rake}'
|
||||
run: REVEAL_RUBOCOP_TODO=0 bundle exec rubocop --parallel --force-exclusion {files}
|
||||
run: REVEAL_RUBOCOP_TODO=0 bundle exec rubocop --config .rubocop.yml --parallel --force-exclusion {files}
|
||||
sidekiq-queues:
|
||||
tags: backend
|
||||
files: git diff --name-only --diff-filter=d $(git merge-base origin/master HEAD)..HEAD
|
||||
|
|
@ -134,7 +134,7 @@ auto-fix:
|
|||
tags: backend style
|
||||
files: git diff --name-only --diff-filter=d $(git merge-base origin/master HEAD)..HEAD
|
||||
glob: '*.{rb,rake}'
|
||||
run: REVEAL_RUBOCOP_TODO=0 bundle exec rubocop --parallel --autocorrect --force-exclusion {files}
|
||||
run: REVEAL_RUBOCOP_TODO=0 bundle exec rubocop --config .rubocop.yml --parallel --autocorrect --force-exclusion {files}
|
||||
gettext:
|
||||
tags: backend frontend view haml
|
||||
files: git diff --name-only --diff-filter=d $(git merge-base origin/master HEAD)..HEAD | while read file;do git diff --unified=1 $(git merge-base origin/master HEAD)..HEAD $file | grep -Fqe '_(' && echo $file;done; true
|
||||
|
|
|
|||
|
|
@ -42370,6 +42370,9 @@ msgstr ""
|
|||
msgid "ServiceDesk|Cannot create custom email"
|
||||
msgstr ""
|
||||
|
||||
msgid "ServiceDesk|Cannot update custom email"
|
||||
msgstr ""
|
||||
|
||||
msgid "ServiceDesk|Custom email address could not be verified."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
FactoryBot.define do
|
||||
factory :ml_models, class: '::Ml::Model' do
|
||||
sequence(:name) { |n| "model#{n}" }
|
||||
|
||||
project
|
||||
default_experiment { association :ml_experiments, project_id: project.id, name: name }
|
||||
end
|
||||
end
|
||||
|
|
@ -27,7 +27,7 @@ RSpec.describe 'Group milestones', feature_category: :groups_and_projects do
|
|||
|
||||
click_button("Preview")
|
||||
|
||||
preview = find('.js-md-preview')
|
||||
preview = find('.js-vue-md-preview')
|
||||
|
||||
expect(preview).to have_content('Nothing to preview.')
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import MockAdapter from 'axios-mock-adapter';
|
||||
import $ from 'jquery';
|
||||
import htmlNewMilestone from 'test_fixtures/milestones/new-milestone.html';
|
||||
import mock from 'xhr-mock';
|
||||
import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
|
|
@ -9,6 +8,7 @@ import PasteMarkdownTable from '~/behaviors/markdown/paste_markdown_table';
|
|||
import dropzoneInput from '~/dropzone_input';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import { HTTP_STATUS_BAD_REQUEST, HTTP_STATUS_OK } from '~/lib/utils/http_status';
|
||||
import htmlNewMilestone from 'test_fixtures_static/textarea.html';
|
||||
|
||||
const TEST_FILE = new File([], 'somefile.jpg');
|
||||
TEST_FILE.upload = {};
|
||||
|
|
|
|||
|
|
@ -1,43 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Projects::MilestonesController, '(JavaScript fixtures)', :with_license, feature_category: :team_planning, type: :controller do
|
||||
include JavaScriptFixturesHelpers
|
||||
|
||||
let_it_be(:user) { create(:user, feed_token: 'feedtoken:coldfeed') }
|
||||
let_it_be(:namespace) { create(:namespace, name: 'frontend-fixtures') }
|
||||
let_it_be(:project) { create(:project_empty_repo, namespace: namespace, path: 'milestones-project') }
|
||||
|
||||
render_views
|
||||
|
||||
before do
|
||||
project.add_maintainer(user)
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
after do
|
||||
remove_repository(project)
|
||||
end
|
||||
|
||||
it 'milestones/new-milestone.html' do
|
||||
get :new, params: {
|
||||
namespace_id: project.namespace.to_param,
|
||||
project_id: project
|
||||
}
|
||||
|
||||
expect(response).to be_successful
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def render_milestone(milestone)
|
||||
get :show, params: {
|
||||
namespace_id: project.namespace.to_param,
|
||||
project_id: project,
|
||||
id: milestone.to_param
|
||||
}
|
||||
|
||||
expect(response).to be_successful
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
<body>
|
||||
<meta charset="utf-8">
|
||||
<title>Document with Textarea</title>
|
||||
<form class="milestone-form common-note-form js-quick-submit js-requires-input" id="new_milestone"
|
||||
action="http://test.host/frontend-fixtures/milestones-project/-/milestones"
|
||||
accept-charset="UTF-8" method="post">
|
||||
<div class="form-group">
|
||||
<div class="md-write-holder">
|
||||
<div class="zen-backdrop">
|
||||
<textarea class="note-textarea js-gfm-input js-autosize markdown-area"
|
||||
placeholder="Write milestone description..." dir="auto"
|
||||
data-supports-quick-actions="false" data-supports-autocomplete="true"
|
||||
data-qa-selector="milestone_description_field" data-autofocus="false"
|
||||
name="milestone[description]"
|
||||
id="milestone_description"></textarea>
|
||||
<a class="zen-control zen-control-leave js-zen-leave gl-text-gray-500"
|
||||
href="#">
|
||||
<svg class="s16" data-testid="minimize-icon">
|
||||
<use href="http://test.host/assets/icons-b8c5a9711f73b1de3c81754da0aca72f43b0e6844aa06dd03092b601a493f45b.svg#minimize"></use>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</body>
|
||||
|
|
@ -27,11 +27,11 @@ describe('packages_list_row', () => {
|
|||
|
||||
const defaultProvide = {
|
||||
isGroupPage: false,
|
||||
canDeletePackages: true,
|
||||
};
|
||||
|
||||
const packageWithoutTags = { ...packageData(), project: packageProject(), ...linksData };
|
||||
const packageWithTags = { ...packageWithoutTags, tags: { nodes: packageTags() } };
|
||||
const packageCannotDestroy = { ...packageData(), ...linksData, canDestroy: false };
|
||||
|
||||
const findPackageTags = () => wrapper.findComponent(PackageTags);
|
||||
const findDeleteDropdown = () => wrapper.findByTestId('action-delete');
|
||||
|
|
@ -105,7 +105,9 @@ describe('packages_list_row', () => {
|
|||
|
||||
describe('delete button', () => {
|
||||
it('does not exist when package cannot be destroyed', () => {
|
||||
mountComponent({ packageEntity: packageCannotDestroy });
|
||||
mountComponent({
|
||||
packageEntity: { ...packageWithoutTags, canDestroy: false },
|
||||
});
|
||||
|
||||
expect(findDeleteDropdown().exists()).toBe(false);
|
||||
});
|
||||
|
|
@ -168,7 +170,10 @@ describe('packages_list_row', () => {
|
|||
describe('left action template', () => {
|
||||
it('does not render checkbox if not permitted', () => {
|
||||
mountComponent({
|
||||
packageEntity: { ...packageWithoutTags, canDestroy: false },
|
||||
provide: {
|
||||
...defaultProvide,
|
||||
canDeletePackages: false,
|
||||
},
|
||||
});
|
||||
|
||||
expect(findBulkDeleteAction().exists()).toBe(false);
|
||||
|
|
@ -248,6 +253,7 @@ describe('packages_list_row', () => {
|
|||
it('if the package is published through CI show the project and author name', () => {
|
||||
mountComponent({
|
||||
provide: {
|
||||
...defaultProvide,
|
||||
isGroupPage: true,
|
||||
},
|
||||
packageEntity: { ...packageWithoutTags, pipelines: { nodes: packagePipelines() } },
|
||||
|
|
@ -261,6 +267,7 @@ describe('packages_list_row', () => {
|
|||
it('if the package is published manually dont show project and the author name', () => {
|
||||
mountComponent({
|
||||
provide: {
|
||||
...defaultProvide,
|
||||
isGroupPage: true,
|
||||
},
|
||||
packageEntity: { ...packageWithoutTags },
|
||||
|
|
|
|||
|
|
@ -41,6 +41,10 @@ describe('packages_list', () => {
|
|||
groupSettings: defaultPackageGroupSettings,
|
||||
};
|
||||
|
||||
const defaultProvide = {
|
||||
canDeletePackages: true,
|
||||
};
|
||||
|
||||
const EmptySlotStub = { name: 'empty-slot-stub', template: '<div>bar</div>' };
|
||||
|
||||
const findPackagesListLoader = () => wrapper.findComponent(PackagesListLoader);
|
||||
|
|
@ -52,8 +56,9 @@ describe('packages_list', () => {
|
|||
|
||||
const showMock = jest.fn();
|
||||
|
||||
const mountComponent = (props) => {
|
||||
const mountComponent = ({ props = {}, provide = defaultProvide } = {}) => {
|
||||
wrapper = shallowMountExtended(PackagesList, {
|
||||
provide,
|
||||
propsData: {
|
||||
...defaultProps,
|
||||
...props,
|
||||
|
|
@ -75,7 +80,7 @@ describe('packages_list', () => {
|
|||
|
||||
describe('when is loading', () => {
|
||||
beforeEach(() => {
|
||||
mountComponent({ isLoading: true });
|
||||
mountComponent({ props: { isLoading: true } });
|
||||
});
|
||||
|
||||
it('shows skeleton loader', () => {
|
||||
|
|
@ -109,6 +114,7 @@ describe('packages_list', () => {
|
|||
title: '2 packages',
|
||||
items: defaultProps.list,
|
||||
pagination: defaultProps.pageInfo,
|
||||
hiddenDelete: false,
|
||||
isLoading: false,
|
||||
});
|
||||
});
|
||||
|
|
@ -137,6 +143,16 @@ describe('packages_list', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('when the user does not have permission to destroy packages', () => {
|
||||
beforeEach(() => {
|
||||
mountComponent({ provide: { canDeletePackages: false } });
|
||||
});
|
||||
|
||||
it('sets the hidden delete prop of registry list to true', () => {
|
||||
expect(findRegistryList().props('hiddenDelete')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe.each`
|
||||
description | finderFunction | deletePayload
|
||||
${'when the user can destroy the package'} | ${findPackagesListRow} | ${firstPackage}
|
||||
|
|
@ -262,7 +278,7 @@ describe('packages_list', () => {
|
|||
|
||||
describe('when an error package is present', () => {
|
||||
beforeEach(() => {
|
||||
mountComponent({ list: [firstPackage, errorPackage] });
|
||||
mountComponent({ props: { list: [firstPackage, errorPackage] } });
|
||||
|
||||
return nextTick();
|
||||
});
|
||||
|
|
@ -290,7 +306,7 @@ describe('packages_list', () => {
|
|||
|
||||
describe('when the list is empty', () => {
|
||||
beforeEach(() => {
|
||||
mountComponent({ list: [] });
|
||||
mountComponent({ props: { list: [] } });
|
||||
});
|
||||
|
||||
it('show the empty slot', () => {
|
||||
|
|
@ -301,7 +317,7 @@ describe('packages_list', () => {
|
|||
|
||||
describe('pagination', () => {
|
||||
beforeEach(() => {
|
||||
mountComponent({ pageInfo: { hasPreviousPage: true } });
|
||||
mountComponent({ props: { pageInfo: { hasPreviousPage: true } } });
|
||||
});
|
||||
|
||||
it('emits prev-page events when the prev event is fired', () => {
|
||||
|
|
|
|||
|
|
@ -132,7 +132,6 @@ RSpec.describe PackagesHelper, feature_category: :package_registry do
|
|||
describe '#show_container_registry_settings' do
|
||||
let_it_be(:project) { create(:project) }
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:admin) { create(:admin) }
|
||||
|
||||
before do
|
||||
allow(helper).to receive(:current_user) { user }
|
||||
|
|
@ -252,4 +251,114 @@ RSpec.describe PackagesHelper, feature_category: :package_registry do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#can_delete_packages?' do
|
||||
let_it_be(:project) { create(:project) }
|
||||
let_it_be(:user) { create(:user) }
|
||||
|
||||
before do
|
||||
allow(helper).to receive(:current_user) { user }
|
||||
end
|
||||
|
||||
subject { helper.can_delete_packages?(project) }
|
||||
|
||||
context 'with package registry config enabled' do
|
||||
before do
|
||||
stub_config(packages: { enabled: true })
|
||||
end
|
||||
|
||||
context 'when user has permission' do
|
||||
before do
|
||||
allow(Ability).to receive(:allowed?).with(user, :destroy_package, project).and_return(true)
|
||||
end
|
||||
|
||||
it { is_expected.to be(true) }
|
||||
end
|
||||
|
||||
context 'when user does not have permission' do
|
||||
before do
|
||||
allow(Ability).to receive(:allowed?).with(user, :destroy_package, project).and_return(false)
|
||||
end
|
||||
|
||||
it { is_expected.to be(false) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'with package registry config disabled' do
|
||||
before do
|
||||
stub_config(packages: { enabled: false })
|
||||
end
|
||||
|
||||
context 'when user has permission' do
|
||||
before do
|
||||
allow(Ability).to receive(:allowed?).with(user, :destroy_package, project).and_return(true)
|
||||
end
|
||||
|
||||
it { is_expected.to be(false) }
|
||||
end
|
||||
|
||||
context 'when user does not have permission' do
|
||||
before do
|
||||
allow(Ability).to receive(:allowed?).with(user, :destroy_package, project).and_return(false)
|
||||
end
|
||||
|
||||
it { is_expected.to be(false) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#can_delete_group_packages?' do
|
||||
let_it_be(:group) { create(:group) }
|
||||
let_it_be(:user) { create(:user) }
|
||||
|
||||
before do
|
||||
allow(helper).to receive(:current_user) { user }
|
||||
end
|
||||
|
||||
subject { helper.can_delete_group_packages?(group) }
|
||||
|
||||
context 'with package registry config enabled' do
|
||||
before do
|
||||
stub_config(packages: { enabled: true })
|
||||
end
|
||||
|
||||
context 'when user has permission' do
|
||||
before do
|
||||
allow(Ability).to receive(:allowed?).with(user, :destroy_package, group).and_return(true)
|
||||
end
|
||||
|
||||
it { is_expected.to be(true) }
|
||||
end
|
||||
|
||||
context 'when user does not have permission' do
|
||||
before do
|
||||
allow(Ability).to receive(:allowed?).with(user, :destroy_package, group).and_return(false)
|
||||
end
|
||||
|
||||
it { is_expected.to be(false) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'with package registry config disabled' do
|
||||
before do
|
||||
stub_config(packages: { enabled: false })
|
||||
end
|
||||
|
||||
context 'when user has permission' do
|
||||
before do
|
||||
allow(Ability).to receive(:allowed?).with(user, :destroy_package, group).and_return(true)
|
||||
end
|
||||
|
||||
it { is_expected.to be(false) }
|
||||
end
|
||||
|
||||
context 'when user does not have permission' do
|
||||
before do
|
||||
allow(Ability).to receive(:allowed?).with(user, :destroy_package, group).and_return(false)
|
||||
end
|
||||
|
||||
it { is_expected.to be(false) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -14,6 +14,21 @@ RSpec.describe Ml::Experiment, feature_category: :mlops do
|
|||
it { is_expected.to belong_to(:user) }
|
||||
it { is_expected.to have_many(:candidates) }
|
||||
it { is_expected.to have_many(:metadata) }
|
||||
it { is_expected.to belong_to(:model).class_name('Ml::Model') }
|
||||
end
|
||||
|
||||
describe '#destroy' do
|
||||
it 'allow experiment without model to be destroyed' do
|
||||
experiment = create(:ml_experiments, project: exp.project)
|
||||
|
||||
expect { experiment.destroy! }.to change { Ml::Experiment.count }.by(-1)
|
||||
end
|
||||
|
||||
it 'throws error when destroying experiment with model' do
|
||||
experiment = create(:ml_models, project: exp.project).default_experiment
|
||||
|
||||
expect { experiment.destroy! }.to raise_error(ActiveRecord::ActiveRecordError)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.package_name' do
|
||||
|
|
|
|||
|
|
@ -0,0 +1,61 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Ml::Model, feature_category: :mlops do
|
||||
describe 'associations' do
|
||||
it { is_expected.to belong_to(:project) }
|
||||
it { is_expected.to have_one(:default_experiment) }
|
||||
end
|
||||
|
||||
describe '#valid?' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
let_it_be(:project) { create(:project) }
|
||||
let_it_be(:existing_model) { create(:ml_models, name: 'an_existing_model', project: project) }
|
||||
let_it_be(:valid_name) { 'a_valid_name' }
|
||||
let_it_be(:default_experiment) { create(:ml_experiments, name: valid_name, project: project) }
|
||||
|
||||
let(:name) { valid_name }
|
||||
|
||||
subject(:errors) do
|
||||
m = described_class.new(name: name, project: project, default_experiment: default_experiment)
|
||||
m.validate
|
||||
m.errors
|
||||
end
|
||||
|
||||
it 'validates a valid model version' do
|
||||
expect(errors).to be_empty
|
||||
end
|
||||
|
||||
describe 'name' do
|
||||
where(:ctx, :name) do
|
||||
'name is blank' | ''
|
||||
'name is not valid package name' | '!!()()'
|
||||
'name is too large' | ('a' * 256)
|
||||
'name is not unique in the project' | 'an_existing_model'
|
||||
end
|
||||
with_them do
|
||||
it { expect(errors).to include(:name) }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'default_experiment' do
|
||||
context 'when experiment name name is different than model name' do
|
||||
before do
|
||||
allow(default_experiment).to receive(:name).and_return("#{name}a")
|
||||
end
|
||||
|
||||
it { expect(errors).to include(:default_experiment) }
|
||||
end
|
||||
|
||||
context 'when model version project is different than model project' do
|
||||
before do
|
||||
allow(default_experiment).to receive(:project_id).and_return(project.id + 1)
|
||||
end
|
||||
|
||||
it { expect(errors).to include(:default_experiment) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,380 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Projects::ServiceDesk::CustomEmailController, feature_category: :service_desk do
|
||||
let_it_be_with_reload(:project) do
|
||||
create(:project, :private, service_desk_enabled: true)
|
||||
end
|
||||
|
||||
let_it_be(:custom_email_path) { project_service_desk_custom_email_path(project, format: :json) }
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:illegitimite_user) { create(:user) }
|
||||
|
||||
let(:message) { instance_double(Mail::Message) }
|
||||
let(:error_cannot_create_custom_email) { s_("ServiceDesk|Cannot create custom email") }
|
||||
let(:error_cannot_update_custom_email) { s_("ServiceDesk|Cannot update custom email") }
|
||||
let(:error_does_not_exist) { s_('ServiceDesk|Custom email does not exist') }
|
||||
let(:error_custom_email_exists) { s_('ServiceDesk|Custom email already exists') }
|
||||
|
||||
let(:custom_email_params) do
|
||||
{
|
||||
custom_email: 'user@example.com',
|
||||
smtp_address: 'smtp.example.com',
|
||||
smtp_port: '587',
|
||||
smtp_username: 'user@example.com',
|
||||
smtp_password: 'supersecret'
|
||||
}
|
||||
end
|
||||
|
||||
let(:empty_json_response) do
|
||||
{
|
||||
"custom_email" => nil,
|
||||
"custom_email_enabled" => false,
|
||||
"custom_email_verification_state" => nil,
|
||||
"custom_email_verification_error" => nil,
|
||||
"custom_email_smtp_address" => nil,
|
||||
"error_message" => nil
|
||||
}
|
||||
end
|
||||
|
||||
before_all do
|
||||
project.add_developer(illegitimite_user)
|
||||
project.add_maintainer(user)
|
||||
end
|
||||
|
||||
shared_examples 'a json response with empty values' do
|
||||
it 'returns json response with empty values' do
|
||||
perform_request
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(json_response).to include(empty_json_response)
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'a controller that responds with status' do |status|
|
||||
it "responds with #{status} for GET custom email" do
|
||||
get custom_email_path
|
||||
expect(response).to have_gitlab_http_status(status)
|
||||
end
|
||||
|
||||
it "responds with #{status} for POST custom email" do
|
||||
post custom_email_path
|
||||
expect(response).to have_gitlab_http_status(status)
|
||||
end
|
||||
|
||||
it "responds with #{status} for PUT custom email" do
|
||||
put custom_email_path
|
||||
expect(response).to have_gitlab_http_status(status)
|
||||
end
|
||||
|
||||
it "responds with #{status} for DELETE custom email" do
|
||||
delete custom_email_path
|
||||
expect(response).to have_gitlab_http_status(status)
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'a controller with disabled feature flag with status' do |status|
|
||||
context 'when feature flag service_desk_custom_email is disabled' do
|
||||
before do
|
||||
stub_feature_flags(service_desk_custom_email: false)
|
||||
end
|
||||
|
||||
it_behaves_like 'a controller that responds with status', status
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'a deletable resource' do
|
||||
describe 'DELETE custom email' do
|
||||
let(:perform_request) { delete custom_email_path }
|
||||
|
||||
it_behaves_like 'a json response with empty values'
|
||||
end
|
||||
end
|
||||
|
||||
context 'with legitimate user signed in' do
|
||||
before do
|
||||
sign_out(illegitimite_user)
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
# because CustomEmailController check_feature_flag_enabled responds
|
||||
it_behaves_like 'a controller with disabled feature flag with status', :not_found
|
||||
|
||||
describe 'GET custom email' do
|
||||
let(:perform_request) { get custom_email_path }
|
||||
|
||||
it_behaves_like 'a json response with empty values'
|
||||
end
|
||||
|
||||
describe 'POST custom email' do
|
||||
before do
|
||||
# We send verification email directly
|
||||
allow(message).to receive(:deliver)
|
||||
allow(Notify).to receive(:service_desk_custom_email_verification_email).and_return(message)
|
||||
end
|
||||
|
||||
it 'adds custom email and kicks of verification' do
|
||||
post custom_email_path, params: custom_email_params
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(json_response).to include(
|
||||
"custom_email" => custom_email_params[:custom_email],
|
||||
"custom_email_enabled" => false,
|
||||
"custom_email_verification_state" => "started",
|
||||
"custom_email_verification_error" => nil,
|
||||
"custom_email_smtp_address" => custom_email_params[:smtp_address],
|
||||
"error_message" => nil
|
||||
)
|
||||
end
|
||||
|
||||
context 'when custom_email param is not valid' do
|
||||
it 'does not add custom email' do
|
||||
post custom_email_path, params: custom_email_params.merge(custom_email: 'useratexample.com')
|
||||
|
||||
expect(response).to have_gitlab_http_status(:unprocessable_entity)
|
||||
expect(json_response).to include(
|
||||
empty_json_response.merge("error_message" => error_cannot_create_custom_email)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when smtp_password param is not valid' do
|
||||
it 'does not add custom email' do
|
||||
post custom_email_path, params: custom_email_params.merge(smtp_password: '2short')
|
||||
|
||||
expect(response).to have_gitlab_http_status(:unprocessable_entity)
|
||||
expect(json_response).to include(
|
||||
empty_json_response.merge("error_message" => error_cannot_create_custom_email)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the verification process fails fast' do
|
||||
before do
|
||||
# Could not establish connection, invalid host etc.
|
||||
allow(message).to receive(:deliver).and_raise(SocketError)
|
||||
end
|
||||
|
||||
it 'adds custom email and kicks of verification and returns verification error state' do
|
||||
post custom_email_path, params: custom_email_params
|
||||
|
||||
# In terms of "custom email object creation", failing fast on the
|
||||
# verification is a legit state that we don't treat as an error.
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(json_response).to include(
|
||||
"custom_email" => custom_email_params[:custom_email],
|
||||
"custom_email_enabled" => false,
|
||||
"custom_email_verification_state" => "failed",
|
||||
"custom_email_verification_error" => "smtp_host_issue",
|
||||
"custom_email_smtp_address" => custom_email_params[:smtp_address],
|
||||
"error_message" => nil
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'PUT custom email' do
|
||||
let(:custom_email_params) { { custom_email_enabled: true } }
|
||||
|
||||
it 'does not update records' do
|
||||
put custom_email_path, params: custom_email_params
|
||||
|
||||
expect(response).to have_gitlab_http_status(:unprocessable_entity)
|
||||
expect(json_response).to include(
|
||||
empty_json_response.merge("error_message" => error_cannot_update_custom_email)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'DELETE custom email' do
|
||||
it 'does not touch any records' do
|
||||
delete custom_email_path
|
||||
|
||||
expect(response).to have_gitlab_http_status(:unprocessable_entity)
|
||||
expect(json_response).to include(
|
||||
empty_json_response.merge("error_message" => error_does_not_exist)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when custom email is set up' do
|
||||
let!(:settings) { create(:service_desk_setting, project: project, custom_email: 'user@example.com') }
|
||||
let!(:credential) { create(:service_desk_custom_email_credential, project: project) }
|
||||
|
||||
before do
|
||||
project.reset
|
||||
end
|
||||
|
||||
context 'and verification started' do
|
||||
let!(:verification) do
|
||||
create(:service_desk_custom_email_verification, project: project)
|
||||
end
|
||||
|
||||
it_behaves_like 'a deletable resource'
|
||||
|
||||
describe 'GET custom email' do
|
||||
it 'returns custom email in its current state' do
|
||||
get custom_email_path
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(json_response).to include(
|
||||
"custom_email" => "user@example.com",
|
||||
"custom_email_enabled" => false,
|
||||
"custom_email_verification_state" => "started",
|
||||
"custom_email_verification_error" => nil,
|
||||
"custom_email_smtp_address" => "smtp.example.com",
|
||||
"error_message" => nil
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST custom email' do
|
||||
it 'returns custom email in its current state' do
|
||||
post custom_email_path, params: custom_email_params
|
||||
|
||||
expect(response).to have_gitlab_http_status(:unprocessable_entity)
|
||||
expect(json_response).to include(
|
||||
"custom_email" => custom_email_params[:custom_email],
|
||||
"custom_email_enabled" => false,
|
||||
"custom_email_verification_state" => "started",
|
||||
"custom_email_verification_error" => nil,
|
||||
"custom_email_smtp_address" => custom_email_params[:smtp_address],
|
||||
"error_message" => error_custom_email_exists
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'PUT custom email' do
|
||||
let(:custom_email_params) { { custom_email_enabled: true } }
|
||||
|
||||
it 'marks custom email as enabled' do
|
||||
put custom_email_path, params: custom_email_params
|
||||
|
||||
expect(response).to have_gitlab_http_status(:unprocessable_entity)
|
||||
expect(json_response).to include(
|
||||
"custom_email" => "user@example.com",
|
||||
"custom_email_enabled" => false,
|
||||
"custom_email_verification_state" => "started",
|
||||
"custom_email_verification_error" => nil,
|
||||
"custom_email_smtp_address" => "smtp.example.com",
|
||||
"error_message" => error_cannot_update_custom_email
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'and verification finished' do
|
||||
let!(:verification) do
|
||||
create(:service_desk_custom_email_verification, project: project, state: :finished, token: nil)
|
||||
end
|
||||
|
||||
it_behaves_like 'a deletable resource'
|
||||
|
||||
describe 'GET custom email' do
|
||||
it 'returns custom email in its current state' do
|
||||
get custom_email_path
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(json_response).to include(
|
||||
"custom_email" => "user@example.com",
|
||||
"custom_email_enabled" => false,
|
||||
"custom_email_verification_state" => "finished",
|
||||
"custom_email_verification_error" => nil,
|
||||
"custom_email_smtp_address" => "smtp.example.com",
|
||||
"error_message" => nil
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'PUT custom email' do
|
||||
let(:custom_email_params) { { custom_email_enabled: true } }
|
||||
|
||||
it 'marks custom email as enabled' do
|
||||
put custom_email_path, params: custom_email_params
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(json_response).to include(
|
||||
"custom_email" => "user@example.com",
|
||||
"custom_email_enabled" => true,
|
||||
"custom_email_verification_state" => "finished",
|
||||
"custom_email_verification_error" => nil,
|
||||
"custom_email_smtp_address" => "smtp.example.com",
|
||||
"error_message" => nil
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'and verification failed' do
|
||||
let!(:verification) do
|
||||
create(:service_desk_custom_email_verification,
|
||||
project: project,
|
||||
state: :failed,
|
||||
token: nil,
|
||||
error: :smtp_host_issue
|
||||
)
|
||||
end
|
||||
|
||||
it_behaves_like 'a deletable resource'
|
||||
|
||||
describe 'GET custom email' do
|
||||
it 'returns custom email in its current state' do
|
||||
get custom_email_path
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(json_response).to include(
|
||||
"custom_email" => "user@example.com",
|
||||
"custom_email_enabled" => false,
|
||||
"custom_email_verification_state" => "failed",
|
||||
"custom_email_verification_error" => "smtp_host_issue",
|
||||
"custom_email_smtp_address" => "smtp.example.com",
|
||||
"error_message" => nil
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'PUT custom email' do
|
||||
let(:custom_email_params) { { custom_email_enabled: true } }
|
||||
|
||||
it 'does not mark custom email as enabled' do
|
||||
put custom_email_path, params: custom_email_params
|
||||
|
||||
expect(response).to have_gitlab_http_status(:unprocessable_entity)
|
||||
expect(json_response).to include(
|
||||
"custom_email" => "user@example.com",
|
||||
"custom_email_enabled" => false,
|
||||
"custom_email_verification_state" => "failed",
|
||||
"custom_email_verification_error" => "smtp_host_issue",
|
||||
"custom_email_smtp_address" => "smtp.example.com",
|
||||
"error_message" => error_cannot_update_custom_email
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is anonymous' do
|
||||
before do
|
||||
sign_out(user)
|
||||
sign_out(illegitimite_user)
|
||||
end
|
||||
|
||||
# because Projects::ApplicationController :authenticate_user! responds
|
||||
# with redirect to login page
|
||||
it_behaves_like 'a controller that responds with status', :found
|
||||
it_behaves_like 'a controller with disabled feature flag with status', :found
|
||||
end
|
||||
|
||||
context 'with illegitimate user signed in' do
|
||||
before do
|
||||
sign_out(user)
|
||||
sign_in(illegitimite_user)
|
||||
end
|
||||
|
||||
it_behaves_like 'a controller that responds with status', :not_found
|
||||
# because CustomEmailController check_feature_flag_enabled responds
|
||||
it_behaves_like 'a controller with disabled feature flag with status', :not_found
|
||||
end
|
||||
end
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.shared_examples 'milestone handling version conflicts' do
|
||||
it 'warns about version conflict when milestone has been updated in the background' do
|
||||
it 'warns about version conflict when milestone has been updated in the background', :js do
|
||||
wait_for_all_requests
|
||||
|
||||
# Update the milestone in the background in order to trigger a version conflict
|
||||
milestone.update!(title: "New title")
|
||||
|
||||
|
|
|
|||
|
|
@ -36,4 +36,22 @@ RSpec.describe 'groups/packages/index.html.haml', feature_category: :package_reg
|
|||
)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'can_delete_packages' do
|
||||
it 'without permission sets false' do
|
||||
allow(view).to receive(:can_delete_group_packages?).and_return(false)
|
||||
|
||||
render
|
||||
|
||||
expect(rendered).to have_selector('[data-can-delete-packages="false"]')
|
||||
end
|
||||
|
||||
it 'with permission sets true' do
|
||||
allow(view).to receive(:can_delete_group_packages?).and_return(true)
|
||||
|
||||
render
|
||||
|
||||
expect(rendered).to have_selector('[data-can-delete-packages="true"]')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -36,4 +36,22 @@ RSpec.describe 'projects/packages/packages/index.html.haml', feature_category: :
|
|||
)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'can_delete_packages' do
|
||||
it 'without permission sets empty settings path' do
|
||||
allow(view).to receive(:can_delete_packages?).and_return(false)
|
||||
|
||||
render
|
||||
|
||||
expect(rendered).to have_selector('[data-can-delete-packages="false"]')
|
||||
end
|
||||
|
||||
it 'with permission sets project settings path' do
|
||||
allow(view).to receive(:can_delete_packages?).and_return(true)
|
||||
|
||||
render
|
||||
|
||||
expect(rendered).to have_selector('[data-can-delete-packages="true"]')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -245,6 +245,7 @@ RSpec.describe 'Every Sidekiq worker', feature_category: :shared do
|
|||
'Geo::RepositoryVerification::Primary::SingleWorker' => false,
|
||||
'Geo::RepositoryVerification::Secondary::SingleWorker' => false,
|
||||
'Geo::ReverificationBatchWorker' => 0,
|
||||
'Geo::BulkMarkPendingBatchWorker' => 0,
|
||||
'Geo::Scheduler::Primary::SchedulerWorker' => false,
|
||||
'Geo::Scheduler::SchedulerWorker' => false,
|
||||
'Geo::Scheduler::Secondary::SchedulerWorker' => false,
|
||||
|
|
|
|||
Loading…
Reference in New Issue