Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2024-02-23 18:12:14 +00:00
parent 4b86353c0b
commit 4dd83293db
89 changed files with 980 additions and 571 deletions

View File

@ -89,7 +89,6 @@ Database/AvoidUsingPluckWithoutLimit:
- 'app/services/merge_requests/push_options_handler_service.rb'
- 'app/services/merge_requests/pushed_branches_service.rb'
- 'app/services/packages/cleanup/execute_policy_service.rb'
- 'app/services/projects/slack_application_install_service.rb'
- 'app/services/projects/unlink_fork_service.rb'
- 'ee/app/finders/ee/issuables/label_filter.rb'
- 'ee/app/finders/ee/merge_requests_finder.rb'

View File

@ -261,7 +261,6 @@ Lint/UnusedMethodArgument:
- 'ee/app/models/iteration.rb'
- 'ee/app/models/iteration_note.rb'
- 'ee/app/replicators/geo/container_repository_replicator.rb'
- 'ee/app/replicators/geo/pipeline_replicator.rb'
- 'ee/app/serializers/analytics/cycle_analytics/value_stream_errors_serializer.rb'
- 'ee/app/services/app_sec/dast/scans/run_service.rb'
- 'ee/app/services/audit_events/runners_token_audit_event_service.rb'

View File

@ -784,7 +784,6 @@ RSpec/BeforeAllRoleAssignment:
- 'spec/controllers/projects/runner_projects_controller_spec.rb'
- 'spec/controllers/projects/settings/ci_cd_controller_spec.rb'
- 'spec/controllers/projects/settings/integrations_controller_spec.rb'
- 'spec/controllers/projects/settings/slacks_controller_spec.rb'
- 'spec/controllers/projects/snippets_controller_spec.rb'
- 'spec/controllers/projects/terraform_controller_spec.rb'
- 'spec/controllers/projects/tree_controller_spec.rb'

View File

@ -709,7 +709,6 @@ RSpec/NamedSubject:
- 'ee/spec/presenters/subscription_presenter_spec.rb'
- 'ee/spec/presenters/vulnerabilities/scanner_presenter_spec.rb'
- 'ee/spec/presenters/vulnerability_presenter_spec.rb'
- 'ee/spec/replicators/geo/pipeline_replicator_spec.rb'
- 'ee/spec/requests/admin/user_permission_exports_controller_spec.rb'
- 'ee/spec/requests/api/audit_events_spec.rb'
- 'ee/spec/requests/api/ci/jobs_spec.rb'

View File

@ -719,7 +719,6 @@ Style/InlineDisableAnnotation:
- 'app/services/projects/open_issues_count_service.rb'
- 'app/services/projects/overwrite_project_service.rb'
- 'app/services/projects/record_target_platforms_service.rb'
- 'app/services/projects/slack_application_install_service.rb'
- 'app/services/projects/transfer_service.rb'
- 'app/services/projects/unlink_fork_service.rb'
- 'app/services/projects/update_pages_service.rb'

View File

@ -1,6 +1,6 @@
<script>
import { GlBanner, GlSprintf, GlLink } from '@gitlab/ui';
import ClusterPopoverSvg from '@gitlab/svgs/dist/illustrations/cluster_popover.svg?url';
import ClusterPopoverSvg from '@gitlab/svgs/dist/illustrations/devops-sm.svg?url';
import UserCalloutDismisser from '~/vue_shared/components/user_callout_dismisser.vue';
import { transitionBannerTexts } from '../constants';
@ -51,7 +51,6 @@ export default {
:svg-path="$options.ClusterPopoverSvg"
button-link="https://gitlab.cn/upgrade/"
class="gl-mt-3"
variant="introduction"
@close="dismiss"
>
<p>

View File

@ -493,11 +493,7 @@ export default {
</div>
<!-- Get Started with ET -->
<div v-else>
<gl-empty-state
:title="__('Get started with error tracking')"
:svg-path="illustrationPath"
:svg-height="null"
>
<gl-empty-state :title="__('Get started with error tracking')" :svg-path="illustrationPath">
<template #description>
<div>
<span>{{ __('Monitor your errors directly in GitLab.') }}</span>

View File

@ -1,5 +1,4 @@
<script>
import emptySearchSvgUrl from '@gitlab/svgs/dist/illustrations/empty-state/empty-search-md.svg?url';
import { GlLoadingIcon, GlModal, GlEmptyState } from '@gitlab/ui';
import { createAlert } from '~/alert';
import { HTTP_STATUS_FORBIDDEN } from '~/lib/utils/http_status';
@ -23,6 +22,7 @@ export default {
GlLoadingIcon,
GlEmptyState,
},
inject: ['emptySearchIllustration'],
props: {
action: {
type: String,
@ -231,7 +231,6 @@ export default {
}
},
},
emptySearchSvgUrl,
};
</script>
@ -247,7 +246,7 @@ export default {
<groups-component v-if="hasGroups" :groups="groups" :page-info="pageInfo" :action="action" />
<gl-empty-state
v-else-if="fromSearch"
:svg-path="$options.emptySearchSvgUrl"
:svg-path="emptySearchIllustration"
:title="$options.i18n.searchEmptyState.title"
:description="$options.i18n.searchEmptyState.description"
data-testid="search-empty-state"

View File

@ -5,14 +5,12 @@ import { s__ } from '~/locale';
export default {
components: { GlEmptyState },
inject: ['groupsEmptyStateIllustration', 'newGroupPath', 'exploreGroupsPath'],
inject: ['groupsEmptyStateIllustration'],
i18n: {
title: s__('GroupsEmptyState|A group is a collection of several projects'),
description: s__(
'GroupsEmptyState|If you organize your projects under a group, it works like a folder. You can manage the permissions and access of your group members for each project in the group.',
"GroupsEmptyState|If you organize your projects under a group, it works like a folder. You can manage your group member's permissions and access to each project in the group.",
),
secondary_button_text: s__('GroupsEmptyState|Explore groups'),
primary_button_text: s__('GroupsEmptyState|New group'),
},
};
</script>
@ -22,10 +20,6 @@ export default {
:title="$options.i18n.title"
:description="$options.i18n.description"
:svg-path="groupsEmptyStateIllustration"
:primary-button-text="$options.i18n.primary_button_text"
:primary-button-link="newGroupPath"
:secondary-button-text="$options.i18n.secondary_button_text"
:secondary-button-link="exploreGroupsPath"
data-testid="groups-empty-state"
:svg-height="null"
/>
</template>

View File

@ -37,19 +37,9 @@ export default (EmptyStateComponent) => {
GroupsApp,
},
provide() {
const {
groupsEmptyStateIllustration,
emptySearchIllustration,
newGroupPath,
exploreGroupsPath,
} = dataset;
const { groupsEmptyStateIllustration, emptySearchIllustration } = dataset;
return {
groupsEmptyStateIllustration,
emptySearchIllustration,
newGroupPath,
exploreGroupsPath,
};
return { groupsEmptyStateIllustration, emptySearchIllustration };
},
data() {
const showSchemaMarkup = parseBoolean(dataset.showSchemaMarkup);

View File

@ -47,6 +47,7 @@ export const initGroupOverviewTabs = () => {
newProjectIllustration,
emptyProjectsIllustration,
emptySubgroupIllustration,
emptySearchIllustration,
canCreateSubgroups,
canCreateProjects,
currentGroupVisibility,
@ -67,6 +68,7 @@ export const initGroupOverviewTabs = () => {
newProjectIllustration,
emptyProjectsIllustration,
emptySubgroupIllustration,
emptySearchIllustration,
canCreateSubgroups: parseBoolean(canCreateSubgroups),
canCreateProjects: parseBoolean(canCreateProjects),
currentGroupVisibility,

View File

@ -1,5 +1,5 @@
<script>
import { GlButton, GlLink, GlForm } from '@gitlab/ui';
import { GlButton, GlForm } from '@gitlab/ui';
// eslint-disable-next-line no-restricted-imports
import { mapActions, mapState, mapGetters } from 'vuex';
import Tracking from '~/tracking';
@ -14,7 +14,6 @@ export default {
name: 'FiltersTemplate',
components: {
GlButton,
GlLink,
GlForm,
},
computed: {
@ -47,12 +46,23 @@ export default {
>
<slot></slot>
<div class="gl-display-flex gl-align-items-center gl-mt-4">
<gl-button category="primary" variant="confirm" type="submit" :disabled="!sidebarDirty">
<gl-button
category="primary"
variant="confirm"
type="submit"
data-testid="search-apply-filters-btn"
:disabled="!sidebarDirty"
>
{{ __('Apply') }}
</gl-button>
<gl-link v-if="sidebarDirty" class="gl-ml-auto" @click="resetQueryWithTracking">{{
__('Reset filters')
}}</gl-link>
<gl-button
v-if="sidebarDirty"
category="tertiary"
class="gl-ml-auto"
data-testid="search-reset-filters-btn"
@click="resetQueryWithTracking"
>{{ __('Reset filters') }}
</gl-button>
</div>
</gl-form>
</template>

View File

@ -8,8 +8,6 @@ module Admin
include ::Integrations::SlackControllerSettings
def slack_auth; end
private
def integration
@ -21,5 +19,9 @@ module Admin
integration || Integrations::GitlabSlackApplication.for_instance.new
)
end
def installation_service
Integrations::SlackInstallation::InstanceService.new(current_user: current_user, params: params)
end
end
end

View File

@ -5,13 +5,26 @@
#
# Controllers should define these methods:
# - `#integration` to return the Integrations::GitLabSlackApplication record.
# - `#redirect_to_integration_page` to redirect to the integration edit page
# - `#redirect_to_integration_page` to redirect to the integration edit page.
# - `#installation_service` to return a service class to handle the OAuth flow.
module Integrations
module SlackControllerSettings
extend ActiveSupport::Concern
included do
feature_category :integrations
before_action :handle_oauth_error, only: :slack_auth
before_action :check_oauth_state, only: :slack_auth
end
def slack_auth
result = installation_service.execute
flash[:alert] = result.message if result.error?
session[:slack_install_success] = result.success?
redirect_to_integration_page
end
def destroy
@ -25,5 +38,18 @@ module Integrations
def slack_integration
@slack_integration ||= integration.slack_integration
end
def handle_oauth_error
return unless params[:error] == 'access_denied'
flash[:alert] = s_('SlackIntegration|Access request canceled')
redirect_to_integration_page
end
def check_oauth_state
render_403 unless valid_authenticity_token?(session, params[:state])
true
end
end
end

View File

@ -13,8 +13,6 @@ module Groups
layout 'group_settings'
def slack_auth; end
private
def integration
@ -26,6 +24,10 @@ module Groups
group, integration || Integrations::GitlabSlackApplication.for_group(group).new
)
end
def installation_service
Integrations::SlackInstallation::GroupService.new(group, current_user: current_user, params: params)
end
end
end
end

View File

@ -108,6 +108,17 @@ class Projects::ApplicationController < ApplicationController
false
end
end
def handle_update_result(result)
if result[:status] == :success
flash[:notice] = format(_("Project '%{project_name}' was successfully updated."), project_name: @project.name)
redirect_to(edit_project_path(@project, anchor: 'js-general-project-settings'))
else
flash[:alert] = result[:message]
@project.reset
render 'edit'
end
end
end
Projects::ApplicationController.prepend_mod_with('Projects::ApplicationController')

View File

@ -3,9 +3,6 @@
module Projects
module Settings
class SlacksController < Projects::ApplicationController
before_action :handle_oauth_error, only: :slack_auth
before_action :check_oauth_state, only: :slack_auth
include ::Integrations::SlackControllerSettings
before_action :authorize_admin_project!
@ -14,15 +11,6 @@ module Projects
layout 'project_settings'
def slack_auth
result = Projects::SlackApplicationInstallService.new(project, current_user, params).execute
flash[:alert] = result[:message] if result[:status] == :error
session[:slack_install_success] = true
redirect_to_integration_page
end
def edit; end
def update
@ -47,22 +35,13 @@ module Projects
)
end
def check_oauth_state
render_403 unless valid_authenticity_token?(session, params[:state])
true
end
def handle_oauth_error
return unless params[:error] == 'access_denied'
flash[:alert] = 'Access denied'
redirect_to_integration_page
end
def slack_integration_params
params.require(:slack_integration).permit(:alias)
end
def installation_service
::Integrations::SlackInstallation::ProjectService.new(project, current_user: current_user, params: params)
end
end
end
end

View File

@ -124,14 +124,7 @@ class ProjectsController < Projects::ApplicationController
# Refresh the repo in case anything changed
@repository = @project.repository
if result[:status] == :success
flash[:notice] = _("Project '%{project_name}' was successfully updated.") % { project_name: @project.name }
redirect_to(edit_project_path(@project, anchor: 'js-general-project-settings'))
else
flash[:alert] = result[:message]
@project.reset
render 'edit'
end
handle_update_result(result)
end
# rubocop: disable CodeReuse/ActiveRecord

View File

@ -155,6 +155,7 @@ module GroupsHelper
new_project_illustration: image_path('illustrations/project-create-new-sm.svg'),
empty_projects_illustration: image_path('illustrations/empty-state/empty-projects-md.svg'),
empty_subgroup_illustration: image_path('illustrations/empty-state/empty-subgroup-md.svg'),
empty_search_illustration: image_path('illustrations/empty-state/empty-search-md.svg'),
render_empty_state: 'true',
can_create_subgroups: can?(current_user, :create_subgroup, group).to_s,
can_create_projects: can?(current_user, :create_projects, group).to_s

View File

@ -226,7 +226,10 @@ module IntegrationsHelper
state: form_authenticity_token
}
"#{::Projects::SlackApplicationInstallService::SLACK_AUTHORIZE_URL}?#{query.to_query}"
Gitlab::Utils.add_url_parameters(
Integrations::SlackInstallation::BaseService::SLACK_AUTHORIZE_URL,
query
)
end
def slack_integration_destroy_path(parent)

View File

@ -12,7 +12,7 @@ module Projects::ErrorTrackingHelper
'integrated-error-tracking-enabled' => integrated_tracking_enabled?(project).to_s,
'project-path' => project.full_path,
'list-path' => project_error_tracking_index_path(project),
'illustration-path' => image_path('illustrations/cluster_popover.svg'),
'illustration-path' => image_path('illustrations/empty-state/empty-radar-md.svg'),
'show-integrated-tracking-disabled-alert' => show_integrated_tracking_disabled_alert?(project).to_s
}
end

View File

@ -282,7 +282,8 @@ module ApplicationSettingImplementation
enable_member_promotion_management: false,
security_approval_policies_limit: 5,
downstream_pipeline_trigger_limit_per_project_user_sha: 0,
asciidoc_max_includes: 32
asciidoc_max_includes: 32,
use_clickhouse_for_analytics: false
}.tap do |hsh|
hsh.merge!(non_production_defaults) unless Rails.env.production?
end

View File

@ -0,0 +1,115 @@
# frozen_string_literal: true
# Base class for services that handle enabling the GitLab for Slack app integration.
#
# Inheriting services should define these methods:
# - `#authorized?` return true if the user is authorized to install the app
# - `#redirect_uri` return the redirect URI for the OAuth flow
# - `#find_or_create_integration` find or create the Integrations::GitlabSlackApplication record
# - `#installation_alias` return the alias property for the SlackIntegration record
module Integrations
module SlackInstallation
class BaseService
include Gitlab::Routing
# Endpoint to initiate the OAuth flow, redirects to Slack's authorization screen
# https://api.slack.com/authentication/oauth-v2#asking
SLACK_AUTHORIZE_URL = 'https://slack.com/oauth/v2/authorize'
# Endpoint to exchange the temporary authorization code for an access token
# https://api.slack.com/authentication/oauth-v2#exchanging
SLACK_EXCHANGE_TOKEN_URL = 'https://slack.com/api/oauth.v2.access'
def initialize(current_user:, params:)
@current_user = current_user
@params = params
end
def execute
unless Gitlab::CurrentSettings.slack_app_enabled
return ServiceResponse.error(message: s_('SlackIntegration|Slack app not enabled on GitLab instance'))
end
return ServiceResponse.error(message: s_('SlackIntegration|Unauthorized')) unless authorized?
begin
slack_data = exchange_slack_token
rescue *::Gitlab::HTTP::HTTP_ERRORS => e
return ServiceResponse
.error(message: s_('SlackIntegration|Error exchanging OAuth token with Slack'))
.track_exception(as: e.class)
end
unless slack_data['ok']
return ServiceResponse.error(
message: format(
s_('SlackIntegration|Error exchanging OAuth token with Slack: %{error}'),
error: slack_data['error']
)
)
end
integration = find_or_create_integration!
installation = integration.slack_integration || integration.build_slack_integration
installation.update!(
bot_user_id: slack_data['bot_user_id'],
bot_access_token: slack_data['access_token'],
team_id: slack_data.dig('team', 'id'),
team_name: slack_data.dig('team', 'name'),
alias: installation_alias,
user_id: slack_data.dig('authed_user', 'id'),
authorized_scope_names: slack_data['scope']
)
update_other_installations!(installation)
ServiceResponse.success
end
private
attr_reader :current_user, :params
def exchange_slack_token
query = {
client_id: Gitlab::CurrentSettings.slack_app_id,
client_secret: Gitlab::CurrentSettings.slack_app_secret,
code: params[:code],
redirect_uri: redirect_uri
}
Gitlab::HTTP.get(SLACK_EXCHANGE_TOKEN_URL, query: query).to_hash
end
# Due to our modelling (mentioned in epic 9418) we create a SlackIntegration record
# for a Slack workspace (team_id) for every GitLab for Slack integration.
# The repetition is redundant, and we should more correctly only create
# a single record per workspace.
#
# Records that share a team_id (Slack workspace ID) should have identical bot token
# and permission scope data. We currently paper-over the modelling problem
# by mass-updating all records that share a team_id so they always reflect the same state.
# for this data. This means if we release a new version of the GitLab for Slack app that has
# a new required permission scope, the first time the workspace authorizes the new scope
# all other records for their workspace will be updated with the latest authorization data
# for that workspace.
def update_other_installations!(installation)
updatable_attributes = installation.attributes.slice(
'user_id',
'bot_user_id',
'encrypted_bot_access_token',
'encrypted_bot_access_token_iv',
'updated_at'
)
SlackIntegration.by_team(installation.team_id).id_not_in(installation.id).each_batch do |batch|
batch_ids = batch.pluck_primary_key
batch.update_all(updatable_attributes)
Integrations::SlackWorkspace::IntegrationApiScope.update_scopes(batch_ids, installation.slack_api_scopes)
end
end
end
end
end

View File

@ -0,0 +1,33 @@
# frozen_string_literal: true
module Integrations
module SlackInstallation
class GroupService < BaseService
def initialize(group, current_user:, params:)
@group = group
super(current_user: current_user, params: params)
end
private
attr_reader :group
def redirect_uri
slack_auth_group_settings_slack_url(group)
end
def installation_alias
group.full_path
end
def authorized?
current_user.can?(:admin_group, group)
end
def find_or_create_integration!
GitlabSlackApplication.for_group(group).first_or_create!
end
end
end
end

View File

@ -0,0 +1,25 @@
# frozen_string_literal: true
module Integrations
module SlackInstallation
class InstanceService < BaseService
private
def redirect_uri
slack_auth_admin_application_settings_slack_url
end
def installation_alias
'_gitlab-instance'
end
def authorized?
current_user.can_admin_all_resources?
end
def find_or_create_integration!
GitlabSlackApplication.for_instance.first_or_create!
end
end
end
end

View File

@ -0,0 +1,33 @@
# frozen_string_literal: true
module Integrations
module SlackInstallation
class ProjectService < BaseService
def initialize(project, current_user:, params:)
@project = project
super(current_user: current_user, params: params)
end
private
attr_reader :project
def redirect_uri
slack_auth_project_settings_slack_url(project)
end
def installation_alias
project.full_path
end
def authorized?
current_user.can?(:admin_project, project)
end
def find_or_create_integration!
project.gitlab_slack_application_integration || project.create_gitlab_slack_application_integration!
end
end
end
end

View File

@ -1,76 +0,0 @@
# frozen_string_literal: true
module Projects
class SlackApplicationInstallService < BaseService
include Gitlab::Routing
# Endpoint to initiate the OAuth flow, redirects to Slack's authorization screen
# https://api.slack.com/authentication/oauth-v2#asking
SLACK_AUTHORIZE_URL = 'https://slack.com/oauth/v2/authorize'
# Endpoint to exchange the temporary authorization code for an access token
# https://api.slack.com/authentication/oauth-v2#exchanging
SLACK_EXCHANGE_TOKEN_URL = 'https://slack.com/api/oauth.v2.access'
def execute
slack_data = exchange_slack_token
return error("Slack: #{slack_data['error']}") unless slack_data['ok']
integration = project.gitlab_slack_application_integration \
|| project.create_gitlab_slack_application_integration!
installation = integration.slack_integration || integration.build_slack_integration
installation.update!(
bot_user_id: slack_data['bot_user_id'],
bot_access_token: slack_data['access_token'],
team_id: slack_data.dig('team', 'id'),
team_name: slack_data.dig('team', 'name'),
alias: project.full_path,
user_id: slack_data.dig('authed_user', 'id'),
authorized_scope_names: slack_data['scope']
)
update_legacy_installations!(installation)
success
end
private
def exchange_slack_token
query = {
client_id: Gitlab::CurrentSettings.slack_app_id,
client_secret: Gitlab::CurrentSettings.slack_app_secret,
code: params[:code],
# NOTE: Needs to match the `redirect_uri` passed to the authorization endpoint,
# otherwise we get a `bad_redirect_uri` error.
redirect_uri: slack_auth_project_settings_slack_url(project)
}
Gitlab::HTTP.get(SLACK_EXCHANGE_TOKEN_URL, query: query).to_hash
end
# Update any legacy SlackIntegration records for the Slack Workspace. Legacy SlackIntegration records
# are any created before our Slack App was upgraded to use Granular Bot Permissions and issue a
# bot_access_token. Any SlackIntegration records for the Slack Workspace will already have the same
# bot_access_token.
def update_legacy_installations!(installation)
updatable_attributes = installation.attributes.slice(
'user_id',
'bot_user_id',
'encrypted_bot_access_token',
'encrypted_bot_access_token_iv',
'updated_at'
)
SlackIntegration.by_team(installation.team_id).id_not_in(installation.id).each_batch do |batch|
batch_ids = batch.pluck(:id) # rubocop: disable CodeReuse/ActiveRecord
batch.update_all(updatable_attributes)
::Integrations::SlackWorkspace::IntegrationApiScope.update_scopes(batch_ids, installation.slack_api_scopes)
end
end
end
end

View File

@ -1,14 +1,13 @@
- if current_user.groups.length > 0
.page-title-holder.d-flex.gl-align-items-center
%h1.page-title.gl-font-size-h-display= _('Groups')
.page-title-holder.d-flex.gl-align-items-center
%h1.page-title.gl-font-size-h-display= _('Groups')
.page-title-controls.gl-display-flex.gl-align-items-center.gl-gap-5
= link_to _("Explore groups"), explore_groups_path
- if current_user.can_create_group?
= render Pajamas::ButtonComponent.new(href: new_group_path, variant: :confirm, button_options: { data: { testid: "new-group-button" } }) do
= _("New group")
.page-title-controls.gl-display-flex.gl-align-items-center.gl-gap-5
= link_to _("Explore groups"), explore_groups_path
- if current_user.can_create_group?
= render Pajamas::ButtonComponent.new(href: new_group_path, variant: :confirm, button_options: { data: { testid: "new-group-button" } }) do
= _("New group")
.top-area.gl-py-3.gl-justify-content-end.gl-border-bottom-0
.nav-controls
= render 'shared/groups/search_form'
= render 'shared/groups/dropdown'
.top-area.gl-py-3.gl-justify-content-end.gl-border-bottom-0
.nav-controls
= render 'shared/groups/search_form'
= render 'shared/groups/dropdown'

View File

@ -1,2 +1,2 @@
.js-groups-list-holder
#js-groups-tree{ data: { endpoint: dashboard_groups_path(format: :json), path: dashboard_groups_path, new_group_path: new_group_path, explore_groups_path: explore_groups_path, form_sel: 'form#group-filter-form', filter_sel: '.js-groups-list-filter', holder_sel: '.js-groups-list-holder', dropdown_sel: '.js-group-filter-dropdown-wrap', groups_empty_state_illustration: image_path('illustrations/empty-state/empty-groups-md.svg'), empty_search_illustration: image_path('illustrations/empty-state/empty-search-md.svg') } }
#js-groups-tree{ data: { endpoint: dashboard_groups_path(format: :json), path: dashboard_groups_path, form_sel: 'form#group-filter-form', filter_sel: '.js-groups-list-filter', holder_sel: '.js-groups-list-holder', dropdown_sel: '.js-group-filter-dropdown-wrap', groups_empty_state_illustration: image_path('illustrations/empty-state/empty-groups-md.svg'), empty_search_illustration: image_path('illustrations/empty-state/empty-search-md.svg') } }

View File

@ -1,4 +1,4 @@
.top-area.gl-py-3.gl-border-0.gl-justify-content-end
.top-area.gl-p-3.gl-justify-content-end
.nav-controls
= render 'shared/groups/search_form'
= render 'shared/groups/dropdown'

View File

@ -11,7 +11,15 @@
= render Pajamas::ButtonComponent.new(href: new_group_path, variant: :confirm) do
= _("New group")
%p= _("Below you will find all the groups that are public. You can easily contribute to them by requesting to join these groups.")
= render 'nav'
- if cookies[:explore_groups_landing_dismissed] != 'true'
.explore-groups.landing.content-block.js-explore-groups-landing.hide
%button.dismiss-button{ type: 'button', 'aria-label' => _('Dismiss') }= sprite_icon('close')
.svg-container
= custom_icon('icon_explore_groups_splash')
.inner-content
%p= _("Below you will find all the groups that are public.")
%p= _("You can easily contribute to them by requesting to join these groups.")
= render 'groups'

View File

@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/432489
milestone: '16.7'
type: development
group: group::global search
default_enabled: false
default_enabled: true

View File

@ -1,8 +0,0 @@
---
name: run_clickhouse_migrations_automatically
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/138661
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/434848
milestone: '16.7'
type: ops
group: group::runner
default_enabled: true

View File

@ -4,7 +4,7 @@ key_path: version
description: Version of GitLab instance
product_section: enablement
product_stage: enablement
product_group: distribution
product_group: distribution_deploy
value_type: string
status: active
milestone: "<13.9"

View File

@ -4,7 +4,7 @@ key_path: installation_type
description: The installation method used to install GitLab (Omnibus, Helm, etc)
product_section: enablement
product_stage: enablement
product_group: distribution
product_group: distribution_deploy
value_type: string
status: active
milestone: "11.0"

View File

@ -4,7 +4,7 @@ key_path: git.version
description: Information about Git version
product_section: enablement
product_stage: enablement
product_group: distribution
product_group: distribution_deploy
value_type: object
value_json_schema: 'config/metrics/objects_schemas/git_version_schema.json'
status: active

View File

@ -6,7 +6,7 @@ description: This metric only returns a value of PostgreSQL in supported version
supported.
product_section: enablement
product_stage: enablement
product_group: enablement_distribution
product_group: distribution_deploy
value_type: string
status: active
time_frame: none

View File

@ -4,7 +4,7 @@ key_path: edition
description: Edition of GitLab such as EE or CE
product_section: enablement
product_stage: enablement
product_group: distribution
product_group: distribution_deploy
value_type: string
status: active
time_frame: none

View File

@ -4,7 +4,7 @@ key_path: settings.ldap_encrypted_secrets_enabled
description: Is encrypted LDAP secrets configured?
product_section: enablement
product_stage: enablement
product_group: distribution
product_group: distribution_deploy
value_type: boolean
status: active
time_frame: none

View File

@ -4,7 +4,7 @@ key_path: database.version
description: The version of the PostgreSQL database.
product_section: enablement
product_stage: enablement
product_group: distribution
product_group: distribution_deploy
value_type: string
status: active
time_frame: none

View File

@ -4,7 +4,7 @@ key_path: database.pg_system_id
description: TBD
product_section: enablement
product_stage: enablement
product_group: distribution
product_group: distribution_deploy
value_type: number
status: active
time_frame: all

View File

@ -4,7 +4,7 @@ key_path: settings.operating_system
description: Information about the operating system running GitLab
product_section: enablement
product_stage: enablement
product_group: distribution
product_group: distribution_deploy
value_type: string
status: active
milestone: "13.10"

View File

@ -3,7 +3,7 @@ key_path: settings.smtp_encrypted_secrets_enabled
description: Is encrypted SMTP secrets configured?
product_section: enablement
product_stage: enablement
product_group: distribution
product_group: distribution_deploy
value_type: boolean
status: active
milestone: "14.2"

View File

@ -3,7 +3,7 @@ key_path: settings.incoming_email_encrypted_secrets_enabled
description: Are encrypted incoming email secrets configured?
product_section: enablement
product_stage: enablement
product_group: distribution
product_group: distribution_deploy
value_type: boolean
status: active
milestone: "15.9"

View File

@ -3,7 +3,7 @@ key_path: settings.service_desk_email_encrypted_secrets_enabled
description: Are service desk email secrets configured?
product_section: enablement
product_stage: enablement
product_group: distribution
product_group: distribution_deploy
value_type: boolean
status: active
milestone: "15.9"

View File

@ -16,3 +16,5 @@ allow_cross_foreign_keys:
- gitlab_main_clusterwide
sharding_key:
project_id: projects
removed_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/145195
removed_in_milestone: '16.10'

View File

@ -0,0 +1,21 @@
# frozen_string_literal: true
class AddClickHouseToApplicationSettings < Gitlab::Database::Migration[2.2]
milestone '16.10'
disable_ddl_transaction!
def up
add_column :application_settings, :clickhouse, :jsonb, default: {}, null: false
add_check_constraint(
:application_settings,
"(jsonb_typeof(clickhouse) = 'object')",
'check_application_settings_clickhouse_is_hash'
)
end
def down
remove_column :application_settings, :clickhouse
end
end

View File

@ -0,0 +1,22 @@
# frozen_string_literal: true
class RemoveForeignKeyProjectRepositoryStates < Gitlab::Database::Migration[2.2]
milestone '16.10'
disable_ddl_transaction!
FOREIGN_KEY_NAME_PROJECTS = "fk_rails_0f2298ca8a"
def up
with_lock_retries do
remove_foreign_key_if_exists(:project_repository_states, :projects,
name: FOREIGN_KEY_NAME_PROJECTS, reverse_lock_order: true)
end
end
def down
add_concurrent_foreign_key(:project_repository_states, :projects,
name: FOREIGN_KEY_NAME_PROJECTS, column: :project_id,
target_column: :id, on_delete: :cascade)
end
end

View File

@ -0,0 +1,57 @@
# frozen_string_literal: true
class DropProjectRepositoryStatesTable < Gitlab::Database::Migration[2.2]
milestone '16.10'
disable_ddl_transaction!
def up
drop_table :project_repository_states, if_exists: true
end
def down
return if table_exists?(:project_repository_states)
create_table :project_repository_states, id: :integer do |t|
t.integer :project_id, null: false
t.binary :repository_verification_checksum, using: 'repository_verification_checksum::bytea'
t.binary :wiki_verification_checksum, using: 'wiki_verification_checksum::bytea'
t.string :last_repository_verification_failure
t.string :last_wiki_verification_failure
t.datetime_with_timezone :repository_retry_at
t.datetime_with_timezone :wiki_retry_at
t.integer :repository_retry_count
t.integer :wiki_retry_count
t.datetime_with_timezone :last_repository_verification_ran_at
t.datetime_with_timezone :last_wiki_verification_ran_at
t.datetime_with_timezone :last_repository_updated_at
t.datetime_with_timezone :last_wiki_updated_at
t.index [:project_id, :last_repository_verification_ran_at],
name: :idx_repository_states_on_last_repository_verification_ran_at,
where: "(repository_verification_checksum IS NOT NULL) AND (last_repository_verification_failure IS NULL)"
t.index [:project_id, :last_wiki_verification_ran_at],
name: :idx_repository_states_on_last_wiki_verification_ran_at,
where: "(wiki_verification_checksum IS NOT NULL) AND (last_wiki_verification_failure IS NULL)"
t.index :last_repository_verification_failure,
name: :idx_repository_states_on_repository_failure_partial,
where: "last_repository_verification_failure IS NOT NULL"
t.index :last_wiki_verification_failure,
name: :idx_repository_states_on_wiki_failure_partial,
where: "last_wiki_verification_failure IS NOT NULL"
# rubocop:disable Layout/LineLength -- Where clause is just too long.
t.index :project_id,
name: :idx_repository_states_outdated_checksums,
where: "((repository_verification_checksum IS NULL) AND (last_repository_verification_failure IS NULL)) OR ((wiki_verification_checksum IS NULL) AND (last_wiki_verification_failure IS NULL))"
# rubocop:enable Layout/LineLength
t.index :project_id,
name: :index_project_repository_states_on_project_id,
unique: true
end
end
end

View File

@ -0,0 +1 @@
05b715f6532afae295aa8fe67eeff166a76fe052f36a75feff8496662afd4280

View File

@ -0,0 +1 @@
c8ce22627e708d6e9d1b967adc7ed83827a589069b729ec2a3f3abcd36af5867

View File

@ -0,0 +1 @@
39f4d9aaf4a2d22f649fe1f0a75ef1fd4cdd9c0066d4e359c8375e8e301400b4

View File

@ -4107,6 +4107,7 @@ CREATE TABLE application_settings (
duo_features_enabled boolean DEFAULT true NOT NULL,
lock_duo_features_enabled boolean DEFAULT false NOT NULL,
asciidoc_max_includes smallint DEFAULT 32 NOT NULL,
clickhouse jsonb DEFAULT '{}'::jsonb 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_container_registry_pre_import_tags_rate_positive CHECK ((container_registry_pre_import_tags_rate >= (0)::numeric)),
CONSTRAINT app_settings_dep_proxy_ttl_policies_worker_capacity_positive CHECK ((dependency_proxy_ttl_group_policy_worker_capacity >= 0)),
@ -4157,6 +4158,7 @@ CREATE TABLE application_settings (
CONSTRAINT check_ae53cf7f82 CHECK ((char_length(vertex_ai_host) <= 255)),
CONSTRAINT check_app_settings_namespace_storage_forks_cost_factor_range CHECK (((namespace_storage_forks_cost_factor >= (0)::double precision) AND (namespace_storage_forks_cost_factor <= (1)::double precision))),
CONSTRAINT check_app_settings_sentry_clientside_traces_sample_rate_range CHECK (((sentry_clientside_traces_sample_rate >= (0)::double precision) AND (sentry_clientside_traces_sample_rate <= (1)::double precision))),
CONSTRAINT check_application_settings_clickhouse_is_hash CHECK ((jsonb_typeof(clickhouse) = 'object'::text)),
CONSTRAINT check_application_settings_rate_limits_is_hash CHECK ((jsonb_typeof(rate_limits) = 'object'::text)),
CONSTRAINT check_b8c74ea5b3 CHECK ((char_length(deactivation_email_additional_text) <= 1000)),
CONSTRAINT check_cdfbd99405 CHECK ((char_length(security_txt_content) <= 2048)),
@ -14167,32 +14169,6 @@ CREATE SEQUENCE project_repositories_id_seq
ALTER SEQUENCE project_repositories_id_seq OWNED BY project_repositories.id;
CREATE TABLE project_repository_states (
id integer NOT NULL,
project_id integer NOT NULL,
repository_verification_checksum bytea,
wiki_verification_checksum bytea,
last_repository_verification_failure character varying,
last_wiki_verification_failure character varying,
repository_retry_at timestamp with time zone,
wiki_retry_at timestamp with time zone,
repository_retry_count integer,
wiki_retry_count integer,
last_repository_verification_ran_at timestamp with time zone,
last_wiki_verification_ran_at timestamp with time zone,
last_repository_updated_at timestamp with time zone,
last_wiki_updated_at timestamp with time zone
);
CREATE SEQUENCE project_repository_states_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE project_repository_states_id_seq OWNED BY project_repository_states.id;
CREATE TABLE project_repository_storage_moves (
id bigint NOT NULL,
created_at timestamp with time zone NOT NULL,
@ -19239,8 +19215,6 @@ ALTER TABLE ONLY project_relation_exports ALTER COLUMN id SET DEFAULT nextval('p
ALTER TABLE ONLY project_repositories ALTER COLUMN id SET DEFAULT nextval('project_repositories_id_seq'::regclass);
ALTER TABLE ONLY project_repository_states ALTER COLUMN id SET DEFAULT nextval('project_repository_states_id_seq'::regclass);
ALTER TABLE ONLY project_repository_storage_moves ALTER COLUMN id SET DEFAULT nextval('project_repository_storage_moves_id_seq'::regclass);
ALTER TABLE ONLY project_security_settings ALTER COLUMN project_id SET DEFAULT nextval('project_security_settings_project_id_seq'::regclass);
@ -21676,9 +21650,6 @@ ALTER TABLE ONLY project_relation_exports
ALTER TABLE ONLY project_repositories
ADD CONSTRAINT project_repositories_pkey PRIMARY KEY (id);
ALTER TABLE ONLY project_repository_states
ADD CONSTRAINT project_repository_states_pkey PRIMARY KEY (id);
ALTER TABLE ONLY project_repository_storage_moves
ADD CONSTRAINT project_repository_storage_moves_pkey PRIMARY KEY (id);
@ -23677,16 +23648,6 @@ CREATE UNIQUE INDEX idx_protected_branch_id_external_approval_rule_id ON externa
CREATE INDEX idx_reminder_frequency_on_work_item_progresses ON work_item_progresses USING btree (reminder_frequency);
CREATE INDEX idx_repository_states_on_last_repository_verification_ran_at ON project_repository_states USING btree (project_id, last_repository_verification_ran_at) WHERE ((repository_verification_checksum IS NOT NULL) AND (last_repository_verification_failure IS NULL));
CREATE INDEX idx_repository_states_on_last_wiki_verification_ran_at ON project_repository_states USING btree (project_id, last_wiki_verification_ran_at) WHERE ((wiki_verification_checksum IS NOT NULL) AND (last_wiki_verification_failure IS NULL));
CREATE INDEX idx_repository_states_on_repository_failure_partial ON project_repository_states USING btree (last_repository_verification_failure) WHERE (last_repository_verification_failure IS NOT NULL);
CREATE INDEX idx_repository_states_on_wiki_failure_partial ON project_repository_states USING btree (last_wiki_verification_failure) WHERE (last_wiki_verification_failure IS NOT NULL);
CREATE INDEX idx_repository_states_outdated_checksums ON project_repository_states USING btree (project_id) WHERE (((repository_verification_checksum IS NULL) AND (last_repository_verification_failure IS NULL)) OR ((wiki_verification_checksum IS NULL) AND (last_wiki_verification_failure IS NULL)));
CREATE INDEX idx_sbom_occurr_on_project_component_version_input_file_path ON sbom_occurrences USING btree (project_id, component_version_id, input_file_path);
CREATE INDEX idx_sbom_occurrences_on_project_id_and_source_id ON sbom_occurrences USING btree (project_id, source_id);
@ -26289,8 +26250,6 @@ CREATE INDEX index_project_repositories_on_shard_id ON project_repositories USIN
CREATE INDEX index_project_repositories_on_shard_id_and_project_id ON project_repositories USING btree (shard_id, project_id);
CREATE UNIQUE INDEX index_project_repository_states_on_project_id ON project_repository_states USING btree (project_id);
CREATE INDEX index_project_repository_storage_moves_on_project_id ON project_repository_storage_moves USING btree (project_id);
CREATE INDEX index_project_settings_on_legacy_os_license_project_id ON project_settings USING btree (project_id) WHERE (legacy_open_source_license_available = true);
@ -30566,9 +30525,6 @@ ALTER TABLE ONLY audit_events_google_cloud_logging_configurations
ALTER TABLE ONLY geo_node_statuses
ADD CONSTRAINT fk_rails_0ecc699c2a FOREIGN KEY (geo_node_id) REFERENCES geo_nodes(id) ON DELETE CASCADE;
ALTER TABLE ONLY project_repository_states
ADD CONSTRAINT fk_rails_0f2298ca8a FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
ALTER TABLE ONLY user_synced_attributes_metadata
ADD CONSTRAINT fk_rails_0f4aa0981f FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;

View File

@ -227,6 +227,18 @@ Consider the following notes:
- The scheduled weekly maintenance window is different from
[emergency maintenance](#emergency-maintenance).
#### GitLab release rollout schedule
GitLab Dedicated tenant instances are upgraded to the minor GitLab release using the following schedule.
Where **T** is the date of a [minor GitLab release](../../policy/maintenance.md) `N`. GitLab Dedicated instances are upgraded to the `N-1` release as follows:
1. At T+5 calendar days: Tenant instances in the `EMEA` and `AMER Option 1` maintenance window are upgraded.
1. At T+6 calendar days: Tenant instances in the `APAC` maintenance window are upgraded.
1. At T+10 calendar days: Tenant instances in the `AMER Option 2` maintenance window are upgraded.
For example, GitLab 16.9 released on 2024-02-15. Therefore, tenant instances in the `EMEA` and `AMER Option 1` maintenance window are upgraded on 2024-04-20.
#### Emergency maintenance
In an event of a platform outage, degradation or a security event requiring urgent action,

View File

@ -21226,7 +21226,7 @@ Contains release-related statistics about a group.
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="groupsavedreplycontent"></a>`content` | [`String!`](#string) | Content of the saved reply. |
| <a id="groupsavedreplyid"></a>`id` | [`GroupsSavedReplyID!`](#groupssavedreplyid) | Global ID of the group saved reply. |
| <a id="groupsavedreplyid"></a>`id` | [`GroupsSavedReplyID!`](#groupssavedreplyid) | Global ID of the group-level saved reply. |
| <a id="groupsavedreplyname"></a>`name` | [`String!`](#string) | Name of the saved reply. |
### `GroupSecurityPolicySource`

View File

@ -4,7 +4,7 @@ group: Threat Insights
info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments"
---
# Project Vulnerabilities API
# Project vulnerabilities API
DETAILS:
**Tier:** Ultimate

View File

@ -66,23 +66,14 @@ DETAILS:
> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/11180) in GitLab 16.7 with the [flags](../../administration/feature_flags.md) named `ci_data_ingestion_to_click_house` and `clickhouse_ci_analytics`. Disabled by default.
> - [Enabled by default](https://gitlab.com/gitlab-org/gitlab/-/issues/424866) in GitLab 16.8. Feature flag `clickhouse_ci_analytics` removed.
> - [Feature flags removed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/145665/diffs) in GitLab 16.10.
This feature is an [Experiment](../../policy/experiment-beta-support.md).
To test it, we have launched an early adopters program.
To join the list of users testing this feature, see
[epic 11180](https://gitlab.com/groups/gitlab-org/-/epics/11180).
### Enable ClickHouse integration
To enable additional CI analytics features:
1. [Configure ClickHouse integration](../../integration/clickhouse.md)
1. [Enable](../../administration/feature_flags.md#how-to-enable-and-disable-features-behind-flags) the following feature flags:
| Feature flag name | Purpose | Status |
|------------------------------------|---------------------------------------------------------------------------|------------------------------------|
| `ci_data_ingestion_to_click_house` | Enables synchronization of new finished CI builds to ClickHouse database. | Enabled by default in GitLab 16.8. |
| `clickhouse_ci_analytics` | Enables the **Wait time to pick a job** chart. | Removed in GitLab 16.8. |
To enable additional CI analytics features, [configure the ClickHouse integration](../../integration/clickhouse.md).
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
For a video walkthrough, see [Setting up Runner Fleet Dashboard with ClickHouse](https://www.youtube.com/watch?v=YpGV95Ctbpk).

View File

@ -85,12 +85,12 @@ write a `before_script` that queries the service.
Services stop at the end of the job, even if the job fails.
## What services are not for
## Using software provided by a service image
As mentioned before, this feature is designed to provide **network accessible**
When you specify the `service`, this provides **network accessible**
services. A database is the simplest example of such a service.
The services feature is not designed to, and does not, add any software from the
The services feature does not add any software from the
defined `services` images to the job's container.
For example, if you have the following `services` defined in your job, the `php`,

View File

@ -250,7 +250,7 @@ The following Elasticsearch settings are available:
| `Username` | The `username` of your Elasticsearch instance. |
| `Password` | The password of your Elasticsearch instance. |
| `Number of Elasticsearch shards and replicas per index` | Elasticsearch indices are split into multiple shards for performance reasons. In general, you should use at least five shards. Indices with tens of millions of documents should have more shards ([see the guidance](#guidance-on-choosing-optimal-cluster-configuration)). Changes to this value do not take effect until you re-create the index. For more information about scalability and resilience, see the [Elasticsearch documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/scalability.html). Each Elasticsearch shard can have a number of replicas. These replicas are a complete copy of the shard and can provide increased query performance or resilience against hardware failure. Increasing this value increases the total disk space required by the index. You can set the number of shards and replicas for each of the indices. |
| `Limit the number of namespaces and projects that can be indexed` | Enabling this allows you to select namespaces and projects to index. All other namespaces and projects use database search instead. If you enable this option but do not select any namespaces or projects, none are indexed. [Read more below](#limit-the-number-of-namespaces-and-projects-that-can-be-indexed).|
| `Limit the amount of namespace and project data to index` | When you enable this setting, you can specify namespaces and projects to index. All other namespaces and projects use database search instead. If you enable this setting but do not specify any namespace or project, [only project records are indexed](#all-project-records-are-indexed). For more information, see [Limit the amount of namespace and project data to index](#limit-the-amount-of-namespace-and-project-data-to-index). |
| `Using AWS OpenSearch Service with IAM credentials` | Sign your OpenSearch requests using [AWS IAM authorization](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html), [AWS EC2 Instance Profile Credentials](https://docs.aws.amazon.com/codedeploy/latest/userguide/getting-started-create-iam-instance-profile.html#getting-started-create-iam-instance-profile-cli), or [AWS ECS Tasks Credentials](https://docs.aws.amazon.com/AmazonECS/latest/userguide/task-iam-roles.html). Refer to [Identity and Access Management in Amazon OpenSearch Service](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/ac.html) for details of AWS hosted OpenSearch domain access policy configuration. |
| `AWS Region` | The AWS region in which your OpenSearch Service is located. |
| `AWS Access Key` | The AWS access key. |
@ -376,22 +376,15 @@ The following permissions are required for advanced search. See [Creating roles]
The index pattern `*` requires a few permissions for advanced search to work.
### Limit the number of namespaces and projects that can be indexed
### Limit the amount of namespace and project data to index
When you select the **Limit the number of namespaces and projects that can be indexed**
When you select the **Limit the amount of namespace and project data to index**
checkbox, you can specify namespaces and projects to index. If the namespace is a group,
any subgroups and projects belonging to those subgroups are also indexed.
![limit namespaces and projects options](img/limit_namespaces_projects_options.png)
Advanced search only provides cross-group code/commit search (global) if all name-spaces are indexed. In this particular scenario where only a subset of namespaces are indexed, a global search does not provide a code or commit scope. This is possible only in the scope of an indexed namespace. There is no way to code/commit search in multiple indexed namespaces (when only a subset of namespaces has been indexed). For example if two groups are indexed, there is no way to run a single code search on both. You can only run a code search on the first group and then on the second.
You can filter the selection dropdown list by writing part of the namespace or project name you're interested in.
![limit namespace filter](img/limit_namespace_filter.png)
NOTE:
If no namespaces or projects are selected, no advanced search indexing takes place.
If you do not specify any namespace or project, [only project records are indexed](#all-project-records-are-indexed).
WARNING:
If you have already indexed your instance, you must regenerate the index to delete all existing data
@ -399,6 +392,24 @@ for filtering to work correctly. To do this, run the Rake tasks `gitlab:elastic:
`gitlab:elastic:clear_index_status`. Afterwards, removing a namespace or a project from the list deletes the data
from the Elasticsearch index as expected.
#### All project records are indexed
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/428070) in GitLab 16.7 [with a flag](../../administration/feature_flags.md) named `search_index_all_projects`. Disabled by default.
> - [Enabled on GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/issues/432489) in GitLab 16.9.
> - [Enabled on self-managed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/145300) in GitLab 16.10.
FLAG:
On self-managed GitLab, by default this feature is available.
To hide the feature, an administrator can [disable the feature flag](../../administration/feature_flags.md) named `search_index_all_projects`.
On GitLab.com, this feature is available.
When you select the **Limit the amount of namespace and project data to index** checkbox:
- All project records are indexed.
- Associated data (issues, merge requests, or code) is not indexed.
If you do not specify any namespace or project, only project records are indexed.
## Enable custom language analyzers
You can improve the language support for Chinese and Japanese languages by utilizing [`smartcn`](https://www.elastic.co/guide/en/elasticsearch/plugins/current/analysis-smartcn.html) and/or [`kuromoji`](https://www.elastic.co/guide/en/elasticsearch/plugins/current/analysis-kuromoji.html) analysis plugins from Elastic.

View File

@ -219,7 +219,7 @@ If the results:
- Do not match up, this indicates a problem with the documents generated from the project. It is best to [re-index that project](../advanced_search/elasticsearch.md#indexing-a-range-of-projects-or-a-specific-project).
NOTE:
The above instructions are not to be used for scenarios that only index a [subset of namespaces](elasticsearch.md#limit-the-number-of-namespaces-and-projects-that-can-be-indexed).
The above instructions are not to be used for scenarios that only index a [subset of namespaces](elasticsearch.md#limit-the-amount-of-namespace-and-project-data-to-index).
See [Elasticsearch Index Scopes](elasticsearch.md#advanced-search-index-scopes) for more information on searching for specific types of data.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

View File

@ -84,7 +84,7 @@ Each bar on the chart displays the sum of total projects per score category, cal
To exclude data from the chart (for example, "Not Included"), in the legend select the series you want to exclude.
Hovering over each bar reveals a dialog that explains the score's definition.
For example, if a project has a high score for Deployment Frequency (Velocity), it means that the project has one or more deploys to production per day.
For example, if a project has a high score for deployment frequency (velocity), it means that the project has one or more deploys to production per day.
| Metric | Description | High | Medium | Low |
|--------|-------------|------|--------|-----|

View File

@ -158,26 +158,35 @@ After migration:
If you used a private network on your source instance to hide content from the general public,
make sure to have a similar setup on the destination instance, or to import into a private group.
## Ensure projects can be imported
You cannot import groups with projects when the source instance or group has **Default project creation protection** set to **No one**. If required, this setting can
be changed:
- For [a whole instance](../../../administration/settings/visibility_and_access_controls.md#define-which-roles-can-create-projects).
- For [specific groups](../index.md#specify-who-can-add-projects-to-a-group).
## Prerequisites
> - Requirement for Maintainer role instead of Developer role introduced in GitLab 16.0 and backported to GitLab 15.11.1 and GitLab 15.10.5.
To migrate groups by direct transfer:
Before migrating by using direct transfer, see the following prerequisites.
### Network
- The network connection between instances or GitLab.com must support HTTPS.
- Any firewalls must not block the connection between the source and destination GitLab instances.
- Firewalls must not block the connection between the source and destination GitLab instances.
### Versions
The source GitLab instance must be running GitLab 14.0 or later to import groups and GitLab 14.4 or later to import
projects. However, to maximize the chance of a successful and performant migration, you should:
- To take advantage of [batched exports and imports](https://gitlab.com/groups/gitlab-org/-/epics/9036) of relations,
update the source and destinations instances to GitLab 16.2 or later.
- Migrate between versions that are as new as possible. Update the source and destination instances to as late a version
as possible to take advantage of bug fixes and improvements added over time.
We have successfully tested migrations between a source instance running GitLab 16.2 and a destination instance running
GitLab 16.8.
### Configuration
- Both GitLab instances must have group migration by direct transfer
[enabled in application settings](../../../administration/settings/import_and_export_settings.md#enable-migration-of-groups-and-projects-by-direct-transfer)
by an instance administrator.
- The source GitLab instance must be running GitLab 14.0 or later.
- You must have a
[personal access token](../../../user/profile/personal_access_tokens.md) for
the source GitLab instance:
@ -192,6 +201,10 @@ To migrate groups by direct transfer:
- To import items stored in object storage, you must either:
- [Configure `proxy_download`](../../../administration/object_storage.md#configure-the-common-parameters).
- Ensure that the destination GitLab instance has access to the object storage of the source GitLab instance.
- You cannot import groups with projects when the source instance or group has **Default project creation protection** set
to **No one**. If required, this setting can be changed:
- For [a whole instance](../../../administration/settings/visibility_and_access_controls.md#define-which-roles-can-create-projects).
- For [specific groups](../index.md#specify-who-can-add-projects-to-a-group).
## Prepare user accounts
@ -231,7 +244,7 @@ role.
1. By default, the proposed group namespaces match the names as they exist in source instance, but based on your permissions, you can choose to edit these names before you proceed to import any of them.
1. Next to the groups you want to import, select either:
- **Import with projects**. If this is not available, see [Ensure projects can be imported](#ensure-projects-can-be-imported).
- **Import with projects**. If this is not available, see [prerequisites](#prerequisites).
- **Import without projects**.
1. The **Status** column shows the import status of each group. If you leave the page open, it updates in real-time.
1. After a group has been imported, select its GitLab path to open its GitLab URL.

View File

@ -14,13 +14,12 @@ module Gitlab
return false unless valid_token?
return Gitlab::SlashCommands::ApplicationHelp.new(nil, params).execute if help_command?
unless slack_integration = find_slack_integration
unless integration = find_slack_integration
error_message = 'GitLab error: project or alias not found'
return Gitlab::SlashCommands::Presenters::Error.new(error_message).message
end
chat_user = ChatNames::FindUserService.new(params[:team_id], params[:user_id]).execute
integration = slack_integration.integration
if chat_user&.user
Gitlab::SlashCommands::Command.new(integration.project, chat_user, params).execute
@ -46,11 +45,13 @@ module Gitlab
# rubocop: disable CodeReuse/ActiveRecord
def find_slack_integration
if project_alias.nil?
SlackIntegration.find_by(team_id: params[:team_id])
else
SlackIntegration.find_by(team_id: params[:team_id], alias: project_alias)
end
find_params = { team_id: params[:team_id], alias: project_alias }.compact
slack_app = SlackIntegration.find_by(find_params)
return unless slack_app
integration = slack_app.integration
integration if integration.project_level?
end
# rubocop: enable CodeReuse/ActiveRecord

View File

@ -120,8 +120,6 @@ namespace :gitlab do
end
def configure_clickhouse_databases
return unless Feature.enabled?(:run_clickhouse_migrations_automatically, type: :ops)
Rake::Task['gitlab:clickhouse:migrate'].invoke(true)
end

View File

@ -3595,7 +3595,7 @@ msgstr ""
msgid "AdminSettings|Let's Encrypt email"
msgstr ""
msgid "AdminSettings|Limit the number of namespaces and projects that can be indexed."
msgid "AdminSettings|Limit the amount of namespace and project data to index"
msgstr ""
msgid "AdminSettings|Maximum downstream pipelines triggered in a project per user"
@ -7711,7 +7711,7 @@ msgstr ""
msgid "Below are the settings for %{link_to_gitlab_pages}."
msgstr ""
msgid "Below you will find all the groups that are public. You can easily contribute to them by requesting to join these groups."
msgid "Below you will find all the groups that are public."
msgstr ""
msgid "Beta"
@ -20667,6 +20667,9 @@ msgstr ""
msgid "Failed to assign a user because no user was found."
msgstr ""
msgid "Failed to assign the framework to the project"
msgstr ""
msgid "Failed to assign you issues related to the merge request."
msgstr ""
@ -24232,16 +24235,10 @@ msgstr ""
msgid "GroupsEmptyState|Create new subgroup"
msgstr ""
msgid "GroupsEmptyState|Explore groups"
msgstr ""
msgid "GroupsEmptyState|Groups are the best way to manage multiple projects and members."
msgstr ""
msgid "GroupsEmptyState|If you organize your projects under a group, it works like a folder. You can manage the permissions and access of your group members for each project in the group."
msgstr ""
msgid "GroupsEmptyState|New group"
msgid "GroupsEmptyState|If you organize your projects under a group, it works like a folder. You can manage your group member's permissions and access to each project in the group."
msgstr ""
msgid "GroupsEmptyState|No archived projects."
@ -47095,6 +47092,9 @@ msgstr ""
msgid "SlackIntegration|- *Slash commands:* Quickly open, access, or close issues from Slack using the `%{slash_command}` command. Streamline your GitLab deployments with ChatOps."
msgstr ""
msgid "SlackIntegration|Access request canceled"
msgstr ""
msgid "SlackIntegration|Are you sure you want to unlink this Slack Workspace from this integration?"
msgstr ""
@ -47119,6 +47119,12 @@ msgstr ""
msgid "SlackIntegration|Download latest manifest file"
msgstr ""
msgid "SlackIntegration|Error exchanging OAuth token with Slack"
msgstr ""
msgid "SlackIntegration|Error exchanging OAuth token with Slack: %{error}"
msgstr ""
msgid "SlackIntegration|Generated for %{host} by GitLab %{version}."
msgstr ""
@ -47161,6 +47167,9 @@ msgstr ""
msgid "SlackIntegration|Signing secret"
msgstr ""
msgid "SlackIntegration|Slack app not enabled on GitLab instance"
msgstr ""
msgid "SlackIntegration|Step 1: Create your GitLab for Slack app"
msgstr ""
@ -47170,6 +47179,9 @@ msgstr ""
msgid "SlackIntegration|This integration allows users to perform common operations on their projects by entering slash commands in Slack."
msgstr ""
msgid "SlackIntegration|Unauthorized"
msgstr ""
msgid "SlackIntegration|Update to the latest version"
msgstr ""
@ -57227,6 +57239,9 @@ msgstr ""
msgid "You can create new ones at your Personal Access Tokens settings %{pat_link}"
msgstr ""
msgid "You can easily contribute to them by requesting to join these groups."
msgstr ""
msgid "You can enable Registration Features because Service Ping is enabled. To continue using Registration Features in the future, you will also need to register with GitLab via a new cloud licensing service."
msgstr ""

View File

@ -1,118 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Projects::Settings::SlacksController, feature_category: :integrations do
let_it_be_with_refind(:project) { create(:project, :public) }
let_it_be(:user) { create(:user) }
before do
project.add_maintainer(user)
sign_in(user)
end
def redirect_url(project)
edit_project_settings_integration_path(
project,
Integrations::GitlabSlackApplication.to_param
)
end
describe 'GET slack_auth' do
def stub_service(result)
service = double
expect(service).to receive(:execute).and_return(result)
expect(Projects::SlackApplicationInstallService)
.to receive(:new).with(project, user, anything).and_return(service)
end
context 'when valid CSRF token is provided' do
before do
allow(controller).to receive(:check_oauth_state).and_return(true)
end
it 'calls service and redirects with no alerts if result is successful' do
stub_service(status: :success)
get :slack_auth, params: { namespace_id: project.namespace, project_id: project }
expect(response).to have_gitlab_http_status(:found)
expect(response).to redirect_to(redirect_url(project))
expect(flash[:alert]).to be_nil
expect(session[:slack_install_success]).to be(true)
end
it 'calls service and redirects with the alert if there is error' do
stub_service(status: :error, message: 'error')
get :slack_auth, params: { namespace_id: project.namespace, project_id: project }
expect(response).to have_gitlab_http_status(:found)
expect(response).to redirect_to(redirect_url(project))
expect(flash[:alert]).to eq('error')
end
end
context 'when no CSRF token is provided' do
it 'returns 403' do
get :slack_auth, params: { namespace_id: project.namespace, project_id: project }
expect(response).to have_gitlab_http_status(:forbidden)
end
end
context 'when there was an OAuth error' do
it 'redirects with an alert' do
get :slack_auth, params: { namespace_id: project.namespace, project_id: project, error: 'access_denied' }
expect(flash[:alert]).to eq('Access denied')
expect(response).to have_gitlab_http_status(:found)
expect(response).to redirect_to(redirect_url(project))
end
end
end
describe 'POST update' do
let_it_be(:integration) { create(:gitlab_slack_application_integration, project: project) }
let(:params) do
{ namespace_id: project.namespace, project_id: project, slack_integration: { alias: new_alias } }
end
context 'when alias is valid' do
let(:new_alias) { 'foo' }
it 'updates the record' do
expect do
post :update, params: params
end.to change { integration.reload.slack_integration.alias }.to(new_alias)
expect(response).to have_gitlab_http_status(:found)
expect(response).to redirect_to(redirect_url(project))
end
end
context 'when alias is invalid' do
let(:new_alias) { '' }
it 'does not update the record' do
expect do
post :update, params: params
end.not_to change { integration.reload.slack_integration.alias }
expect(response).to have_gitlab_http_status(:ok)
expect(response).to render_template('projects/settings/slacks/edit')
end
end
end
describe 'DELETE destroy' do
it 'destroys the record' do
create(:gitlab_slack_application_integration, project: project)
expect do
delete :destroy, params: { namespace_id: project.namespace, project_id: project }
end.to change { project.gitlab_slack_application_integration.reload.slack_integration }.to(nil)
expect(response).to have_gitlab_http_status(:found)
expect(response).to redirect_to(redirect_url(project))
end
end
end

View File

@ -7,15 +7,15 @@ RSpec.describe 'Dashboard Group', feature_category: :groups_and_projects do
sign_in(create(:user))
end
it 'shows empty state', :js do
it 'defaults sort dropdown to last created' do
visit dashboard_groups_path
expect(page).to have_selector('[data-testid="groups-empty-state"]')
expect(page).to have_button('Last created')
end
it 'creates new group', :js do
visit dashboard_groups_path
click_link 'New group'
find_by_testid('new-group-button').click
click_link 'Create group'
new_name = 'Samurai'
@ -26,14 +26,4 @@ RSpec.describe 'Dashboard Group', feature_category: :groups_and_projects do
expect(page).to have_current_path group_path(Group.find_by(name: new_name)), ignore_query: true
expect(page).to have_content(new_name)
end
it 'defaults sort dropdown to last created' do
user = create(:user)
group = create(:group)
group.add_owner(user)
sign_in(user)
visit dashboard_groups_path
expect(page).to have_selector('[data-testid="group_sort_by_dropdown"]')
end
end

View File

@ -32,7 +32,7 @@ RSpec.describe 'Dashboard shortcuts', :js, feature_category: :shared do
find('body').send_keys([:shift, 'G'])
expect(page).to have_selector('.js-groups-list-holder')
check_page_title('Groups')
find('body').send_keys([:shift, 'P'])

View File

@ -70,6 +70,26 @@ RSpec.describe 'Explore Groups page', :js, feature_category: :groups_and_project
# Check project count
expect(find('.js-groups-list-holder .groups-list li:first-child .stats .number-projects')).to have_text("1")
end
describe 'landing component' do
it 'shows a landing component' do
expect(page).to have_content('Below you will find all the groups that are public.')
end
it 'is dismissable' do
find('.dismiss-button').click
expect(page).not_to have_content('Below you will find all the groups that are public.')
end
it 'does not show persistently once dismissed' do
find('.dismiss-button').click
visit explore_groups_path
expect(page).not_to have_content('Below you will find all the groups that are public.')
end
end
end
context 'when there are no groups to show' do

View File

@ -58,6 +58,9 @@ describe('AppComponent', () => {
mocks: {
$toast,
},
provide: {
emptySearchIllustration: '/assets/illustrations/empty-state/empty-search-md.svg',
},
});
vm = wrapper.vm;
};

View File

@ -7,8 +7,6 @@ let wrapper;
const defaultProvide = {
groupsEmptyStateIllustration: '/assets/illustrations/empty-state/empty-groups-md.svg',
newGroupPath: '/groups/new',
exploreGroupsPath: '/explore/groups',
};
const createComponent = () => {
@ -24,12 +22,8 @@ describe('GroupsDashboardEmptyState', () => {
expect(wrapper.findComponent(GlEmptyState).props()).toMatchObject({
title: 'A group is a collection of several projects',
description:
'If you organize your projects under a group, it works like a folder. You can manage the permissions and access of your group members for each project in the group.',
"If you organize your projects under a group, it works like a folder. You can manage your group member's permissions and access to each project in the group.",
svgPath: defaultProvide.groupsEmptyStateIllustration,
primaryButtonText: 'New group',
primaryButtonLink: defaultProvide.newGroupPath,
secondaryButtonText: 'Explore groups',
secondaryButtonLink: defaultProvide.exploreGroupsPath,
});
});
});

View File

@ -45,6 +45,7 @@ describe('OverviewTabs', () => {
newProjectIllustration: '',
emptyProjectsIllustration: '',
emptySubgroupIllustration: '',
emptySearchIllustration: '',
canCreateSubgroups: false,
canCreateProjects: false,
initialSort: 'name_asc',

View File

@ -1,4 +1,4 @@
import { GlForm, GlButton, GlLink } from '@gitlab/ui';
import { GlForm } from '@gitlab/ui';
import Vue from 'vue';
// eslint-disable-next-line no-restricted-imports
import Vuex from 'vuex';
@ -52,8 +52,8 @@ describe('GlobalSearchSidebarLanguageFilter', () => {
};
const findForm = () => wrapper.findComponent(GlForm);
const findApplyButton = () => wrapper.findComponent(GlButton);
const findResetButton = () => wrapper.findComponent(GlLink);
const findApplyButton = () => wrapper.findComponentByTestId('search-apply-filters-btn');
const findResetButton = () => wrapper.findComponentByTestId('search-reset-filters-btn');
const findSlotContent = () => wrapper.findByText('Filters Content');
describe('Renders correctly', () => {

View File

@ -490,6 +490,7 @@ RSpec.describe GroupsHelper, feature_category: :groups_and_projects do
new_project_illustration: including('illustrations/project-create-new-sm'),
empty_projects_illustration: including('illustrations/empty-state/empty-projects-md'),
empty_subgroup_illustration: including('illustrations/empty-state/empty-subgroup-md'),
empty_search_illustration: including('illustrations/empty-state/empty-search-md'),
render_empty_state: 'true',
can_create_subgroups: 'true',
can_create_projects: 'true'

View File

@ -114,7 +114,7 @@ RSpec.describe IntegrationsHelper, feature_category: :integrations do
expect(subject[:upgrade_slack_url]).to start_with(
[
Projects::SlackApplicationInstallService::SLACK_AUTHORIZE_URL,
Integrations::SlackInstallation::BaseService::SLACK_AUTHORIZE_URL,
'?client_id=MOCK_APP_ID',
"&redirect_uri=#{CGI.escape(redirect_url)}"
].join
@ -232,8 +232,8 @@ RSpec.describe IntegrationsHelper, feature_category: :integrations do
it 'returns the endpoint URL with all needed params' do
expect(helper).to receive(:slack_auth_project_settings_slack_url).and_return('http://redirect')
expect(slack_link).to start_with(Projects::SlackApplicationInstallService::SLACK_AUTHORIZE_URL)
expect(slack_link).to include('&state=a+token')
expect(slack_link).to include('&state=a%20token')
expect(slack_link).to start_with(Integrations::SlackInstallation::BaseService::SLACK_AUTHORIZE_URL)
expect(query).to include(
'scope' => 'commands,chat:write,chat:write.public',

View File

@ -35,7 +35,7 @@ RSpec.describe Projects::ErrorTrackingHelper do
'integrated-error-tracking-enabled' => 'false',
'list-path' => list_path,
'project-path' => project_path,
'illustration-path' => match_asset_path('/assets/illustrations/cluster_popover.svg'),
'illustration-path' => match_asset_path('/assets/illustrations/empty-state/empty-radar-md.svg'),
'show-integrated-tracking-disabled-alert' => 'false'
)
end

View File

@ -60,7 +60,10 @@ RSpec.describe Gitlab::SlashCommands::GlobalSlackHandler, feature_category: :int
it 'returns error if project alias not found' do
expect_next(Gitlab::SlashCommands::Command).not_to receive(:execute)
expect_next(Gitlab::SlashCommands::Presenters::Error).to receive(:message)
expect_next(
Gitlab::SlashCommands::Presenters::Error,
'GitLab error: project or alias not found'
).to receive(:message)
handler_with_valid_token(
text: "fake/fake issue new title",
@ -87,5 +90,49 @@ RSpec.describe Gitlab::SlashCommands::GlobalSlackHandler, feature_category: :int
text: "help"
).trigger
end
context 'when integration is group-level' do
let_it_be(:group) { create(:group) }
let_it_be_with_reload(:slack_integration) do
create(:gitlab_slack_application_integration, :group, group: group,
slack_integration: build(:slack_integration, alias: group.full_path)
).slack_integration
end
it 'returns error that the project alias not found' do
expect_next(Gitlab::SlashCommands::Command).not_to receive(:execute)
expect_next(
Gitlab::SlashCommands::Presenters::Error,
'GitLab error: project or alias not found'
).to receive(:message)
handler_with_valid_token(
text: "#{group.full_path} issue new title",
team_id: slack_integration.team_id
).trigger
end
end
context 'when integration is instance-level' do
let_it_be_with_reload(:slack_integration) do
create(:gitlab_slack_application_integration, :instance,
slack_integration: build(:slack_integration, alias: '_gitlab-instance')
).slack_integration
end
it 'returns error that the project alias not found' do
expect_next(Gitlab::SlashCommands::Command).not_to receive(:execute)
expect_next(
Gitlab::SlashCommands::Presenters::Error,
'GitLab error: project or alias not found'
).to receive(:message)
handler_with_valid_token(
text: "instance issue new title",
team_id: slack_integration.team_id
).trigger
end
end
end
end

View File

@ -3,61 +3,29 @@
require 'spec_helper'
RSpec.describe Admin::SlacksController, :enable_admin_mode, feature_category: :integrations do
let_it_be(:admin) { create(:admin) }
let_it_be(:user) { create(:user) }
let_it_be(:user) { create(:admin) }
before do
stub_application_setting(slack_app_enabled: true)
sign_in(user)
end
def redirect_url
edit_admin_application_settings_integration_path(
Integrations::GitlabSlackApplication.to_param
)
end
it_behaves_like Integrations::SlackControllerSettings do
let(:slack_auth_path) { slack_auth_admin_application_settings_slack_path }
let(:destroy_path) { admin_application_settings_slack_path }
let(:service) { Integrations::SlackInstallation::InstanceService }
let(:flag_protected) { true }
describe 'DELETE destroy' do
subject(:destroy!) { delete admin_application_settings_slack_path }
context 'when user is not an admin' do
before_all do
sign_in(user)
end
it 'responds with status :not_found' do
destroy!
expect(response).to have_gitlab_http_status(:not_found)
end
let(:redirect_url) do
edit_admin_application_settings_integration_path(
Integrations::GitlabSlackApplication.to_param
)
end
context 'when user is an admin' do
before do
sign_in(admin)
end
it 'destroys the record and redirects back to #edit' do
create(:gitlab_slack_application_integration, :instance,
slack_integration: build(:slack_integration)
)
expect { destroy! }
.to change { Integrations::GitlabSlackApplication.for_instance.first&.slack_integration }.to(nil)
expect(response).to have_gitlab_http_status(:found)
expect(response).to redirect_to(redirect_url)
end
context 'when the flag is disabled' do
before do
stub_feature_flags(gitlab_for_slack_app_instance_and_group_level: false)
end
it 'responds with status :not_found' do
destroy!
expect(response).to have_gitlab_http_status(:not_found)
end
end
def create_integration
create(:gitlab_slack_application_integration, :instance,
slack_integration: build(:slack_integration)
)
end
end
end

View File

@ -6,60 +6,32 @@ RSpec.describe Groups::Settings::SlacksController, feature_category: :integratio
let_it_be(:group) { create(:group) }
let_it_be(:user) { create(:user) }
before_all do
group.add_owner(user)
end
before do
sign_in(user)
stub_application_setting(slack_app_enabled: true)
end
def redirect_url(group)
edit_group_settings_integration_path(
group,
Integrations::GitlabSlackApplication.to_param
)
end
it_behaves_like Integrations::SlackControllerSettings do
let(:slack_auth_path) { slack_auth_group_settings_slack_path(group) }
let(:destroy_path) { group_settings_slack_path(group) }
let(:service) { Integrations::SlackInstallation::GroupService }
let(:flag_protected) { true }
describe 'DELETE destroy' do
subject(:destroy!) { delete group_settings_slack_path(group) }
context 'when user is not an admin' do
before_all do
group.add_developer(user)
end
it 'responds with status :not_found' do
destroy!
expect(response).to have_gitlab_http_status(:not_found)
end
let(:redirect_url) do
edit_group_settings_integration_path(
group,
Integrations::GitlabSlackApplication.to_param
)
end
context 'when user is an admin' do
before_all do
group.add_owner(user)
end
it 'destroys the record and redirects back to #edit' do
create(:gitlab_slack_application_integration, :group, group: group,
slack_integration: build(:slack_integration)
)
expect { destroy! }
.to change { Integrations::GitlabSlackApplication.for_group(group).first&.slack_integration }.to(nil)
expect(response).to have_gitlab_http_status(:found)
expect(response).to redirect_to(redirect_url(group))
end
context 'when the flag is disabled' do
before do
stub_feature_flags(gitlab_for_slack_app_instance_and_group_level: false)
end
it 'responds with status :not_found' do
destroy!
expect(response).to have_gitlab_http_status(:not_found)
end
end
def create_integration
create(:gitlab_slack_application_integration, :group, group: group,
slack_integration: build(:slack_integration)
)
end
end
end

View File

@ -0,0 +1,71 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Projects::Settings::SlacksController, feature_category: :integrations do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
let(:redirect_url) do
edit_project_settings_integration_path(
project,
Integrations::GitlabSlackApplication.to_param
)
end
before_all do
project.add_maintainer(user)
end
before do
sign_in(user)
end
it_behaves_like Integrations::SlackControllerSettings do
let(:slack_auth_path) { slack_auth_project_settings_slack_path(project) }
let(:destroy_path) { project_settings_slack_path(project) }
let(:service) { Integrations::SlackInstallation::ProjectService }
let(:flag_protected) { false }
def create_integration
create(:gitlab_slack_application_integration, project: project)
end
end
describe 'PUT update' do
let_it_be(:integration) { create(:gitlab_slack_application_integration, project: project) }
let(:new_alias) { 'foo' }
subject(:put_update) do
put project_settings_slack_path(project), params: { slack_integration: { alias: new_alias } }
end
it 'updates the record' do
expect { put_update }.to change { integration.reload.slack_integration.alias }.to(new_alias)
expect(flash[:notice]).to eq('The project alias was updated successfully')
expect(response).to have_gitlab_http_status(:found)
expect(response).to redirect_to(redirect_url)
end
context 'when alias is invalid' do
let(:new_alias) { '' }
it 'does not update the record' do
expect { put_update }.not_to change { integration.reload.slack_integration.alias }
expect(response).to have_gitlab_http_status(:ok)
expect(response).to render_template('projects/settings/slacks/edit')
end
end
context 'when user is unauthorized' do
let_it_be(:user) { create(:user) }
it 'returns not found response' do
put_update
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
end

View File

@ -0,0 +1,25 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Integrations::SlackInstallation::GroupService, feature_category: :integrations do
let_it_be_with_refind(:group) { create(:group) }
let_it_be(:user) { create(:user) }
let(:params) { {} }
subject(:service) { described_class.new(group, current_user: user, params: params) }
before_all do
group.add_owner(user)
end
it_behaves_like Integrations::SlackInstallation::BaseService do
let(:installation_alias) { group.full_path }
let(:integration) { Integrations::GitlabSlackApplication.for_group(group).first }
let(:redirect_url) { Gitlab::Routing.url_helpers.slack_auth_group_settings_slack_url(group) }
def create_gitlab_slack_application_integration!
Integrations::GitlabSlackApplication.create!(group: group)
end
end
end

View File

@ -0,0 +1,20 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Integrations::SlackInstallation::InstanceService, :enable_admin_mode, feature_category: :integrations do
let_it_be(:user) { create(:admin) }
let(:params) { {} }
subject(:service) { described_class.new(current_user: user, params: params) }
it_behaves_like Integrations::SlackInstallation::BaseService do
let(:installation_alias) { '_gitlab-instance' }
let(:integration) { Integrations::GitlabSlackApplication.for_instance.first }
let(:redirect_url) { Gitlab::Routing.url_helpers.slack_auth_admin_application_settings_slack_url }
def create_gitlab_slack_application_integration!
Integrations::GitlabSlackApplication.create!(instance: true)
end
end
end

View File

@ -0,0 +1,25 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Integrations::SlackInstallation::ProjectService, feature_category: :integrations do
let_it_be_with_refind(:project) { create(:project) }
let_it_be(:user) { create(:user) }
let(:params) { {} }
subject(:service) { described_class.new(project, current_user: user, params: params) }
before_all do
project.add_owner(user)
end
it_behaves_like Integrations::SlackInstallation::BaseService do
let(:installation_alias) { project.full_path }
let(:integration) { project.gitlab_slack_application_integration }
let(:redirect_url) { Gitlab::Routing.url_helpers.slack_auth_project_settings_slack_url(project) }
def create_gitlab_slack_application_integration!
project.create_gitlab_slack_application_integration!
end
end
end

View File

@ -1876,7 +1876,6 @@
- './ee/spec/replicators/geo/package_file_replicator_spec.rb'
- './ee/spec/replicators/geo/pages_deployment_replicator_spec.rb'
- './ee/spec/replicators/geo/pipeline_artifact_replicator_spec.rb'
- './ee/spec/replicators/geo/pipeline_replicator_spec.rb'
- './ee/spec/replicators/geo/snippet_repository_replicator_spec.rb'
- './ee/spec/replicators/geo/terraform_state_version_replicator_spec.rb'
- './ee/spec/replicators/geo/upload_replicator_spec.rb'

View File

@ -0,0 +1,129 @@
# frozen_string_literal: true
RSpec.shared_examples_for Integrations::SlackControllerSettings do
let(:flag_protected) { false }
describe 'GET slack_auth' do
subject(:get_slack_auth) { get slack_auth_path }
context 'when valid CSRF token is provided' do
before do
allow_next_instance_of(described_class) do |controller|
allow(controller).to receive(:valid_authenticity_token?).and_return(true)
end
end
it 'calls service and redirects with no alerts if result is successful' do
expect_next_instance_of(service) do |service_instance|
expect(service_instance).to receive(:execute).and_return(ServiceResponse.success)
end
get_slack_auth
expect(response).to have_gitlab_http_status(:found)
expect(response).to redirect_to(redirect_url)
expect(flash[:alert]).to be_nil
expect(session[:slack_install_success]).to be(true)
end
it 'calls service and redirects with an alert if there is a service error' do
expect_next_instance_of(service) do |service_instance|
expect(service_instance).to receive(:execute).and_return(ServiceResponse.error(message: 'error'))
end
get_slack_auth
expect(response).to have_gitlab_http_status(:found)
expect(response).to redirect_to(redirect_url)
expect(flash[:alert]).to eq('error')
end
context 'when the flag is disabled' do
before do
skip unless flag_protected
stub_feature_flags(gitlab_for_slack_app_instance_and_group_level: false)
end
it 'responds with status :not_found' do
expect(service).not_to receive(:new)
get_slack_auth
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'when user is unauthorized' do
let_it_be(:user) { create(:user) }
it 'returns not found response' do
expect(service).not_to receive(:new)
get_slack_auth
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
context 'when no CSRF token is provided' do
it 'returns 403' do
expect(service).not_to receive(:new)
get_slack_auth
expect(response).to have_gitlab_http_status(:forbidden)
end
end
context 'when there was an OAuth error' do
it 'redirects with an alert' do
expect(service).not_to receive(:new)
get "#{slack_auth_path}?error=access_denied"
expect(flash[:alert]).to eq('Access request canceled')
expect(response).to have_gitlab_http_status(:found)
expect(response).to redirect_to(redirect_url)
end
end
end
describe 'DELETE destroy' do
subject(:delete_destroy) { delete destroy_path }
it 'destroys the record and redirects back to #edit' do
integration = create_integration
expect { delete_destroy }.to change { integration.reload.slack_integration }.to(nil)
expect(response).to have_gitlab_http_status(:found)
expect(response).to redirect_to(redirect_url)
end
context 'when the flag is disabled' do
before do
skip unless flag_protected
stub_feature_flags(gitlab_for_slack_app_instance_and_group_level: false)
end
it 'responds with status :not_found' do
integration = create_integration
expect { delete_destroy }
.not_to change { integration.reload.slack_integration }
.from(kind_of(SlackIntegration))
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'when user is unauthorized' do
let_it_be(:user) { create(:user) }
it 'returns not found response' do
delete_destroy
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
end

View File

@ -1,25 +1,19 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Projects::SlackApplicationInstallService, feature_category: :integrations do
let_it_be(:user) { create(:user) }
let_it_be_with_refind(:project) { create(:project) }
let(:integration) { project.gitlab_slack_application_integration }
let(:installation) { integration.slack_integration }
RSpec.shared_examples_for Integrations::SlackInstallation::BaseService do
let(:slack_app_id) { 'A12345' }
let(:slack_app_secret) { 'secret' }
let(:oauth_code) { 'code' }
let(:params) { { code: oauth_code } }
let(:exchange_url) { described_class::SLACK_EXCHANGE_TOKEN_URL }
let(:redirect_url) { Gitlab::Routing.url_helpers.slack_auth_project_settings_slack_url(project) }
subject(:service) { described_class.new(project, user, params) }
let(:installation) { integration.slack_integration }
before do
stub_application_setting(slack_app_id: slack_app_id, slack_app_secret: slack_app_secret)
stub_application_setting(
slack_app_enabled: true,
slack_app_id: slack_app_id,
slack_app_secret: slack_app_secret
)
query = {
client_id: slack_app_id,
@ -33,6 +27,16 @@ RSpec.describe Projects::SlackApplicationInstallService, feature_category: :inte
.to_return(body: response.to_json, headers: { 'Content-Type' => 'application/json' })
end
shared_examples 'error response' do |message:|
it 'returns error result with message' do
result = service.execute
expect(result).to be_error
expect(result.message).to eq(message)
expect(integration).to be_nil
end
end
context 'when Slack responds with an error' do
let(:response) do
{
@ -41,11 +45,43 @@ RSpec.describe Projects::SlackApplicationInstallService, feature_category: :inte
}
end
it 'returns error result' do
result = service.execute
it_behaves_like 'error response', message: 'Error exchanging OAuth token with Slack: something is wrong'
end
expect(result).to eq(message: 'Slack: something is wrong', status: :error)
context 'when HTTP error occurs when exchanging token' do
let(:response) { {} }
before do
allow(Gitlab::HTTP).to receive(:get).and_raise(Errno::ECONNREFUSED.new('error'))
end
it_behaves_like 'error response', message: 'Error exchanging OAuth token with Slack'
it 'tracks the error' do
expect(Gitlab::ErrorTracking)
.to receive(:track_exception)
.with(Errno::ECONNREFUSED.new('Error exchanging OAuth token with Slack'), kind_of(Hash))
service.execute
end
end
context 'when slack_app_enabled is not set' do
before do
stub_application_setting(slack_app_enabled: false)
end
let(:response) { {} }
it_behaves_like 'error response', message: 'Slack app not enabled on GitLab instance'
end
context 'when user is unauthorized' do
let_it_be(:user) { create(:user) }
let(:response) { {} }
it_behaves_like 'error response', message: 'Unauthorized'
end
context 'when Slack responds with an access token' do
@ -73,14 +109,14 @@ RSpec.describe Projects::SlackApplicationInstallService, feature_category: :inte
it 'returns success result and creates all needed records' do
result = service.execute
expect(result).to eq(status: :success)
expect(integration).to be_present
expect(installation).to be_present
expect(result).to be_success
expect(integration.reload).to be_present
expect(installation.reload).to be_present
expect(installation).to have_attributes(
integration_id: integration.id,
team_id: team_id,
team_name: team_name,
alias: project.full_path,
alias: installation_alias,
user_id: user_id,
bot_user_id: bot_user_id,
bot_access_token: bot_access_token,
@ -93,7 +129,7 @@ RSpec.describe Projects::SlackApplicationInstallService, feature_category: :inte
context 'when integration record already exists' do
before do
project.create_gitlab_slack_application_integration!
create_gitlab_slack_application_integration!
end
it_behaves_like 'success response'

View File

@ -376,14 +376,6 @@ RSpec.describe 'gitlab:db namespace rake task', :silence_stdout, feature_categor
run_rake_task('gitlab:db:configure')
end
it 'does not run clickhouse migrations if feature flag is disabled' do
stub_feature_flags(run_clickhouse_migrations_automatically: false)
expect(Rake::Task['gitlab:clickhouse:migrate']).not_to receive(:invoke)
run_rake_task('gitlab:db:configure')
end
it 'does not fail if clickhouse is not configured' do
allow(::ClickHouse::Client).to receive(:configuration).and_return(::ClickHouse::Client::Configuration.new)