Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
4b86353c0b
commit
4dd83293db
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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') } }
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1 @@
|
|||
05b715f6532afae295aa8fe67eeff166a76fe052f36a75feff8496662afd4280
|
||||
|
|
@ -0,0 +1 @@
|
|||
c8ce22627e708d6e9d1b967adc7ed83827a589069b729ec2a3f3abcd36af5867
|
||||
|
|
@ -0,0 +1 @@
|
|||
39f4d9aaf4a2d22f649fe1f0a75ef1fd4cdd9c0066d4e359c8375e8e301400b4
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
|
|
|||
|
|
@ -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`,
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||

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

|
||||
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -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 |
|
|
@ -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 |
|
||||
|--------|-------------|------|--------|-----|
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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 ""
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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'])
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -58,6 +58,9 @@ describe('AppComponent', () => {
|
|||
mocks: {
|
||||
$toast,
|
||||
},
|
||||
provide: {
|
||||
emptySearchIllustration: '/assets/illustrations/empty-state/empty-search-md.svg',
|
||||
},
|
||||
});
|
||||
vm = wrapper.vm;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ describe('OverviewTabs', () => {
|
|||
newProjectIllustration: '',
|
||||
emptyProjectsIllustration: '',
|
||||
emptySubgroupIllustration: '',
|
||||
emptySearchIllustration: '',
|
||||
canCreateSubgroups: false,
|
||||
canCreateProjects: false,
|
||||
initialSort: 'name_asc',
|
||||
|
|
|
|||
|
|
@ -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', () => {
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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'
|
||||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue