diff --git a/app/assets/javascripts/diffs/components/app.vue b/app/assets/javascripts/diffs/components/app.vue
index 9971d3bf7f8..4331260db99 100644
--- a/app/assets/javascripts/diffs/components/app.vue
+++ b/app/assets/javascripts/diffs/components/app.vue
@@ -61,8 +61,14 @@ import VirtualScrollerScrollSync from './virtual_scroller_scroll_sync';
import DiffsFileTree from './diffs_file_tree.vue';
import getMRCodequalityAndSecurityReports from './graphql/get_mr_codequality_and_security_reports.query.graphql';
+export const FINDINGS_STATUS_PARSED = 'PARSED';
+export const FINDINGS_STATUS_ERROR = 'ERROR';
+export const FINDINGS_POLL_INTERVAL = 1000;
+
export default {
name: 'DiffsApp',
+ FINDINGS_STATUS_PARSED,
+ FINDINGS_STATUS_ERROR,
components: {
DiffsFileTree,
FindingsDrawer,
@@ -145,6 +151,7 @@ export default {
apollo: {
getMRCodequalityAndSecurityReports: {
query: getMRCodequalityAndSecurityReports,
+ pollInterval: FINDINGS_POLL_INTERVAL,
variables() {
return { fullPath: this.projectPath, iid: this.iid };
},
@@ -154,23 +161,37 @@ export default {
return !this.sastReportsInInlineDiff || (!codeQualityBoolean && !this.sastReportAvailable);
},
update(data) {
- if (data?.project?.mergeRequest?.codequalityReportsComparer?.report?.newErrors) {
- this.$store.commit(
- 'diffs/SET_CODEQUALITY_DATA',
- sortFindingsByFile(
- data.project.mergeRequest.codequalityReportsComparer.report.newErrors,
- ),
+ const codeQualityBoolean = Boolean(this.endpointCodequality);
+ const { codequalityReportsComparer, sastReport } = data?.project?.mergeRequest || {};
+
+ if (
+ (sastReport?.status === FINDINGS_STATUS_PARSED || !this.sastReportAvailable) &&
+ /* Checking for newErrors instead of a status indicator is a workaround that
+ needs to be adjusted once https://gitlab.com/gitlab-org/gitlab/-/issues/429527 is resolved. */
+ (!codeQualityBoolean || codequalityReportsComparer?.report?.newErrors.length > 0)
+ ) {
+ this.getMRCodequalityAndSecurityReportStopPolling(
+ this.$apollo.queries.getMRCodequalityAndSecurityReports,
);
}
- if (data.project?.mergeRequest?.sastReport?.report) {
- this.$store.commit('diffs/SET_SAST_DATA', data.project.mergeRequest.sastReport.report);
+ if (sastReport?.status === FINDINGS_STATUS_ERROR && this.sastReportAvailable) {
+ this.fetchScannerFindingsError();
+ }
+
+ if (codequalityReportsComparer?.report?.newErrors) {
+ this.$store.commit(
+ 'diffs/SET_CODEQUALITY_DATA',
+ sortFindingsByFile(codequalityReportsComparer.report.newErrors),
+ );
+ }
+
+ if (sastReport?.report) {
+ this.$store.commit('diffs/SET_SAST_DATA', sastReport.report);
}
},
error() {
- createAlert({
- message: __('Something went wrong fetching the Scanner Findings. Please try again!'),
- });
+ this.fetchScannerFindingsError();
},
},
},
@@ -410,6 +431,11 @@ export default {
closeDrawer() {
this.setDrawer({});
},
+ fetchScannerFindingsError() {
+ createAlert({
+ message: __('Something went wrong fetching the Scanner Findings. Please try again.'),
+ });
+ },
subscribeToEvents() {
notesEventHub.$once('fetchDiffData', this.fetchData);
notesEventHub.$on('refetchDiffData', this.refetchDiffData);
@@ -419,6 +445,9 @@ export default {
diffsEventHub.$on(EVT_MR_PREPARED, this.fetchData);
diffsEventHub.$on(EVT_DISCUSSIONS_ASSIGNED, this.handleHash);
},
+ getMRCodequalityAndSecurityReportStopPolling(query) {
+ query.stopPolling();
+ },
unsubscribeFromEvents() {
diffsEventHub.$off(EVT_DISCUSSIONS_ASSIGNED, this.handleHash);
diffsEventHub.$off(EVT_MR_PREPARED, this.fetchData);
@@ -494,7 +523,7 @@ export default {
})
.catch(() => {
createAlert({
- message: __('Something went wrong on our end. Please try again!'),
+ message: __('Something went wrong on our end. Please try again.'),
});
});
}
@@ -511,7 +540,7 @@ export default {
})
.catch(() => {
createAlert({
- message: __('Something went wrong on our end. Please try again!'),
+ message: __('Something went wrong on our end. Please try again.'),
});
});
}
diff --git a/app/assets/javascripts/environments/components/environments_app.vue b/app/assets/javascripts/environments/components/environments_app.vue
index fd5fcb12cc5..4e8b75536a4 100644
--- a/app/assets/javascripts/environments/components/environments_app.vue
+++ b/app/assets/javascripts/environments/components/environments_app.vue
@@ -78,7 +78,7 @@ export default {
newEnvironmentButtonLabel: s__('Environments|New environment'),
reviewAppButtonLabel: s__('Environments|Enable review apps'),
cleanUpEnvsButtonLabel: s__('Environments|Clean up environments'),
- available: __('Available'),
+ active: __('Active'),
stopped: __('Stopped'),
prevPage: __('Go to previous page'),
nextPage: __('Go to next page'),
@@ -97,9 +97,7 @@ export default {
isStopStaleEnvModalVisible: false,
page: parseInt(page, 10),
pageInfo: {},
- scope: Object.values(ENVIRONMENTS_SCOPE).includes(scope)
- ? scope
- : ENVIRONMENTS_SCOPE.AVAILABLE,
+ scope: Object.values(ENVIRONMENTS_SCOPE).includes(scope) ? scope : ENVIRONMENTS_SCOPE.ACTIVE,
environmentToDelete: {},
environmentToRollback: {},
environmentToStop: {},
@@ -133,14 +131,14 @@ export default {
hasSearch() {
return Boolean(this.search);
},
- availableCount() {
- return this.environmentApp?.availableCount;
+ activeCount() {
+ return this.environmentApp?.activeCount ?? 0;
},
stoppedCount() {
- return this.environmentApp?.stoppedCount;
+ return this.environmentApp?.stoppedCount ?? 0;
},
hasAnyEnvironment() {
- return this.availableCount > 0 || this.stoppedCount > 0;
+ return this.activeCount > 0 || this.stoppedCount > 0;
},
showContent() {
return this.hasAnyEnvironment || this.hasSearch;
@@ -278,13 +276,13 @@ export default {
@primary="showCleanUpEnvsModal"
>
- {{ $options.i18n.available }}
+ {{ $options.i18n.active }}
- {{ availableCount }}
+ {{ activeCount }}
diff --git a/app/assets/javascripts/environments/components/kubernetes_overview.vue b/app/assets/javascripts/environments/components/kubernetes_overview.vue
index 550568ffaaa..36cce29d624 100644
--- a/app/assets/javascripts/environments/components/kubernetes_overview.vue
+++ b/app/assets/javascripts/environments/components/kubernetes_overview.vue
@@ -111,7 +111,6 @@ export default {
diff --git a/app/assets/javascripts/environments/components/kubernetes_status_bar.vue b/app/assets/javascripts/environments/components/kubernetes_status_bar.vue
index c603d83db9c..8ecb61711ce 100644
--- a/app/assets/javascripts/environments/components/kubernetes_status_bar.vue
+++ b/app/assets/javascripts/environments/components/kubernetes_status_bar.vue
@@ -37,11 +37,6 @@ export default {
required: true,
type: String,
},
- namespace: {
- required: false,
- type: String,
- default: '',
- },
fluxResourcePath: {
required: false,
type: String,
@@ -54,14 +49,12 @@ export default {
variables() {
return {
configuration: this.configuration,
- namespace: this.namespace,
- environmentName: this.environmentName.toLowerCase(),
fluxResourcePath: this.fluxResourcePath,
};
},
skip() {
return Boolean(
- !this.namespace || this.fluxResourcePath?.includes(HELM_RELEASES_RESOURCE_TYPE),
+ !this.fluxResourcePath || this.fluxResourcePath?.includes(HELM_RELEASES_RESOURCE_TYPE),
);
},
error(err) {
@@ -73,17 +66,12 @@ export default {
variables() {
return {
configuration: this.configuration,
- namespace: this.namespace,
- environmentName: this.environmentName.toLowerCase(),
fluxResourcePath: this.fluxResourcePath,
};
},
skip() {
return Boolean(
- !this.namespace ||
- this.$apollo.queries.fluxKustomizationStatus.loading ||
- this.hasKustomizations ||
- this.fluxResourcePath?.includes(KUSTOMIZATIONS_RESOURCE_TYPE),
+ !this.fluxResourcePath || this.fluxResourcePath?.includes(KUSTOMIZATIONS_RESOURCE_TYPE),
);
},
error(err) {
diff --git a/app/assets/javascripts/environments/constants.js b/app/assets/javascripts/environments/constants.js
index 7214454c45c..e97720312b0 100644
--- a/app/assets/javascripts/environments/constants.js
+++ b/app/assets/javascripts/environments/constants.js
@@ -42,12 +42,12 @@ export const CANARY_STATUS = {
export const CANARY_UPDATE_MODAL = 'confirm-canary-change';
export const ENVIRONMENTS_SCOPE = {
- AVAILABLE: 'available',
+ ACTIVE: 'active',
STOPPED: 'stopped',
};
export const ENVIRONMENT_COUNT_BY_SCOPE = {
- [ENVIRONMENTS_SCOPE.AVAILABLE]: 'availableCount',
+ [ENVIRONMENTS_SCOPE.ACTIVE]: 'activeCount',
[ENVIRONMENTS_SCOPE.STOPPED]: 'stoppedCount',
};
diff --git a/app/assets/javascripts/environments/graphql/queries/environment_app.query.graphql b/app/assets/javascripts/environments/graphql/queries/environment_app.query.graphql
index 7a50ded7d6c..ef5a8194dca 100644
--- a/app/assets/javascripts/environments/graphql/queries/environment_app.query.graphql
+++ b/app/assets/javascripts/environments/graphql/queries/environment_app.query.graphql
@@ -1,6 +1,6 @@
query getEnvironmentApp($page: Int, $scope: String, $search: String) {
environmentApp(page: $page, scope: $scope, search: $search) @client {
- availableCount
+ activeCount
stoppedCount
environments
reviewApp
diff --git a/app/assets/javascripts/environments/graphql/queries/flux_helm_release_status.query.graphql b/app/assets/javascripts/environments/graphql/queries/flux_helm_release_status.query.graphql
index 544232dafd7..042bdc1992d 100644
--- a/app/assets/javascripts/environments/graphql/queries/flux_helm_release_status.query.graphql
+++ b/app/assets/javascripts/environments/graphql/queries/flux_helm_release_status.query.graphql
@@ -1,15 +1,6 @@
-query getFluxHelmReleaseStatusQuery(
- $configuration: LocalConfiguration
- $namespace: String
- $environmentName: String
- $fluxResourcePath: String
-) {
- fluxHelmReleaseStatus(
- configuration: $configuration
- namespace: $namespace
- environmentName: $environmentName
- fluxResourcePath: $fluxResourcePath
- ) @client {
+query getFluxHelmReleaseStatusQuery($configuration: LocalConfiguration, $fluxResourcePath: String) {
+ fluxHelmReleaseStatus(configuration: $configuration, fluxResourcePath: $fluxResourcePath)
+ @client {
message
status
type
diff --git a/app/assets/javascripts/environments/graphql/queries/flux_kustomization_status.query.graphql b/app/assets/javascripts/environments/graphql/queries/flux_kustomization_status.query.graphql
index 2884f95355e..458b8a4d9db 100644
--- a/app/assets/javascripts/environments/graphql/queries/flux_kustomization_status.query.graphql
+++ b/app/assets/javascripts/environments/graphql/queries/flux_kustomization_status.query.graphql
@@ -1,15 +1,9 @@
query getFluxHelmKustomizationStatusQuery(
$configuration: LocalConfiguration
- $namespace: String
- $environmentName: String
$fluxResourcePath: String
) {
- fluxKustomizationStatus(
- configuration: $configuration
- namespace: $namespace
- environmentName: $environmentName
- fluxResourcePath: $fluxResourcePath
- ) @client {
+ fluxKustomizationStatus(configuration: $configuration, fluxResourcePath: $fluxResourcePath)
+ @client {
message
status
type
diff --git a/app/assets/javascripts/environments/graphql/queries/folder.query.graphql b/app/assets/javascripts/environments/graphql/queries/folder.query.graphql
index c662acb8f93..ac6a68e450c 100644
--- a/app/assets/javascripts/environments/graphql/queries/folder.query.graphql
+++ b/app/assets/javascripts/environments/graphql/queries/folder.query.graphql
@@ -1,6 +1,6 @@
query getEnvironmentFolder($environment: NestedLocalEnvironment, $scope: String, $search: String) {
folder(environment: $environment, scope: $scope, search: $search) @client {
- availableCount
+ activeCount
environments
stoppedCount
}
diff --git a/app/assets/javascripts/environments/graphql/resolvers/base.js b/app/assets/javascripts/environments/graphql/resolvers/base.js
index 9752a3a6634..4427b8ff2ef 100644
--- a/app/assets/javascripts/environments/graphql/resolvers/base.js
+++ b/app/assets/javascripts/environments/graphql/resolvers/base.js
@@ -47,7 +47,7 @@ export const baseQueries = (endpoint) => ({
});
return {
- availableCount: res.data.available_count,
+ activeCount: res.data.active_count,
environments: res.data.environments.map(mapNestedEnvironment),
reviewApp: {
...convertObjectPropsToCamelCase(res.data.review_app),
@@ -61,7 +61,7 @@ export const baseQueries = (endpoint) => ({
},
folder(_, { environment: { folderPath }, scope, search }) {
return axios.get(folderPath, { params: { scope, search, per_page: 3 } }).then((res) => ({
- availableCount: res.data.available_count,
+ activeCount: res.data.active_count,
environments: res.data.environments.map(mapEnvironment),
stoppedCount: res.data.stopped_count,
__typename: 'LocalEnvironmentFolder',
diff --git a/app/assets/javascripts/environments/graphql/resolvers/flux.js b/app/assets/javascripts/environments/graphql/resolvers/flux.js
index 627737276db..5cb5db5d752 100644
--- a/app/assets/javascripts/environments/graphql/resolvers/flux.js
+++ b/app/assets/javascripts/environments/graphql/resolvers/flux.js
@@ -18,14 +18,8 @@ const handleClusterError = (err) => {
throw error;
};
-const buildFluxResourceUrl = ({
- basePath,
- namespace,
- apiVersion,
- resourceType,
- environmentName = '',
-}) => {
- return `${basePath}/apis/${apiVersion}/namespaces/${namespace}/${resourceType}/${environmentName}`;
+const buildFluxResourceUrl = ({ basePath, namespace, apiVersion, resourceType }) => {
+ return `${basePath}/apis/${apiVersion}/namespaces/${namespace}/${resourceType}`;
};
const buildFluxResourceWatchPath = ({ namespace, apiVersion, resourceType }) => {
@@ -57,17 +51,22 @@ const watchFluxResource = ({ watchPath, resourceName, query, variables, field, c
});
};
-const getFluxResourceStatus = ({ url, watchPath, query, variables, field, client }) => {
+const getFluxResourceStatus = ({ query, variables, field, resourceType, client }) => {
const { headers } = variables.configuration;
const withCredentials = true;
+ const url = `${variables.configuration.basePath}/apis/${variables.fluxResourcePath}`;
return axios
.get(url, { withCredentials, headers })
.then((res) => {
const fluxData = res?.data;
const resourceName = fluxData?.metadata?.name;
+ const namespace = fluxData?.metadata?.namespace;
+ const apiVersion = fluxData?.apiVersion;
if (gon.features?.k8sWatchApi && resourceName) {
+ const watchPath = buildFluxResourceWatchPath({ namespace, apiVersion, resourceType });
+
watchFluxResource({
watchPath,
resourceName,
@@ -111,67 +110,21 @@ const getFluxResources = (configuration, url) => {
};
export default {
- fluxKustomizationStatus(
- _,
- { configuration, namespace, environmentName, fluxResourcePath = '' },
- { client },
- ) {
- const watchPath = buildFluxResourceWatchPath({
- namespace,
- apiVersion: kustomizationsApiVersion,
- resourceType: KUSTOMIZATIONS_RESOURCE_TYPE,
- });
- let url;
-
- if (fluxResourcePath) {
- url = `${configuration.basePath}/apis/${fluxResourcePath}`;
- } else {
- url = buildFluxResourceUrl({
- basePath: configuration.basePath,
- resourceType: KUSTOMIZATIONS_RESOURCE_TYPE,
- apiVersion: kustomizationsApiVersion,
- namespace,
- environmentName,
- });
- }
+ fluxKustomizationStatus(_, { configuration, fluxResourcePath }, { client }) {
return getFluxResourceStatus({
- url,
- watchPath,
query: fluxKustomizationStatusQuery,
- variables: { configuration, namespace, environmentName, fluxResourcePath },
+ variables: { configuration, fluxResourcePath },
field: kustomizationField,
+ resourceType: KUSTOMIZATIONS_RESOURCE_TYPE,
client,
});
},
- fluxHelmReleaseStatus(
- _,
- { configuration, namespace, environmentName, fluxResourcePath },
- { client },
- ) {
- const watchPath = buildFluxResourceWatchPath({
- namespace,
- apiVersion: helmReleasesApiVersion,
- resourceType: HELM_RELEASES_RESOURCE_TYPE,
- });
- let url;
-
- if (fluxResourcePath) {
- url = `${configuration.basePath}/apis/${fluxResourcePath}`;
- } else {
- url = buildFluxResourceUrl({
- basePath: configuration.basePath,
- resourceType: HELM_RELEASES_RESOURCE_TYPE,
- apiVersion: helmReleasesApiVersion,
- namespace,
- environmentName,
- });
- }
+ fluxHelmReleaseStatus(_, { configuration, fluxResourcePath }, { client }) {
return getFluxResourceStatus({
- url,
- watchPath,
query: fluxHelmReleaseStatusQuery,
- variables: { configuration, namespace, environmentName, fluxResourcePath },
+ variables: { configuration, fluxResourcePath },
field: helmReleaseField,
+ resourceType: HELM_RELEASES_RESOURCE_TYPE,
client,
});
},
diff --git a/app/assets/javascripts/work_items/components/work_item_type_icon.vue b/app/assets/javascripts/work_items/components/work_item_type_icon.vue
index 5426f3965b3..76a73093206 100644
--- a/app/assets/javascripts/work_items/components/work_item_type_icon.vue
+++ b/app/assets/javascripts/work_items/components/work_item_type_icon.vue
@@ -36,6 +36,11 @@ export default {
return this.workItemType.toUpperCase().split(' ').join('_');
},
iconName() {
+ // TODO Delete this conditional once we have an `issue-type-epic` icon
+ if (this.workItemIconName === 'issue-type-epic') {
+ return 'epic';
+ }
+
return (
this.workItemIconName ||
WORK_ITEMS_TYPE_MAP[this.workItemTypeUppercase]?.icon ||
diff --git a/app/controllers/jira_connect/subscriptions_controller.rb b/app/controllers/jira_connect/subscriptions_controller.rb
index 773ef2bddca..17a79f83a78 100644
--- a/app/controllers/jira_connect/subscriptions_controller.rb
+++ b/app/controllers/jira_connect/subscriptions_controller.rb
@@ -48,7 +48,7 @@ class JiraConnect::SubscriptionsController < JiraConnect::ApplicationController
def destroy
subscription = current_jira_installation.subscriptions.find(params[:id])
- if !jira_user&.site_admin?
+ if !jira_user&.jira_admin?
render json: { error: 'forbidden' }, status: :forbidden
elsif subscription.destroy
render json: { success: true }
diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb
index efb8c63252b..4b2749dc716 100644
--- a/app/controllers/projects/environments_controller.rb
+++ b/app/controllers/projects/environments_controller.rb
@@ -2,6 +2,8 @@
class Projects::EnvironmentsController < Projects::ApplicationController
MIN_SEARCH_LENGTH = 3
+ ACTIVE_STATES = %i[available stopping].freeze
+ SCOPES_TO_STATES = { "active" => ACTIVE_STATES, "stopped" => %i[stopped] }.freeze
include ProductAnalyticsTracking
include KasCookie
@@ -35,7 +37,9 @@ class Projects::EnvironmentsController < Projects::ApplicationController
respond_to do |format|
format.html
format.json do
- @environments = search_environments.with_state(params[:scope] || :available)
+ states = SCOPES_TO_STATES.fetch(params[:scope], ACTIVE_STATES)
+ @environments = search_environments.with_state(states)
+
environments_count_by_state = search_environments.count_by_state
Gitlab::PollingInterval.set_header(response, interval: 3_000)
@@ -44,6 +48,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController
review_app: serialize_review_app,
can_stop_stale_environments: can?(current_user, :stop_environment, @project),
available_count: environments_count_by_state[:available],
+ active_count: environments_count_by_state[:available] + environments_count_by_state[:stopping],
stopped_count: environments_count_by_state[:stopped]
}
end
@@ -58,14 +63,16 @@ class Projects::EnvironmentsController < Projects::ApplicationController
respond_to do |format|
format.html
format.json do
+ states = SCOPES_TO_STATES.fetch(params[:scope], ACTIVE_STATES)
folder_environments = search_environments(type: params[:id])
- @environments = folder_environments.with_state(params[:scope] || :available)
+ @environments = folder_environments.with_state(states)
.order(:name)
render json: {
environments: serialize_environments(request, response),
available_count: folder_environments.available.count,
+ active_count: folder_environments.active.count,
stopped_count: folder_environments.stopped.count
}
end
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index ec4fbdec73a..8d4f50de75e 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -32,6 +32,8 @@ class ApplicationSetting < MainClusterwide::ApplicationRecord
], remove_with: '16.5', remove_after: '2023-09-22'
ignore_columns %i[encrypted_ai_access_token encrypted_ai_access_token_iv], remove_with: '16.10', remove_after: '2024-03-22'
+ ignore_columns %i[repository_storages], remove_with: '16.8', remove_after: '2023-12-21'
+
INSTANCE_REVIEW_MIN_USERS = 50
GRAFANA_URL_ERROR_MESSAGE = 'Please check your Grafana URL setting in ' \
'Admin Area > Settings > Metrics and profiling > Metrics - Grafana'
@@ -91,7 +93,6 @@ class ApplicationSetting < MainClusterwide::ApplicationRecord
serialize :disabled_oauth_sign_in_sources, Array # rubocop:disable Cop/ActiveRecordSerialize
serialize :domain_allowlist, Array # rubocop:disable Cop/ActiveRecordSerialize
serialize :domain_denylist, Array # rubocop:disable Cop/ActiveRecordSerialize
- serialize :repository_storages # rubocop:disable Cop/ActiveRecordSerialize
# See https://gitlab.com/gitlab-org/gitlab/-/issues/300916
serialize :asset_proxy_allowlist, Array # rubocop:disable Cop/ActiveRecordSerialize
@@ -303,8 +304,6 @@ class ApplicationSetting < MainClusterwide::ApplicationRecord
presence: true,
numericality: { only_integer: true, greater_than_or_equal_to: 0 }
- validates :repository_storages, presence: true
- validate :check_repository_storages
validate :check_repository_storages_weighted
validates :auto_devops_domain,
diff --git a/app/models/application_setting_implementation.rb b/app/models/application_setting_implementation.rb
index a5ed402aa9a..00b093c8ac3 100644
--- a/app/models/application_setting_implementation.rb
+++ b/app/models/application_setting_implementation.rb
@@ -159,7 +159,6 @@ module ApplicationSettingImplementation
recaptcha_enabled: false,
repository_checks_enabled: true,
repository_storages_weighted: { 'default' => 100 },
- repository_storages: ['default'],
require_admin_approval_after_user_signup: true,
require_two_factor_authentication: false,
restricted_visibility_levels: Settings.gitlab['restricted_visibility_levels'],
@@ -434,10 +433,6 @@ module ApplicationSettingImplementation
read_attribute(:asset_proxy_whitelist)
end
- def repository_storages
- Array(read_attribute(:repository_storages))
- end
-
def commit_email_hostname
super.presence || self.class.default_commit_email_hostname
end
@@ -645,12 +640,6 @@ module ApplicationSettingImplementation
self.uuid = SecureRandom.uuid
end
- def check_repository_storages
- invalid = repository_storages - Gitlab.config.repositories.storages.keys
- errors.add(:repository_storages, "can't include: #{invalid.join(", ")}") unless
- invalid.empty?
- end
-
def coerce_repository_storages_weighted
repository_storages_weighted.transform_values!(&:to_i)
end
diff --git a/app/models/environment.rb b/app/models/environment.rb
index c3066bbca0a..4f76fae24eb 100644
--- a/app/models/environment.rb
+++ b/app/models/environment.rb
@@ -91,6 +91,7 @@ class Environment < ApplicationRecord
delegate :auto_rollback_enabled?, to: :project
scope :available, -> { with_state(:available) }
+ scope :active, -> { with_state(:available, :stopping) }
scope :stopped, -> { with_state(:stopped) }
scope :order_by_last_deployed_at, -> do
diff --git a/app/services/ci/catalog/resources/validate_service.rb b/app/services/ci/catalog/resources/validate_service.rb
index 186b1699d03..0e842fb7405 100644
--- a/app/services/ci/catalog/resources/validate_service.rb
+++ b/app/services/ci/catalog/resources/validate_service.rb
@@ -44,7 +44,7 @@ module Ci
return if Ci::Catalog::ComponentsProject.new(project).fetch_component_paths(ref,
limit: MINIMUM_AMOUNT_OF_COMPONENTS).any?
- errors << 'Project must contain components'
+ errors << 'Project must contain components. Ensure you are using the correct directory structure'
end
def project_has_readme?
diff --git a/app/services/jira_connect_subscriptions/create_service.rb b/app/services/jira_connect_subscriptions/create_service.rb
index d5ab3800dcf..f537da5c091 100644
--- a/app/services/jira_connect_subscriptions/create_service.rb
+++ b/app/services/jira_connect_subscriptions/create_service.rb
@@ -11,7 +11,7 @@ module JiraConnectSubscriptions
return error(s_('JiraConnect|Could not fetch user information from Jira. ' \
'Check the permissions in Jira and try again.'), 403)
elsif !can_administer_jira?
- return error(s_('JiraConnect|The Jira user is not a site administrator. ' \
+ return error(s_('JiraConnect|The Jira user is not a site or organization administrator. ' \
'Check the permissions in Jira and try again.'), 403)
end
@@ -25,7 +25,7 @@ module JiraConnectSubscriptions
private
def can_administer_jira?
- params[:jira_user]&.site_admin?
+ params[:jira_user]&.jira_admin?
end
def create_subscription
diff --git a/app/services/members/create_service.rb b/app/services/members/create_service.rb
index 79bc07103fd..b453098e27a 100644
--- a/app/services/members/create_service.rb
+++ b/app/services/members/create_service.rb
@@ -83,18 +83,9 @@ module Members
end
def add_members
- @members = if Feature.enabled?(:invitations_member_role_id, source)
- creator_service.add_members(
- source, invites, params[:access_level], **create_params
- )
- else
- source.add_members(
- invites,
- params[:access_level],
- expires_at: params[:expires_at],
- current_user: current_user
- )
- end
+ @members = creator_service.add_members(
+ source, invites, params[:access_level], **create_params
+ )
members.each { |member| process_result(member) }
end
diff --git a/app/services/releases/create_service.rb b/app/services/releases/create_service.rb
index 0e105ca3575..38c9e6d60a7 100644
--- a/app/services/releases/create_service.rb
+++ b/app/services/releases/create_service.rb
@@ -53,7 +53,7 @@ module Releases
if project.catalog_resource && release.valid?
response = Ci::Catalog::Resources::ReleaseService.new(release).execute
- return error(response.message) if response.error?
+ return error(response.message, 422) if response.error?
end
release.save!
diff --git a/config/feature_flags/development/explain_code_vertex_ai.yml b/config/feature_flags/development/explain_code_vertex_ai.yml
deleted file mode 100644
index 4eb4d64ed30..00000000000
--- a/config/feature_flags/development/explain_code_vertex_ai.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: explain_code_vertex_ai
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/125292
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/416907
-milestone: '16.2'
-type: development
-group: group::source code
-default_enabled: false
diff --git a/config/feature_flags/development/invitations_member_role_id.yml b/config/feature_flags/development/invitations_member_role_id.yml
deleted file mode 100644
index ccb319e4e35..00000000000
--- a/config/feature_flags/development/invitations_member_role_id.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: invitations_member_role_id
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/134100
-rollout_issue_url:
-milestone: '16.6'
-type: development
-group: group::authorization
-default_enabled: false
diff --git a/config/initializers/postgresql_cte.rb b/config/initializers/postgresql_cte.rb
index 7f0196197b9..40d959c1ba0 100644
--- a/config/initializers/postgresql_cte.rb
+++ b/config/initializers/postgresql_cte.rb
@@ -40,6 +40,8 @@ module ActiveRecord::Querying
delegate :with, to: :all
end
+# Rails 7.1 defines #with method.
+# Therefore, this file can be either simplified or completely removed.
module ActiveRecord
class Relation
# WithChain objects act as placeholder for queries in which #with does not have any parameter.
@@ -51,21 +53,21 @@ module ActiveRecord
# Returns a new relation expressing WITH RECURSIVE
def recursive(*args)
- @scope.with_values += args
+ @scope.with_values_ += args
@scope.recursive_value = true
@scope.extend(Gitlab::Database::ReadOnlyRelation)
@scope
end
end
- def with_values
- @values[:with] || []
+ def with_values_
+ @values[:with_values] || []
end
- def with_values=(values)
+ def with_values_=(values)
raise ImmutableRelation if @loaded
- @values[:with] = values
+ @values[:with_values] = values
end
def recursive_value=(value)
@@ -92,7 +94,7 @@ module ActiveRecord
if opts == :chain
WithChain.new(self)
else
- self.with_values += [opts] + rest
+ self.with_values_ += [opts] + rest
self
end
end
@@ -100,13 +102,13 @@ module ActiveRecord
def build_arel(aliases = nil)
arel = super
- build_with(arel) if @values[:with]
+ build_with(arel) if @values[:with_values]
arel
end
def build_with(arel)
- with_statements = with_values.flat_map do |with_value|
+ with_statements = with_values_.flat_map do |with_value|
case with_value
when String
with_value
diff --git a/db/post_migrate/20231101130230_remove_in_product_marketing_emails_campaign_column.rb b/db/post_migrate/20231101130230_remove_in_product_marketing_emails_campaign_column.rb
new file mode 100644
index 00000000000..8916a1e9729
--- /dev/null
+++ b/db/post_migrate/20231101130230_remove_in_product_marketing_emails_campaign_column.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+class RemoveInProductMarketingEmailsCampaignColumn < Gitlab::Database::Migration[2.2]
+ disable_ddl_transaction!
+ milestone '16.6'
+
+ TARGET_TABLE = :in_product_marketing_emails
+ UNIQUE_INDEX_NAME = :index_in_product_marketing_emails_on_user_campaign
+ CONSTRAINT_NAME = :in_product_marketing_emails_track_and_series_or_campaign
+ TRACK_AND_SERIES_NOT_NULL_CONSTRAINT = 'track IS NOT NULL AND series IS NOT NULL AND campaign IS NULL'
+ CAMPAIGN_NOT_NULL_CONSTRAINT = 'track IS NULL AND series IS NULL AND campaign IS NOT NULL'
+
+ def up
+ with_lock_retries do
+ remove_column :in_product_marketing_emails, :campaign, if_exists: true
+ end
+ end
+
+ def down
+ with_lock_retries do
+ add_column :in_product_marketing_emails, :campaign, :text, if_not_exists: true
+ end
+
+ add_text_limit :in_product_marketing_emails, :campaign, 255
+
+ add_concurrent_index TARGET_TABLE, [:user_id, :campaign], unique: true, name: UNIQUE_INDEX_NAME
+ add_check_constraint TARGET_TABLE,
+ "(#{TRACK_AND_SERIES_NOT_NULL_CONSTRAINT}) OR (#{CAMPAIGN_NOT_NULL_CONSTRAINT})",
+ CONSTRAINT_NAME
+ end
+end
diff --git a/db/schema_migrations/20231101130230 b/db/schema_migrations/20231101130230
new file mode 100644
index 00000000000..8fa382d7033
--- /dev/null
+++ b/db/schema_migrations/20231101130230
@@ -0,0 +1 @@
+c8dbdeb4ffcb7f5dc1c719a09a1f6c41188f584c80331a4482542a873d3ad12d
\ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 04f3f70feb8..870ae4727f1 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -17462,10 +17462,7 @@ CREATE TABLE in_product_marketing_emails (
track smallint,
series smallint,
created_at timestamp with time zone NOT NULL,
- updated_at timestamp with time zone NOT NULL,
- campaign text,
- CONSTRAINT check_9d8b29f74f CHECK ((char_length(campaign) <= 255)),
- CONSTRAINT in_product_marketing_emails_track_and_series_or_campaign CHECK ((((track IS NOT NULL) AND (series IS NOT NULL) AND (campaign IS NULL)) OR ((track IS NULL) AND (series IS NULL) AND (campaign IS NOT NULL))))
+ updated_at timestamp with time zone NOT NULL
);
CREATE SEQUENCE in_product_marketing_emails_id_seq
@@ -32915,8 +32912,6 @@ CREATE INDEX index_imported_projects_on_import_type_id ON projects USING btree (
CREATE INDEX index_in_product_marketing_emails_on_track_series_id_clicked ON in_product_marketing_emails USING btree (track, series, id, cta_clicked_at);
-CREATE UNIQUE INDEX index_in_product_marketing_emails_on_user_campaign ON in_product_marketing_emails USING btree (user_id, campaign);
-
CREATE INDEX index_in_product_marketing_emails_on_user_id ON in_product_marketing_emails USING btree (user_id);
CREATE UNIQUE INDEX index_in_product_marketing_emails_on_user_track_series ON in_product_marketing_emails USING btree (user_id, track, series);
diff --git a/doc/administration/settings/jira_cloud_app.md b/doc/administration/settings/jira_cloud_app.md
index 0b9def383d5..8ff2a9acdb8 100644
--- a/doc/administration/settings/jira_cloud_app.md
+++ b/doc/administration/settings/jira_cloud_app.md
@@ -50,14 +50,25 @@ To create an OAuth application on your self-managed instance:
## Jira user requirements
-You must ensure that the Jira user that is used to setup the GitLab for Jira Cloud app is a member of the Site Administrators (`site-admins`) group in your
-[Atlassian organization](https://admin.atlassian.com):
+> Support for the `org-admins` group [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/420687) in GitLab 16.6.
-1. If you don't have a `site-admins` group in your Atlassian organization, [create the group](https://support.atlassian.com/user-management/docs/create-groups/).
-1. If not already a member, [add your Jira user as a member](https://support.atlassian.com/user-management/docs/edit-a-group/) of the `site-admins` group.
+In your [Atlassian organization](https://admin.atlassian.com), you must ensure that the Jira user that is used to set up the GitLab for Jira Cloud app is a member of
+either:
-If you have customized your global permissions in Jira, you might also need to grant the
-[`Browse users and groups` permission](https://confluence.atlassian.com/jirakb/unable-to-browse-for-users-and-groups-120521888.html) to the Jira user.
+- The Organization Administrators (`org-admins`) group. Newer Atlassian organizations are using
+ [centralized user management](https://support.atlassian.com/user-management/docs/give-users-admin-permissions/#Centralized-user-management-content),
+ which contains the `org-admins` group. Existing Atlassian organizations are being migrated to centralized user management.
+ If available, you should use the `org-admins` group to indicate which Jira users can manage the GitLab for Jira app. Alternatively you can use the
+ `site-admins` group.
+- The Site Administrators (`site-admins`) group. The `site-admins` group was used under
+ [original user management](https://support.atlassian.com/user-management/docs/give-users-admin-permissions/#Original-user-management-content).
+
+If necessary:
+
+1. [Create your preferred group](https://support.atlassian.com/user-management/docs/create-groups/).
+1. [Edit the group](https://support.atlassian.com/user-management/docs/edit-a-group/) to add your Jira user as a member of it.
+1. If you customized your global permissions in Jira, you might also need to grant the
+ [`Browse users and groups` permission](https://confluence.atlassian.com/jirakb/unable-to-browse-for-users-and-groups-120521888.html) to the Jira user.
## Connect the GitLab for Jira Cloud app
diff --git a/doc/api/invitations.md b/doc/api/invitations.md
index 9c209f04d65..0bf38b6e616 100644
--- a/doc/api/invitations.md
+++ b/doc/api/invitations.md
@@ -43,7 +43,7 @@ POST /projects/:id/invitations
| `access_level` | integer | yes | A valid access level |
| `expires_at` | string | no | A date string in the format YEAR-MONTH-DAY |
| `invite_source` | string | no | The source of the invitation that starts the member creation process. See [this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/327120). |
-| `member_role_id` **(ULTIMATE ALL)** | integer | no | Assigns the new member to the provided custom role. ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/134100) in GitLab 16.6 under a feature flag `invitations_member_role_id`. |
+| `member_role_id` **(ULTIMATE ALL)** | integer | no | Assigns the new member to the provided custom role. ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/134100) in GitLab 16.6. |
```shell
curl --request POST --header "PRIVATE-TOKEN: " \
diff --git a/doc/api/settings.md b/doc/api/settings.md
index 0a74afe2abf..c8899eeb3e6 100644
--- a/doc/api/settings.md
+++ b/doc/api/settings.md
@@ -216,7 +216,6 @@ Example response:
"container_registry_token_expire_delay": 5,
"decompress_archive_file_timeout": 210,
"package_registry_cleanup_policies_worker_capacity": 2,
- "repository_storages": ["default"],
"plantuml_enabled": false,
"plantuml_url": null,
"diagramsnet_enabled": true,
@@ -537,7 +536,6 @@ listed in the descriptions of the relevant settings.
| `repository_checks_enabled` | boolean | no | GitLab periodically runs `git fsck` in all project and wiki repositories to look for silent disk corruption issues. |
| `repository_size_limit` **(PREMIUM ALL)** | integer | no | Size limit per repository (MB) |
| `repository_storages_weighted` | hash of strings to integers | no | (GitLab 13.1 and later) Hash of names of taken from `gitlab.yml` to [weights](../administration/repository_storage_paths.md#configure-where-new-repositories-are-stored). New projects are created in one of these stores, chosen by a weighted random selection. |
-| `repository_storages` | array of strings | no | (GitLab 13.0 and earlier) List of names of enabled storage paths, taken from `gitlab.yml`. New projects are created in one of these stores, chosen at random. |
| `require_admin_approval_after_user_signup` | boolean | no | When enabled, any user that signs up for an account using the registration form is placed under a **Pending approval** state and has to be explicitly [approved](../administration/moderate_users.md) by an administrator. |
| `require_two_factor_authentication` | boolean | no | (**If enabled, requires:** `two_factor_grace_period`) Require all users to set up Two-factor authentication. |
| `restricted_visibility_levels` | array of strings | no | Selected levels cannot be used by non-Administrator users for groups, projects or snippets. Can take `private`, `internal` and `public` as a parameter. Default is `null` which means there is no restriction.[Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/131203) in GitLab 16.4: cannot select levels that are set as `default_project_visibility` and `default_group_visibility`. |
diff --git a/doc/ci/environments/kubernetes_dashboard.md b/doc/ci/environments/kubernetes_dashboard.md
index 4062fbee25e..42fa560ad76 100644
--- a/doc/ci/environments/kubernetes_dashboard.md
+++ b/doc/ci/environments/kubernetes_dashboard.md
@@ -83,8 +83,7 @@ You can review the sync status of your Flux deployments from a dashboard.
To display the deployment status, your dashboard must be able to retrieve the `Kustomization` and `HelmRelease` resources,
which requires a namespace to be configured for the environment.
-By default, GitLab searches the `Kustomization` and `HelmRelease` resources for the name of the project slug.
-You can specify the resource names with the **Flux resource** dropdown list in the environment settings.
+GitLab searches the `Kustomization` and `HelmRelease` resources specified by the **Flux resource** dropdown list in the environment settings.
A dashboard displays one of the following status badges:
diff --git a/doc/development/ai_architecture.md b/doc/development/ai_architecture.md
index f03ffa748fa..a41d17887ca 100644
--- a/doc/development/ai_architecture.md
+++ b/doc/development/ai_architecture.md
@@ -77,7 +77,7 @@ A [draft MR](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/122035) has b
The index function has been updated to improve search quality. This was tested locally by setting the `ivfflat.probes` value to `10` with the following SQL command:
```ruby
-Embedding::TanukiBotMvc.connection.execute("SET ivfflat.probes = 10")
+::Embedding::Vertex::GitlabDocumentation.connection.execute("SET ivfflat.probes = 10")
```
Setting the `probes` value for indexing improves results, as per the neighbor [documentation](https://github.com/ankane/neighbor#indexing).
diff --git a/doc/development/ee_features.md b/doc/development/ee_features.md
index 9bef2f2ab10..d05249f3d3f 100644
--- a/doc/development/ee_features.md
+++ b/doc/development/ee_features.md
@@ -78,7 +78,7 @@ end
### Do not use SaaS-only features for functionality in CE
-`Gitlab::Saas.feature_vailable?` must not appear in CE.
+`Gitlab::Saas.feature_available?` must not appear in CE.
See [extending CE with EE guide](#extend-ce-features-with-ee-backend-code).
### SaaS-only features in tests
diff --git a/doc/user/profile/account/two_factor_authentication.md b/doc/user/profile/account/two_factor_authentication.md
index d1f1d28663e..d26f2193124 100644
--- a/doc/user/profile/account/two_factor_authentication.md
+++ b/doc/user/profile/account/two_factor_authentication.md
@@ -544,3 +544,9 @@ generates the codes. For example:
1. Select General.
1. Select Date & Time.
1. Enable Set Automatically. If it's already enabled, disable it, wait a few seconds, and re-enable.
+
+### Error: "Permission denied (publickey)" when regenerating recovery codes
+
+If you receive a `Permission denied (publickey)` error when attempting to [generate new recovery codes using an SSH key](#generate-new-recovery-codes-using-ssh)
+and you are using a non-default SSH key pair file path,
+you might need to [manually register your private SSH key](../../ssh.md#configure-ssh-to-point-to-a-different-directory) using `ssh-agent`.
diff --git a/lib/atlassian/jira_connect/jira_user.rb b/lib/atlassian/jira_connect/jira_user.rb
index 57ceb8fdf13..051165474af 100644
--- a/lib/atlassian/jira_connect/jira_user.rb
+++ b/lib/atlassian/jira_connect/jira_user.rb
@@ -3,15 +3,17 @@
module Atlassian
module JiraConnect
class JiraUser
+ ADMIN_GROUPS = %w[site-admins org-admins].freeze
+
def initialize(data)
@data = data
end
- def site_admin?
+ def jira_admin?
groups = @data.dig('groups', 'items')
return false unless groups
- groups.any? { |g| g['name'] == 'site-admins' }
+ groups.any? { |group| ADMIN_GROUPS.include?(group['name']) }
end
end
end
diff --git a/lib/gitlab/database/migration_helpers/convert_to_bigint.rb b/lib/gitlab/database/migration_helpers/convert_to_bigint.rb
index 11f1e62e8b9..d1edb739b85 100644
--- a/lib/gitlab/database/migration_helpers/convert_to_bigint.rb
+++ b/lib/gitlab/database/migration_helpers/convert_to_bigint.rb
@@ -4,12 +4,16 @@ module Gitlab
module Database
module MigrationHelpers
module ConvertToBigint
- # This helper is extracted for the purpose of
- # https://gitlab.com/gitlab-org/gitlab/-/issues/392815
- # so that we can test all combinations just once,
- # and simplify migration tests.
- #
- # Once we are done with the PK conversions we can remove this.
+ INDEX_OPTIONS_MAP = {
+ unique: :unique,
+ order: :orders,
+ opclass: :opclasses,
+ where: :where,
+ type: :type,
+ using: :using,
+ comment: :comment
+ }.freeze
+
def com_or_dev_or_test_but_not_jh?
return true if Gitlab.dev_or_test_env?
@@ -29,6 +33,78 @@ module Gitlab
column.sql_type == 'bigint' && temp_column.sql_type == 'integer'
end
+
+ def add_bigint_column_indexes(table_name, int_column_name)
+ bigint_column_name = convert_to_bigint_column(int_column_name)
+
+ unless column_exists?(table_name.to_s, bigint_column_name)
+ raise "Bigint column '#{bigint_column_name}' does not exist on #{table_name}"
+ end
+
+ indexes(table_name).each do |i|
+ next unless Array(i.columns).join(' ').match?(/\b#{int_column_name}\b/)
+
+ create_bigint_index(table_name, i, int_column_name, bigint_column_name)
+ end
+ end
+
+ # default 'index_name' method is not used because this method can be reused while swapping/dropping the indexes
+ def bigint_index_name(int_column_index_name)
+ # First 20 digits of the hash is chosen to make sure it fits the 63 chars limit
+ digest = Digest::SHA256.hexdigest(int_column_index_name).first(20)
+ "bigint_idx_#{digest}"
+ end
+
+ private
+
+ def create_bigint_index(table_name, index_definition, int_column_name, bigint_column_name)
+ index_attributes = index_definition.as_json
+ index_options = INDEX_OPTIONS_MAP
+ .transform_values { |key| index_attributes[key.to_s] }
+ .select { |_, v| v.present? }
+
+ bigint_index_options = create_bigint_options(
+ index_options,
+ index_definition.name,
+ int_column_name,
+ bigint_column_name
+ )
+
+ add_concurrent_index(
+ table_name,
+ bigint_index_columns(int_column_name, bigint_column_name, index_definition.columns),
+ name: bigint_index_options.delete(:name),
+ ** bigint_index_options
+ )
+ end
+
+ def bigint_index_columns(int_column_name, bigint_column_name, int_index_columns)
+ if int_index_columns.is_a?(String)
+ int_index_columns.gsub(/\b#{int_column_name}\b/, bigint_column_name)
+ else
+ int_index_columns.map do |column|
+ column == int_column_name.to_s ? bigint_column_name : column
+ end
+ end
+ end
+
+ def create_bigint_options(index_options, int_index_name, int_column_name, bigint_column_name)
+ index_options[:name] = bigint_index_name(int_index_name)
+ index_options[:where]&.gsub!(/\b#{int_column_name}\b/, bigint_column_name)
+
+ # ordering on multiple columns will return a Hash instead of string
+ index_options[:order] =
+ if index_options[:order].is_a?(Hash)
+ index_options[:order].to_h do |column, order|
+ column = bigint_column_name if column == int_column_name
+ [column, order]
+ end
+ else
+ index_options[:order]&.gsub(/\b#{int_column_name}\b/, bigint_column_name)
+ end
+
+ index_options.select { |_, v| v.present? }
+ end
end
end
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 31facc5689b..dbe4be7456b 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -21509,15 +21509,9 @@ msgstr ""
msgid "Geo|Remove %{siteType} site"
msgstr ""
-msgid "Geo|Remove entry"
-msgstr ""
-
msgid "Geo|Remove site"
msgstr ""
-msgid "Geo|Remove tracking database entry"
-msgstr ""
-
msgid "Geo|Removing a Geo site stops the synchronization to and from that site. Are you sure?"
msgstr ""
@@ -21689,9 +21683,6 @@ msgstr ""
msgid "Geo|Time in seconds"
msgstr ""
-msgid "Geo|Tracking database entry will be removed. Are you sure?"
-msgstr ""
-
msgid "Geo|Tuning settings"
msgstr ""
@@ -26948,7 +26939,7 @@ msgstr ""
msgid "JiraConnect|Tell us what you think!"
msgstr ""
-msgid "JiraConnect|The Jira user is not a site administrator. Check the permissions in Jira and try again."
+msgid "JiraConnect|The Jira user is not a site or organization administrator. Check the permissions in Jira and try again."
msgstr ""
msgid "JiraConnect|We would love to learn more about your experience with the GitLab for Jira Cloud App."
@@ -45549,7 +45540,7 @@ msgstr ""
msgid "Something went wrong"
msgstr ""
-msgid "Something went wrong fetching the Scanner Findings. Please try again!"
+msgid "Something went wrong fetching the Scanner Findings. Please try again."
msgstr ""
msgid "Something went wrong on our end"
diff --git a/spec/features/projects/environments/environments_spec.rb b/spec/features/projects/environments/environments_spec.rb
index 3a2c7f0ac7b..0a54f5923f2 100644
--- a/spec/features/projects/environments/environments_spec.rb
+++ b/spec/features/projects/environments/environments_spec.rb
@@ -34,16 +34,16 @@ RSpec.describe 'Environments page', :js, feature_category: :continuous_delivery
describe 'with one available environment' do
let!(:environment) { create(:environment, project: project, state: :available) }
- it 'shows "Available" and "Stopped" tab with links' do
+ it 'shows "Active" and "Stopped" tab with links' do
visit_environments(project)
- expect(page).to have_link(_('Available'))
+ expect(page).to have_link(_('Active'))
expect(page).to have_link(_('Stopped'))
end
- describe 'in available tab page' do
+ describe 'in active tab page' do
it 'shows one environment' do
- visit_environments(project, scope: 'available')
+ visit_environments(project, scope: 'active')
expect(page).to have_link(environment.name, href: project_environment_path(project, environment))
end
@@ -56,7 +56,7 @@ RSpec.describe 'Environments page', :js, feature_category: :continuous_delivery
end
it 'renders second page of pipelines' do
- visit_environments(project, scope: 'available')
+ visit_environments(project, scope: 'active')
find('.page-link.next-page-item').click
wait_for_requests
@@ -85,7 +85,7 @@ RSpec.describe 'Environments page', :js, feature_category: :continuous_delivery
end
it 'shows one environment without error' do
- visit_environments(project, scope: 'available')
+ visit_environments(project, scope: 'active')
expect(page).to have_link(environment.name, href: project_environment_path(project, environment))
end
@@ -95,9 +95,9 @@ RSpec.describe 'Environments page', :js, feature_category: :continuous_delivery
describe 'with one stopped environment' do
let!(:environment) { create(:environment, project: project, state: :stopped) }
- describe 'in available tab page' do
+ describe 'in active tab page' do
it 'shows no environments' do
- visit_environments(project, scope: 'available')
+ visit_environments(project, scope: 'active')
expect(page).to have_content(s_('Environments|Get started with environments'))
end
@@ -122,7 +122,7 @@ RSpec.describe 'Environments page', :js, feature_category: :continuous_delivery
it 'does not show environments and tabs' do
expect(page).to have_content(s_('Environments|Get started with environments'))
- expect(page).not_to have_link(_('Available'))
+ expect(page).not_to have_link(_('Active'))
expect(page).not_to have_link(_('Stopped'))
end
end
@@ -142,7 +142,7 @@ RSpec.describe 'Environments page', :js, feature_category: :continuous_delivery
it 'shows environments names and counters' do
expect(page).to have_link(environment.name, href: project_environment_path(project, environment))
- expect(page).to have_link("#{_('Available')} 1")
+ expect(page).to have_link("#{_('Active')} 1")
expect(page).to have_link("#{_('Stopped')} 0")
end
diff --git a/spec/frontend/environments/environments_app_spec.js b/spec/frontend/environments/environments_app_spec.js
index 8c02a07994b..5ac949e77b6 100644
--- a/spec/frontend/environments/environments_app_spec.js
+++ b/spec/frontend/environments/environments_app_spec.js
@@ -71,7 +71,7 @@ describe('~/environments/components/environments_app.vue', () => {
previousPage: 1,
__typename: 'LocalPageInfo',
},
- location = '?scope=available&page=2&search=prod',
+ location = '?scope=active&page=2&search=prod',
}) => {
setWindowLocation(location);
environmentAppMock.mockReturnValue(environmentsApp);
@@ -96,7 +96,7 @@ describe('~/environments/components/environments_app.vue', () => {
paginationMock = jest.fn();
});
- it('should request available environments if the scope is invalid', async () => {
+ it('should request active environments if the scope is invalid', async () => {
await createWrapperWithMocked({
environmentsApp: resolvedEnvironmentsApp,
folder: resolvedFolder,
@@ -105,7 +105,7 @@ describe('~/environments/components/environments_app.vue', () => {
expect(environmentAppMock).toHaveBeenCalledWith(
expect.anything(),
- expect.objectContaining({ scope: 'available', page: 2 }),
+ expect.objectContaining({ scope: 'active', page: 2 }),
expect.anything(),
expect.anything(),
);
@@ -225,16 +225,16 @@ describe('~/environments/components/environments_app.vue', () => {
});
describe('tabs', () => {
- it('should show tabs for available and stopped environmets', async () => {
+ it('should show tabs for active and stopped environmets', async () => {
await createWrapperWithMocked({
environmentsApp: resolvedEnvironmentsApp,
folder: resolvedFolder,
});
- const [available, stopped] = wrapper.findAllByRole('tab').wrappers;
+ const [active, stopped] = wrapper.findAllByRole('tab').wrappers;
- expect(available.text()).toContain(__('Available'));
- expect(available.text()).toContain(resolvedEnvironmentsApp.availableCount.toString());
+ expect(active.text()).toContain(__('Active'));
+ expect(active.text()).toContain(resolvedEnvironmentsApp.activeCount.toString());
expect(stopped.text()).toContain(__('Stopped'));
expect(stopped.text()).toContain(resolvedEnvironmentsApp.stoppedCount.toString());
});
@@ -379,7 +379,7 @@ describe('~/environments/components/environments_app.vue', () => {
next.trigger('click');
await nextTick();
- expect(window.location.search).toBe('?scope=available&page=3&search=prod');
+ expect(window.location.search).toBe('?scope=active&page=3&search=prod');
});
});
@@ -406,7 +406,7 @@ describe('~/environments/components/environments_app.vue', () => {
await waitForDebounce();
- expect(window.location.search).toBe('?scope=available&page=1&search=hello');
+ expect(window.location.search).toBe('?scope=active&page=1&search=hello');
});
it('should query for the entered parameter', async () => {
diff --git a/spec/frontend/environments/graphql/mock_data.js b/spec/frontend/environments/graphql/mock_data.js
index b80b8508e8d..7d354566761 100644
--- a/spec/frontend/environments/graphql/mock_data.js
+++ b/spec/frontend/environments/graphql/mock_data.js
@@ -267,12 +267,12 @@ export const environmentsApp = {
'{"deploy_review"=>{"stage"=>"deploy", "script"=>["echo \\"Deploy a review app\\""], "environment"=>{"name"=>"review/$CI_COMMIT_REF_NAME", "url"=>"https://$CI_ENVIRONMENT_SLUG.example.com"}, "only"=>["branches"]}}',
},
can_stop_stale_environments: true,
- available_count: 4,
+ active_count: 4,
stopped_count: 0,
};
export const resolvedEnvironmentsApp = {
- availableCount: 4,
+ activeCount: 4,
environments: [
{
name: 'review',
@@ -535,7 +535,7 @@ export const folder = {
has_opened_alert: false,
},
],
- available_count: 2,
+ active_count: 2,
stopped_count: 0,
};
@@ -704,7 +704,7 @@ export const resolvedEnvironment = {
};
export const resolvedFolder = {
- availableCount: 2,
+ activeCount: 2,
environments: [
{
id: 42,
diff --git a/spec/frontend/environments/graphql/resolvers/flux_spec.js b/spec/frontend/environments/graphql/resolvers/flux_spec.js
index ea733c6e0e8..526c98b55b3 100644
--- a/spec/frontend/environments/graphql/resolvers/flux_spec.js
+++ b/spec/frontend/environments/graphql/resolvers/flux_spec.js
@@ -15,8 +15,6 @@ describe('~/frontend/environments/graphql/resolvers', () => {
headers: { 'GitLab-Agent-Id': '1' },
},
};
- const namespace = 'default';
- const environmentName = 'my-environment';
beforeEach(() => {
mockResolvers = resolvers();
@@ -29,34 +27,14 @@ describe('~/frontend/environments/graphql/resolvers', () => {
describe('fluxKustomizationStatus', () => {
const client = { writeQuery: jest.fn() };
- const endpoint = `${configuration.basePath}/apis/kustomize.toolkit.fluxcd.io/v1beta1/namespaces/${namespace}/kustomizations/${environmentName}`;
const fluxResourcePath =
'kustomize.toolkit.fluxcd.io/v1beta1/namespaces/my-namespace/kustomizations/app';
- const endpointWithFluxResourcePath = `${configuration.basePath}/apis/${fluxResourcePath}`;
+ const endpoint = `${configuration.basePath}/apis/${fluxResourcePath}`;
describe('when k8sWatchApi feature is disabled', () => {
- it('should request Flux Kustomizations for the provided namespace via the Kubernetes API if the fluxResourcePath is not specified', async () => {
- mock
- .onGet(endpoint, { withCredentials: true, headers: configuration.baseOptions.headers })
- .reply(HTTP_STATUS_OK, {
- status: { conditions: fluxKustomizationsMock },
- });
-
- const fluxKustomizationStatus = await mockResolvers.Query.fluxKustomizationStatus(
- null,
- {
- configuration,
- namespace,
- environmentName,
- },
- { client },
- );
-
- expect(fluxKustomizationStatus).toEqual(fluxKustomizationsMock);
- });
it('should request Flux Kustomization for the provided fluxResourcePath via the Kubernetes API', async () => {
mock
- .onGet(endpointWithFluxResourcePath, {
+ .onGet(endpoint, {
withCredentials: true,
headers: configuration.baseOptions.headers,
})
@@ -68,8 +46,6 @@ describe('~/frontend/environments/graphql/resolvers', () => {
null,
{
configuration,
- namespace,
- environmentName,
fluxResourcePath,
},
{ client },
@@ -87,8 +63,7 @@ describe('~/frontend/environments/graphql/resolvers', () => {
null,
{
configuration,
- namespace,
- environmentName,
+ fluxResourcePath,
},
{ client },
);
@@ -108,6 +83,8 @@ describe('~/frontend/environments/graphql/resolvers', () => {
}
});
const resourceName = 'custom-resource';
+ const resourceNamespace = 'custom-namespace';
+ const apiVersion = 'kustomize.toolkit.fluxcd.io/v1beta1';
beforeEach(() => {
gon.features = { k8sWatchApi: true };
@@ -120,7 +97,8 @@ describe('~/frontend/environments/graphql/resolvers', () => {
mock
.onGet(endpoint, { withCredentials: true, headers: configuration.baseOptions.headers })
.reply(HTTP_STATUS_OK, {
- metadata: { name: resourceName },
+ apiVersion,
+ metadata: { name: resourceName, namespace: resourceNamespace },
status: { conditions: fluxKustomizationsMock },
});
});
@@ -129,14 +107,13 @@ describe('~/frontend/environments/graphql/resolvers', () => {
null,
{
configuration,
- namespace,
- environmentName,
+ fluxResourcePath,
},
{ client },
);
expect(mockKustomizationStatusFn).toHaveBeenCalledWith(
- `/apis/kustomize.toolkit.fluxcd.io/v1beta1/namespaces/${namespace}/kustomizations`,
+ `/apis/${apiVersion}/namespaces/${resourceNamespace}/kustomizations`,
{
watch: true,
fieldSelector: `metadata.name=${decodeURIComponent(resourceName)}`,
@@ -149,8 +126,7 @@ describe('~/frontend/environments/graphql/resolvers', () => {
null,
{
configuration,
- namespace,
- environmentName,
+ fluxResourcePath,
},
{ client },
);
@@ -168,8 +144,7 @@ describe('~/frontend/environments/graphql/resolvers', () => {
null,
{
configuration,
- namespace,
- environmentName,
+ fluxResourcePath,
},
{ client },
);
@@ -181,34 +156,14 @@ describe('~/frontend/environments/graphql/resolvers', () => {
describe('fluxHelmReleaseStatus', () => {
const client = { writeQuery: jest.fn() };
- const endpoint = `${configuration.basePath}/apis/helm.toolkit.fluxcd.io/v2beta1/namespaces/${namespace}/helmreleases/${environmentName}`;
const fluxResourcePath =
'helm.toolkit.fluxcd.io/v2beta1/namespaces/my-namespace/helmreleases/app';
- const endpointWithFluxResourcePath = `${configuration.basePath}/apis/${fluxResourcePath}`;
+ const endpoint = `${configuration.basePath}/apis/${fluxResourcePath}`;
describe('when k8sWatchApi feature is disabled', () => {
- it('should request Flux Helm Releases via the Kubernetes API', async () => {
- mock
- .onGet(endpoint, { withCredentials: true, headers: configuration.baseOptions.headers })
- .reply(HTTP_STATUS_OK, {
- status: { conditions: fluxKustomizationsMock },
- });
-
- const fluxHelmReleaseStatus = await mockResolvers.Query.fluxHelmReleaseStatus(
- null,
- {
- configuration,
- namespace,
- environmentName,
- },
- { client },
- );
-
- expect(fluxHelmReleaseStatus).toEqual(fluxKustomizationsMock);
- });
it('should request Flux HelmRelease for the provided fluxResourcePath via the Kubernetes API', async () => {
mock
- .onGet(endpointWithFluxResourcePath, {
+ .onGet(endpoint, {
withCredentials: true,
headers: configuration.baseOptions.headers,
})
@@ -220,8 +175,6 @@ describe('~/frontend/environments/graphql/resolvers', () => {
null,
{
configuration,
- namespace,
- environmentName,
fluxResourcePath,
},
{ client },
@@ -239,8 +192,7 @@ describe('~/frontend/environments/graphql/resolvers', () => {
null,
{
configuration,
- namespace,
- environmentName,
+ fluxResourcePath,
},
{ client },
);
@@ -260,6 +212,8 @@ describe('~/frontend/environments/graphql/resolvers', () => {
}
});
const resourceName = 'custom-resource';
+ const resourceNamespace = 'custom-namespace';
+ const apiVersion = 'helm.toolkit.fluxcd.io/v2beta1';
beforeEach(() => {
gon.features = { k8sWatchApi: true };
@@ -272,7 +226,8 @@ describe('~/frontend/environments/graphql/resolvers', () => {
mock
.onGet(endpoint, { withCredentials: true, headers: configuration.baseOptions.headers })
.reply(HTTP_STATUS_OK, {
- metadata: { name: resourceName },
+ apiVersion,
+ metadata: { name: resourceName, namespace: resourceNamespace },
status: { conditions: fluxKustomizationsMock },
});
});
@@ -281,14 +236,13 @@ describe('~/frontend/environments/graphql/resolvers', () => {
null,
{
configuration,
- namespace,
- environmentName,
+ fluxResourcePath,
},
{ client },
);
expect(mockHelmReleaseStatusFn).toHaveBeenCalledWith(
- `/apis/helm.toolkit.fluxcd.io/v2beta1/namespaces/${namespace}/helmreleases`,
+ `/apis/${apiVersion}/namespaces/${resourceNamespace}/helmreleases`,
{
watch: true,
fieldSelector: `metadata.name=${decodeURIComponent(resourceName)}`,
@@ -301,8 +255,7 @@ describe('~/frontend/environments/graphql/resolvers', () => {
null,
{
configuration,
- namespace,
- environmentName,
+ fluxResourcePath,
},
{ client },
);
@@ -320,8 +273,7 @@ describe('~/frontend/environments/graphql/resolvers', () => {
null,
{
configuration,
- namespace,
- environmentName,
+ fluxResourcePath,
},
{ client },
);
diff --git a/spec/frontend/environments/kubernetes_overview_spec.js b/spec/frontend/environments/kubernetes_overview_spec.js
index 9756859f71e..12689df586f 100644
--- a/spec/frontend/environments/kubernetes_overview_spec.js
+++ b/spec/frontend/environments/kubernetes_overview_spec.js
@@ -122,7 +122,6 @@ describe('~/environments/components/kubernetes_overview.vue', () => {
expect(findKubernetesStatusBar().props()).toEqual({
clusterHealthStatus: 'success',
configuration,
- namespace: kubernetesNamespace,
environmentName: resolvedEnvironment.name,
fluxResourcePath: fluxResourcePathMock,
});
diff --git a/spec/frontend/environments/kubernetes_status_bar_spec.js b/spec/frontend/environments/kubernetes_status_bar_spec.js
index 5dec7ca5aac..dcd628354e1 100644
--- a/spec/frontend/environments/kubernetes_status_bar_spec.js
+++ b/spec/frontend/environments/kubernetes_status_bar_spec.js
@@ -49,7 +49,6 @@ describe('~/environments/components/kubernetes_status_bar.vue', () => {
const createWrapper = ({
apolloProvider = createApolloProvider(),
clusterHealthStatus = '',
- namespace = '',
fluxResourcePath = '',
} = {}) => {
wrapper = shallowMountExtended(KubernetesStatusBar, {
@@ -57,7 +56,6 @@ describe('~/environments/components/kubernetes_status_bar.vue', () => {
clusterHealthStatus,
configuration,
environmentName,
- namespace,
fluxResourcePath,
},
apolloProvider,
@@ -88,7 +86,7 @@ describe('~/environments/components/kubernetes_status_bar.vue', () => {
});
describe('sync badge', () => {
- describe('when no namespace is provided', () => {
+ describe('when no flux resource path is provided', () => {
beforeEach(() => {
createWrapper();
});
@@ -104,7 +102,6 @@ describe('~/environments/components/kubernetes_status_bar.vue', () => {
});
describe('when flux resource path is provided', () => {
- const namespace = 'my-namespace';
let fluxResourcePath;
describe('if the provided resource is a Kustomization', () => {
@@ -112,7 +109,7 @@ describe('~/environments/components/kubernetes_status_bar.vue', () => {
fluxResourcePath =
'kustomize.toolkit.fluxcd.io/v1beta1/namespaces/my-namespace/kustomizations/app';
- createWrapper({ namespace, fluxResourcePath });
+ createWrapper({ fluxResourcePath });
});
it('requests the Kustomization resource status', () => {
@@ -120,8 +117,6 @@ describe('~/environments/components/kubernetes_status_bar.vue', () => {
{},
expect.objectContaining({
configuration,
- namespace,
- environmentName,
fluxResourcePath,
}),
expect.any(Object),
@@ -139,7 +134,7 @@ describe('~/environments/components/kubernetes_status_bar.vue', () => {
fluxResourcePath =
'helm.toolkit.fluxcd.io/v2beta1/namespaces/my-namespace/helmreleases/app';
- createWrapper({ namespace, fluxResourcePath });
+ createWrapper({ fluxResourcePath });
});
it('requests the HelmRelease resource status', () => {
@@ -147,8 +142,6 @@ describe('~/environments/components/kubernetes_status_bar.vue', () => {
{},
expect.objectContaining({
configuration,
- namespace,
- environmentName,
fluxResourcePath,
}),
expect.any(Object),
@@ -160,30 +153,6 @@ describe('~/environments/components/kubernetes_status_bar.vue', () => {
expect(fluxKustomizationStatusQuery).not.toHaveBeenCalled();
});
});
- });
-
- describe('when namespace is provided', () => {
- describe('with no Flux resources found', () => {
- beforeEach(() => {
- createWrapper({ namespace: 'my-namespace' });
- });
-
- it('requests Kustomizations', () => {
- expect(fluxKustomizationStatusQuery).toHaveBeenCalled();
- });
-
- it('requests HelmReleases when there were no Kustomizations found', async () => {
- await waitForPromises();
-
- expect(fluxHelmReleaseStatusQuery).toHaveBeenCalled();
- });
-
- it('renders sync status as Unavailable when no Kustomizations and HelmReleases found', async () => {
- await waitForPromises();
-
- expect(findSyncBadge().text()).toBe(s__('Deployment|Unavailable'));
- });
- });
describe('with Flux Kustomizations available', () => {
const createApolloProviderWithKustomizations = ({
@@ -202,63 +171,11 @@ describe('~/environments/components/kubernetes_status_bar.vue', () => {
it("doesn't request HelmReleases when the Kustomizations were found", async () => {
createWrapper({
apolloProvider: createApolloProviderWithKustomizations(),
- namespace: 'my-namespace',
});
await waitForPromises();
expect(fluxHelmReleaseStatusQuery).not.toHaveBeenCalled();
});
-
- it.each`
- status | type | badgeType
- ${'True'} | ${'Stalled'} | ${'stalled'}
- ${'True'} | ${'Reconciling'} | ${'reconciling'}
- ${'True'} | ${'Ready'} | ${'reconciled'}
- ${'False'} | ${'Ready'} | ${'failed'}
- ${'True'} | ${'Unknown'} | ${'unknown'}
- `(
- 'renders $badgeType when status is $status and type is $type',
- async ({ status, type, badgeType }) => {
- createWrapper({
- apolloProvider: createApolloProviderWithKustomizations({
- result: { status, type, message: '' },
- }),
- namespace: 'my-namespace',
- });
- await waitForPromises();
-
- const badge = SYNC_STATUS_BADGES[badgeType];
-
- expect(findSyncBadge().text()).toBe(badge.text);
- expect(findSyncBadge().props()).toMatchObject({
- icon: badge.icon,
- variant: badge.variant,
- });
- },
- );
-
- it.each`
- status | type | message | popoverTitle | popoverText
- ${'True'} | ${'Stalled'} | ${'stalled reason'} | ${s__('Deployment|Flux sync stalled')} | ${'stalled reason'}
- ${'True'} | ${'Reconciling'} | ${''} | ${undefined} | ${s__('Deployment|Flux sync reconciling')}
- ${'True'} | ${'Ready'} | ${''} | ${undefined} | ${s__('Deployment|Flux sync reconciled successfully')}
- ${'False'} | ${'Ready'} | ${'failed reason'} | ${s__('Deployment|Flux sync failed')} | ${'failed reason'}
- ${'True'} | ${'Unknown'} | ${''} | ${s__('Deployment|Flux sync status is unknown')} | ${s__('Deployment|Unable to detect state. %{linkStart}How are states detected?%{linkEnd}')}
- `(
- 'renders correct popover text when status is $status and type is $type',
- async ({ status, type, message, popoverTitle, popoverText }) => {
- createWrapper({
- apolloProvider: createApolloProviderWithKustomizations({
- result: { status, type, message },
- }),
- namespace: 'my-namespace',
- });
- await waitForPromises();
-
- expect(findPopover().text()).toMatchInterpolatedText(popoverText);
- expect(findPopover().props('title')).toBe(popoverTitle);
- },
- );
});
describe('when Flux API errored', () => {
@@ -277,7 +194,8 @@ describe('~/environments/components/kubernetes_status_bar.vue', () => {
beforeEach(async () => {
createWrapper({
apolloProvider: createApolloProviderWithErrors(),
- namespace: 'my-namespace',
+ fluxResourcePath:
+ 'kustomize.toolkit.fluxcd.io/v1beta1/namespaces/my-namespace/kustomizations/app',
});
await waitForPromises();
});
diff --git a/spec/lib/atlassian/jira_connect/client_spec.rb b/spec/lib/atlassian/jira_connect/client_spec.rb
index f7597579e7a..a692d76da77 100644
--- a/spec/lib/atlassian/jira_connect/client_spec.rb
+++ b/spec/lib/atlassian/jira_connect/client_spec.rb
@@ -447,38 +447,77 @@ RSpec.describe Atlassian::JiraConnect::Client, feature_category: :integrations d
end
describe '#user_info' do
- let(:account_id) { '12345' }
- let(:response_body) do
- {
- groups: {
- items: [
- { name: 'site-admins' }
- ]
- }
- }.to_json
- end
+ context 'when user is a site administrator' do
+ let(:account_id) { '12345' }
+ let(:response_body) do
+ {
+ groups: {
+ items: [
+ { name: 'site-admins' }
+ ]
+ }
+ }.to_json
+ end
- before do
- stub_full_request("https://gitlab-test.atlassian.net/rest/api/3/user?accountId=#{account_id}&expand=groups")
- .to_return(status: response_status, body: response_body, headers: { 'Content-Type': 'application/json' })
- end
+ before do
+ stub_full_request("https://gitlab-test.atlassian.net/rest/api/3/user?accountId=#{account_id}&expand=groups")
+ .to_return(status: response_status, body: response_body, headers: { 'Content-Type': 'application/json' })
+ end
- context 'with a successful response' do
- let(:response_status) { 200 }
+ context 'with a successful response' do
+ let(:response_status) { 200 }
- it 'returns a JiraUser instance' do
- jira_user = client.user_info(account_id)
+ it 'returns a JiraUser instance' do
+ jira_user = client.user_info(account_id)
- expect(jira_user).to be_a(Atlassian::JiraConnect::JiraUser)
- expect(jira_user).to be_site_admin
+ expect(jira_user).to be_a(Atlassian::JiraConnect::JiraUser)
+ expect(jira_user).to be_jira_admin
+ end
+ end
+
+ context 'with a failed response' do
+ let(:response_status) { 401 }
+
+ it 'returns nil' do
+ expect(client.user_info(account_id)).to be_nil
+ end
end
end
- context 'with a failed response' do
- let(:response_status) { 401 }
+ context 'when user is an organization administrator' do
+ let(:account_id) { '12345' }
+ let(:response_body) do
+ {
+ groups: {
+ items: [
+ { name: 'org-admins' }
+ ]
+ }
+ }.to_json
+ end
- it 'returns nil' do
- expect(client.user_info(account_id)).to be_nil
+ before do
+ stub_full_request("https://gitlab-test.atlassian.net/rest/api/3/user?accountId=#{account_id}&expand=groups")
+ .to_return(status: response_status, body: response_body, headers: { 'Content-Type': 'application/json' })
+ end
+
+ context 'with a successful response' do
+ let(:response_status) { 200 }
+
+ it 'returns a JiraUser instance' do
+ jira_user = client.user_info(account_id)
+
+ expect(jira_user).to be_a(Atlassian::JiraConnect::JiraUser)
+ expect(jira_user).to be_jira_admin
+ end
+ end
+
+ context 'with a failed response' do
+ let(:response_status) { 401 }
+
+ it 'returns nil' do
+ expect(client.user_info(account_id)).to be_nil
+ end
end
end
end
diff --git a/spec/lib/gitlab/database/migration_helpers/convert_to_bigint_spec.rb b/spec/lib/gitlab/database/migration_helpers/convert_to_bigint_spec.rb
index 1ff157b51d4..b0384a37746 100644
--- a/spec/lib/gitlab/database/migration_helpers/convert_to_bigint_spec.rb
+++ b/spec/lib/gitlab/database/migration_helpers/convert_to_bigint_spec.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Database::MigrationHelpers::ConvertToBigint, feature_category: :database do
let(:migration) do
Class
- .new
+ .new(Gitlab::Database::Migration[2.1])
.include(described_class)
.include(Gitlab::Database::MigrationHelpers)
.new
@@ -73,4 +73,135 @@ RSpec.describe Gitlab::Database::MigrationHelpers::ConvertToBigint, feature_cate
expect(migration.columns_swapped?(:test_table, :id)).to eq(false)
end
end
+
+ describe '#add_bigint_column_indexes' do
+ let(:connection) { migration.connection }
+
+ let(:table_name) { '_test_table_bigint_indexes' }
+ let(:int_column) { 'token' }
+ let(:bigint_column) { 'token_convert_to_bigint' }
+
+ subject(:add_bigint_column_indexes) { migration.add_bigint_column_indexes(table_name, int_column) }
+
+ before do
+ connection.execute(<<~SQL)
+ CREATE TABLE IF NOT EXISTS public.#{table_name} (
+ name varchar(40),
+ #{int_column} integer
+ );
+ SQL
+
+ allow(migration).to receive(:transaction_open?).and_return(false)
+ allow(migration).to receive(:disable_statement_timeout).and_call_original
+ end
+
+ after do
+ connection.execute("DROP TABLE IF EXISTS #{table_name}")
+ end
+
+ context 'without corresponding bigint column' do
+ let(:error_msg) { "Bigint column '#{bigint_column}' does not exist on #{table_name}" }
+
+ it { expect { subject }.to raise_error(RuntimeError, error_msg) }
+ end
+
+ context 'with corresponding bigint column' do
+ let(:indexes) { connection.indexes(table_name) }
+ let(:int_column_indexes) { indexes.select { |i| i.columns.include?(int_column) } }
+ let(:bigint_column_indexes) { indexes.select { |i| i.columns.include?(bigint_column) } }
+
+ before do
+ connection.execute("ALTER TABLE #{table_name} ADD COLUMN #{bigint_column} bigint")
+ end
+
+ context 'without the integer column index' do
+ it 'does not create new bigint index' do
+ expect(int_column_indexes).to be_empty
+
+ add_bigint_column_indexes
+
+ expect(bigint_column_indexes).to be_empty
+ end
+ end
+
+ context 'with integer column indexes' do
+ let(:bigint_index_name) { ->(int_index_name) { migration.bigint_index_name(int_index_name) } }
+ let(:expected_bigint_indexes) do
+ [
+ {
+ name: bigint_index_name.call("hash_idx_#{table_name}"),
+ column: [bigint_column],
+ using: 'hash'
+ },
+ {
+ name: bigint_index_name.call("idx_#{table_name}"),
+ column: [bigint_column],
+ using: 'btree'
+ },
+ {
+ name: bigint_index_name.call("idx_#{table_name}_combined"),
+ column: "#{bigint_column}, lower((name)::text)",
+ where: "(#{bigint_column} IS NOT NULL)",
+ using: 'btree'
+ },
+ {
+ name: bigint_index_name.call("idx_#{table_name}_functional"),
+ column: "#{bigint_column}, lower((name)::text)",
+ using: 'btree'
+ },
+ {
+ name: bigint_index_name.call("idx_#{table_name}_ordered"),
+ column: [bigint_column],
+ order: 'DESC NULLS LAST',
+ using: 'btree'
+ },
+ {
+ name: bigint_index_name.call("idx_#{table_name}_ordered_multiple"),
+ column: [bigint_column, 'name'],
+ order: { bigint_column => 'DESC NULLS LAST', 'name' => 'desc' },
+ using: 'btree'
+ },
+ {
+ name: bigint_index_name.call("idx_#{table_name}_partial"),
+ column: [bigint_column],
+ where: "(#{bigint_column} IS NOT NULL)",
+ using: 'btree'
+ },
+ {
+ name: bigint_index_name.call("uniq_idx_#{table_name}"),
+ column: [bigint_column],
+ unique: true,
+ using: 'btree'
+ }
+ ]
+ end
+
+ before do
+ connection.execute(<<~SQL)
+ CREATE INDEX "hash_idx_#{table_name}" ON #{table_name} USING hash (#{int_column});
+ CREATE INDEX "idx_#{table_name}" ON #{table_name} USING btree (#{int_column});
+ CREATE INDEX "idx_#{table_name}_combined" ON #{table_name} USING btree (#{int_column}, lower((name)::text)) WHERE (#{int_column} IS NOT NULL);
+ CREATE INDEX "idx_#{table_name}_functional" ON #{table_name} USING btree (#{int_column}, lower((name)::text));
+ CREATE INDEX "idx_#{table_name}_ordered" ON #{table_name} USING btree (#{int_column} DESC NULLS LAST);
+ CREATE INDEX "idx_#{table_name}_ordered_multiple" ON #{table_name} USING btree (#{int_column} DESC NULLS LAST, name DESC);
+ CREATE INDEX "idx_#{table_name}_partial" ON #{table_name} USING btree (#{int_column}) WHERE (#{int_column} IS NOT NULL);
+ CREATE UNIQUE INDEX "uniq_idx_#{table_name}" ON #{table_name} USING btree (#{int_column});
+ SQL
+ end
+
+ it 'creates appropriate bigint indexes' do
+ expected_bigint_indexes.each do |bigint_index|
+ expect(migration).to receive(:add_concurrent_index).with(
+ table_name,
+ bigint_index[:column],
+ name: bigint_index[:name],
+ ** bigint_index.except(:name, :column)
+ )
+ end
+
+ add_bigint_column_indexes
+ end
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/health_checks/gitaly_check_spec.rb b/spec/lib/gitlab/health_checks/gitaly_check_spec.rb
index 64c4e92f80b..8f676d20c22 100644
--- a/spec/lib/gitlab/health_checks/gitaly_check_spec.rb
+++ b/spec/lib/gitlab/health_checks/gitaly_check_spec.rb
@@ -4,11 +4,6 @@ require 'fast_spec_helper'
RSpec.describe Gitlab::HealthChecks::GitalyCheck do
let(:result_class) { Gitlab::HealthChecks::Result }
- let(:repository_storages) { ['default'] }
-
- before do
- allow(described_class).to receive(:repository_storages) { repository_storages }
- end
describe '#readiness' do
subject { described_class.readiness }
diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb
index f2f79311fdf..a2d6c60fbd0 100644
--- a/spec/models/application_setting_spec.rb
+++ b/spec/models/application_setting_spec.rb
@@ -822,15 +822,6 @@ RSpec.describe ApplicationSetting, feature_category: :shared, type: :model do
subject { setting }
end
- # Upgraded databases will have this sort of content
- context 'repository_storages is a String, not an Array' do
- before do
- described_class.where(id: setting.id).update_all(repository_storages: 'default')
- end
-
- it { expect(setting.repository_storages).to eq(['default']) }
- end
-
context 'auto_devops_domain setting' do
context 'when auto_devops_enabled? is true' do
before do
@@ -867,31 +858,6 @@ RSpec.describe ApplicationSetting, feature_category: :shared, type: :model do
end
end
- context 'repository storages' do
- before do
- storages = {
- 'custom1' => 'tmp/tests/custom_repositories_1',
- 'custom2' => 'tmp/tests/custom_repositories_2',
- 'custom3' => 'tmp/tests/custom_repositories_3'
-
- }
- allow(Gitlab.config.repositories).to receive(:storages).and_return(storages)
- end
-
- describe 'inclusion' do
- it { is_expected.to allow_value('custom1').for(:repository_storages) }
- it { is_expected.to allow_value(%w[custom2 custom3]).for(:repository_storages) }
- it { is_expected.not_to allow_value('alternative').for(:repository_storages) }
- it { is_expected.not_to allow_value(%w[alternative custom1]).for(:repository_storages) }
- end
-
- describe 'presence' do
- it { is_expected.not_to allow_value([]).for(:repository_storages) }
- it { is_expected.not_to allow_value("").for(:repository_storages) }
- it { is_expected.not_to allow_value(nil).for(:repository_storages) }
- end
- end
-
context 'housekeeping settings' do
it { is_expected.not_to allow_value(0).for(:housekeeping_optimize_repository_period) }
end
diff --git a/spec/services/ci/catalog/resources/release_service_spec.rb b/spec/services/ci/catalog/resources/release_service_spec.rb
index 1901485d402..60cd6cb5f96 100644
--- a/spec/services/ci/catalog/resources/release_service_spec.rb
+++ b/spec/services/ci/catalog/resources/release_service_spec.rb
@@ -31,7 +31,9 @@ RSpec.describe Ci::Catalog::Resources::ReleaseService, feature_category: :pipeli
expect(Ci::Catalog::Resources::Version.count).to be(0)
expect(response).to be_error
- expect(response.message).to eq('Project must have a description, Project must contain components')
+ expect(response.message).to eq(
+ 'Project must have a description, ' \
+ 'Project must contain components. Ensure you are using the correct directory structure')
end
end
diff --git a/spec/services/ci/catalog/resources/validate_service_spec.rb b/spec/services/ci/catalog/resources/validate_service_spec.rb
index 597451db486..39ab758d78d 100644
--- a/spec/services/ci/catalog/resources/validate_service_spec.rb
+++ b/spec/services/ci/catalog/resources/validate_service_spec.rb
@@ -19,7 +19,9 @@ RSpec.describe Ci::Catalog::Resources::ValidateService, feature_category: :pipel
response = described_class.new(project, project.default_branch).execute
expect(response.message).to eq(
- 'Project must have a README, Project must have a description, Project must contain components')
+ 'Project must have a README, ' \
+ 'Project must have a description, ' \
+ 'Project must contain components. Ensure you are using the correct directory structure')
end
end
@@ -37,7 +39,9 @@ RSpec.describe Ci::Catalog::Resources::ValidateService, feature_category: :pipel
project = create(:project, :small_repo, description: 'project with no README and no components')
response = described_class.new(project, project.default_branch).execute
- expect(response.message).to eq('Project must have a README, Project must contain components')
+ expect(response.message).to eq(
+ 'Project must have a README, ' \
+ 'Project must contain components. Ensure you are using the correct directory structure')
end
end
@@ -46,7 +50,9 @@ RSpec.describe Ci::Catalog::Resources::ValidateService, feature_category: :pipel
project = create(:project, :repository)
response = described_class.new(project, project.default_branch).execute
- expect(response.message).to eq('Project must have a description, Project must contain components')
+ expect(response.message).to eq(
+ 'Project must have a description, ' \
+ 'Project must contain components. Ensure you are using the correct directory structure')
end
end
@@ -74,7 +80,8 @@ RSpec.describe Ci::Catalog::Resources::ValidateService, feature_category: :pipel
project = create(:project, :readme, description: 'project with no README and no components')
response = described_class.new(project, project.default_branch).execute
- expect(response.message).to eq('Project must contain components')
+ expect(response.message).to eq(
+ 'Project must contain components. Ensure you are using the correct directory structure')
end
end
end
diff --git a/spec/services/jira_connect_subscriptions/create_service_spec.rb b/spec/services/jira_connect_subscriptions/create_service_spec.rb
index f9d3954b84c..2296d0fbfed 100644
--- a/spec/services/jira_connect_subscriptions/create_service_spec.rb
+++ b/spec/services/jira_connect_subscriptions/create_service_spec.rb
@@ -9,7 +9,7 @@ RSpec.describe JiraConnectSubscriptions::CreateService, feature_category: :integ
let(:path) { group.full_path }
let(:params) { { namespace_path: path, jira_user: jira_user } }
- let(:jira_user) { double(:JiraUser, site_admin?: true) }
+ let(:jira_user) { double(:JiraUser, jira_admin?: true) }
subject { described_class.new(installation, current_user, params).execute }
@@ -29,11 +29,11 @@ RSpec.describe JiraConnectSubscriptions::CreateService, feature_category: :integ
end
context 'remote user does not have access' do
- let(:jira_user) { double(site_admin?: false) }
+ let(:jira_user) { double(jira_admin?: false) }
it_behaves_like 'a failed execution',
http_status: 403,
- message: 'The Jira user is not a site administrator. Check the permissions in Jira and try again.'
+ message: 'The Jira user is not a site or organization administrator. Check the permissions in Jira and try again.'
end
context 'remote user cannot be retrieved' do
diff --git a/spec/services/releases/create_service_spec.rb b/spec/services/releases/create_service_spec.rb
index b28d7549fbb..3504f00412c 100644
--- a/spec/services/releases/create_service_spec.rb
+++ b/spec/services/releases/create_service_spec.rb
@@ -71,7 +71,10 @@ RSpec.describe Releases::CreateService, feature_category: :continuous_integratio
result = service.execute
expect(result[:status]).to eq(:error)
- expect(result[:message]).to eq('Project must have a description, Project must contain components')
+ expect(result[:http_status]).to eq(422)
+ expect(result[:message]).to eq(
+ 'Project must have a description, ' \
+ 'Project must contain components. Ensure you are using the correct directory structure')
end
end
end
@@ -105,6 +108,7 @@ RSpec.describe Releases::CreateService, feature_category: :continuous_integratio
result = service.execute
expect(result[:status]).to eq(:error)
+ expect(result[:http_status]).to eq(403)
end
end
@@ -140,6 +144,7 @@ RSpec.describe Releases::CreateService, feature_category: :continuous_integratio
it 'raises an error and does not update the release' do
result = service.execute
expect(result[:status]).to eq(:error)
+ expect(result[:http_status]).to eq(409)
expect(project.releases.find_by(tag: tag_name).description).to eq(description)
end
end
@@ -151,6 +156,7 @@ RSpec.describe Releases::CreateService, feature_category: :continuous_integratio
result = service.execute
expect(result[:status]).to eq(:error)
+ expect(result[:http_status]).to eq(400)
expect(result[:message]).to eq("Milestone(s) not found: #{inexistent_milestone_tag}")
end
@@ -160,6 +166,7 @@ RSpec.describe Releases::CreateService, feature_category: :continuous_integratio
result = service.execute
expect(result[:status]).to eq(:error)
+ expect(result[:http_status]).to eq(400)
expect(result[:message]).to eq("Milestone id(s) not found: #{inexistent_milestone_id}")
end
end
@@ -245,6 +252,7 @@ RSpec.describe Releases::CreateService, feature_category: :continuous_integratio
result = service.execute
expect(result[:status]).to eq(:error)
+ expect(result[:http_status]).to eq(400)
expect(result[:message]).to eq("Milestone(s) not found: #{inexistent_title}")
end
@@ -261,6 +269,7 @@ RSpec.describe Releases::CreateService, feature_category: :continuous_integratio
result = service.execute
expect(result[:status]).to eq(:error)
+ expect(result[:http_status]).to eq(400)
expect(result[:message]).to eq("Milestone id(s) not found: #{non_existing_record_id}")
end
end
diff --git a/spec/support/helpers/database/duplicate_indexes.yml b/spec/support/helpers/database/duplicate_indexes.yml
index bb2802ba6d2..b4532ca7d7a 100644
--- a/spec/support/helpers/database/duplicate_indexes.yml
+++ b/spec/support/helpers/database/duplicate_indexes.yml
@@ -2,255 +2,253 @@
# It maps table_name to {index1: array_of_duplicate_indexes, index2: array_of_duplicate_indexes, ... }
abuse_reports:
idx_abuse_reports_user_id_status_and_category:
- - index_abuse_reports_on_user_id
+ - index_abuse_reports_on_user_id
alert_management_http_integrations:
index_http_integrations_on_project_and_endpoint:
- - index_alert_management_http_integrations_on_project_id
+ - index_alert_management_http_integrations_on_project_id
approval_project_rules_users:
index_approval_project_rules_users_1:
- - index_approval_project_rules_users_on_approval_project_rule_id
+ - index_approval_project_rules_users_on_approval_project_rule_id
approvals:
index_approvals_on_merge_request_id_and_created_at:
- - index_approvals_on_merge_request_id
+ - index_approvals_on_merge_request_id
board_group_recent_visits:
index_board_group_recent_visits_on_user_group_and_board:
- - index_board_group_recent_visits_on_user_id
+ - index_board_group_recent_visits_on_user_id
board_project_recent_visits:
index_board_project_recent_visits_on_user_project_and_board:
- - index_board_project_recent_visits_on_user_id
+ - index_board_project_recent_visits_on_user_id
board_user_preferences:
index_board_user_preferences_on_user_id_and_board_id:
- - index_board_user_preferences_on_user_id
+ - index_board_user_preferences_on_user_id
boards_epic_board_recent_visits:
index_epic_board_recent_visits_on_user_group_and_board:
- - index_boards_epic_board_recent_visits_on_user_id
+ - index_boards_epic_board_recent_visits_on_user_id
boards_epic_user_preferences:
index_boards_epic_user_preferences_on_board_user_epic_unique:
- - index_boards_epic_user_preferences_on_board_id
+ - index_boards_epic_user_preferences_on_board_id
bulk_import_batch_trackers:
i_bulk_import_trackers_id_batch_number:
- - index_bulk_import_batch_trackers_on_tracker_id
+ - index_bulk_import_batch_trackers_on_tracker_id
bulk_import_export_batches:
i_bulk_import_export_batches_id_batch_number:
- - index_bulk_import_export_batches_on_export_id
+ - index_bulk_import_export_batches_on_export_id
ci_job_artifacts:
index_ci_job_artifacts_on_id_project_id_and_created_at:
- - index_ci_job_artifacts_on_project_id
+ - index_ci_job_artifacts_on_project_id
index_ci_job_artifacts_on_id_project_id_and_file_type:
- - index_ci_job_artifacts_on_project_id
+ - index_ci_job_artifacts_on_project_id
index_ci_job_artifacts_on_project_id_and_id:
- - index_ci_job_artifacts_on_project_id
+ - index_ci_job_artifacts_on_project_id
ci_pipeline_artifacts:
index_ci_pipeline_artifacts_on_pipeline_id_and_file_type:
- - index_ci_pipeline_artifacts_on_pipeline_id
+ - index_ci_pipeline_artifacts_on_pipeline_id
ci_stages:
index_ci_stages_on_pipeline_id_and_name:
- - index_ci_stages_on_pipeline_id
+ - index_ci_stages_on_pipeline_id
index_ci_stages_on_pipeline_id_and_position:
- - index_ci_stages_on_pipeline_id
+ - index_ci_stages_on_pipeline_id
index_ci_stages_on_pipeline_id_convert_to_bigint_and_name:
- - index_ci_stages_on_pipeline_id_convert_to_bigint
+ - index_ci_stages_on_pipeline_id_convert_to_bigint
index_ci_stages_on_pipeline_id_convert_to_bigint_and_position:
- - index_ci_stages_on_pipeline_id_convert_to_bigint
+ - index_ci_stages_on_pipeline_id_convert_to_bigint
dast_site_tokens:
index_dast_site_token_on_project_id_and_url:
- - index_dast_site_tokens_on_project_id
+ - index_dast_site_tokens_on_project_id
design_management_designs:
index_design_management_designs_on_iid_and_project_id:
- - index_design_management_designs_on_project_id
+ - index_design_management_designs_on_project_id
design_management_designs_versions:
design_management_designs_versions_uniqueness:
- - index_design_management_designs_versions_on_design_id
+ - index_design_management_designs_versions_on_design_id
error_tracking_errors:
index_et_errors_on_project_id_and_status_and_id:
- - index_error_tracking_errors_on_project_id
+ - index_error_tracking_errors_on_project_id
index_et_errors_on_project_id_and_status_events_count_id_desc:
- - index_error_tracking_errors_on_project_id
+ - index_error_tracking_errors_on_project_id
index_et_errors_on_project_id_and_status_first_seen_at_id_desc:
- - index_error_tracking_errors_on_project_id
+ - index_error_tracking_errors_on_project_id
index_et_errors_on_project_id_and_status_last_seen_at_id_desc:
- - index_error_tracking_errors_on_project_id
+ - index_error_tracking_errors_on_project_id
geo_node_namespace_links:
index_geo_node_namespace_links_on_geo_node_id_and_namespace_id:
- - index_geo_node_namespace_links_on_geo_node_id
+ - index_geo_node_namespace_links_on_geo_node_id
in_product_marketing_emails:
- index_in_product_marketing_emails_on_user_campaign:
- - index_in_product_marketing_emails_on_user_id
index_in_product_marketing_emails_on_user_track_series:
- - index_in_product_marketing_emails_on_user_id
+ - index_in_product_marketing_emails_on_user_id
incident_management_oncall_participants:
index_inc_mgmnt_oncall_participants_on_user_id_and_rotation_id:
- - index_inc_mgmnt_oncall_participants_on_oncall_user_id
+ - index_inc_mgmnt_oncall_participants_on_oncall_user_id
incident_management_oncall_schedules:
index_im_oncall_schedules_on_project_id_and_iid:
- - index_incident_management_oncall_schedules_on_project_id
+ - index_incident_management_oncall_schedules_on_project_id
instance_audit_events_streaming_headers:
idx_instance_external_audit_event_destination_id_key_uniq:
- - idx_headers_instance_external_audit_event_destination_id
+ - idx_headers_instance_external_audit_event_destination_id
issue_links:
index_issue_links_on_source_id_and_target_id:
- - index_issue_links_on_source_id
+ - index_issue_links_on_source_id
issues:
index_issues_on_author_id_and_id_and_created_at:
- - index_issues_on_author_id
+ - index_issues_on_author_id
jira_connect_subscriptions:
idx_jira_connect_subscriptions_on_installation_id_namespace_id:
- - idx_jira_connect_subscriptions_on_installation_id
+ - idx_jira_connect_subscriptions_on_installation_id
list_user_preferences:
index_list_user_preferences_on_user_id_and_list_id:
- - index_list_user_preferences_on_user_id
+ - index_list_user_preferences_on_user_id
member_tasks:
index_member_tasks_on_member_id_and_project_id:
- - index_member_tasks_on_member_id
+ - index_member_tasks_on_member_id
members:
index_members_on_member_namespace_id_compound:
- - index_members_on_member_namespace_id
+ - index_members_on_member_namespace_id
merge_request_assignees:
index_merge_request_assignees_on_merge_request_id_and_user_id:
- - index_merge_request_assignees_on_merge_request_id
+ - index_merge_request_assignees_on_merge_request_id
merge_requests:
index_merge_requests_on_author_id_and_created_at:
- - index_merge_requests_on_author_id
+ - index_merge_requests_on_author_id
index_merge_requests_on_author_id_and_id:
- - index_merge_requests_on_author_id
+ - index_merge_requests_on_author_id
index_merge_requests_on_author_id_and_target_project_id:
- - index_merge_requests_on_author_id
+ - index_merge_requests_on_author_id
ml_candidate_params:
index_ml_candidate_params_on_candidate_id_on_name:
- - index_ml_candidate_params_on_candidate_id
+ - index_ml_candidate_params_on_candidate_id
ml_candidates:
index_ml_candidates_on_project_id_on_internal_id:
- - index_ml_candidates_on_project_id
+ - index_ml_candidates_on_project_id
ml_model_versions:
index_ml_model_versions_on_project_id_and_model_id_and_version:
- - index_ml_model_versions_on_project_id
+ - index_ml_model_versions_on_project_id
ml_models:
index_ml_models_on_project_id_and_name:
- - index_ml_models_on_project_id
+ - index_ml_models_on_project_id
p_ci_runner_machine_builds:
index_p_ci_runner_machine_builds_on_runner_machine_id:
- - index_ci_runner_machine_builds_on_runner_machine_id
+ - index_ci_runner_machine_builds_on_runner_machine_id
packages_debian_group_distributions:
uniq_pkgs_debian_group_distributions_group_id_and_codename:
- - index_packages_debian_group_distributions_on_group_id
+ - index_packages_debian_group_distributions_on_group_id
uniq_pkgs_debian_group_distributions_group_id_and_suite:
- - index_packages_debian_group_distributions_on_group_id
+ - index_packages_debian_group_distributions_on_group_id
packages_debian_project_distributions:
uniq_pkgs_debian_project_distributions_project_id_and_codename:
- - index_packages_debian_project_distributions_on_project_id
+ - index_packages_debian_project_distributions_on_project_id
uniq_pkgs_debian_project_distributions_project_id_and_suite:
- - index_packages_debian_project_distributions_on_project_id
+ - index_packages_debian_project_distributions_on_project_id
packages_tags:
index_packages_tags_on_package_id_and_updated_at:
- - index_packages_tags_on_package_id
+ - index_packages_tags_on_package_id
pages_domains:
index_pages_domains_on_project_id_and_enabled_until:
- - index_pages_domains_on_project_id
+ - index_pages_domains_on_project_id
index_pages_domains_on_verified_at_and_enabled_until:
- - index_pages_domains_on_verified_at
+ - index_pages_domains_on_verified_at
personal_access_tokens:
index_pat_on_user_id_and_expires_at:
- - index_personal_access_tokens_on_user_id
+ - index_personal_access_tokens_on_user_id
pm_affected_packages:
i_affected_packages_unique_for_upsert:
- - index_pm_affected_packages_on_pm_advisory_id
+ - index_pm_affected_packages_on_pm_advisory_id
pm_package_version_licenses:
i_pm_package_version_licenses_join_ids:
- - index_pm_package_version_licenses_on_pm_package_version_id
+ - index_pm_package_version_licenses_on_pm_package_version_id
pm_package_versions:
i_pm_package_versions_on_package_id_and_version:
- - index_pm_package_versions_on_pm_package_id
+ - index_pm_package_versions_on_pm_package_id
project_compliance_standards_adherence:
u_project_compliance_standards_adherence_for_reporting:
- - index_project_compliance_standards_adherence_on_project_id
+ - index_project_compliance_standards_adherence_on_project_id
project_relation_exports:
index_project_export_job_relation:
- - index_project_relation_exports_on_project_export_job_id
+ - index_project_relation_exports_on_project_export_job_id
project_repositories:
index_project_repositories_on_shard_id_and_project_id:
- - index_project_repositories_on_shard_id
+ - index_project_repositories_on_shard_id
project_topics:
index_project_topics_on_project_id_and_topic_id:
- - index_project_topics_on_project_id
+ - index_project_topics_on_project_id
projects:
index_projects_api_path_id_desc:
- - index_on_projects_path
+ - index_on_projects_path
index_projects_on_path_and_id:
- - index_on_projects_path
+ - index_on_projects_path
protected_environments:
index_protected_environments_on_project_id_and_name:
- - index_protected_environments_on_project_id
+ - index_protected_environments_on_project_id
protected_tags:
index_protected_tags_on_project_id_and_name:
- - index_protected_tags_on_project_id
+ - index_protected_tags_on_project_id
related_epic_links:
index_related_epic_links_on_source_id_and_target_id:
- - index_related_epic_links_on_source_id
+ - index_related_epic_links_on_source_id
requirements_management_test_reports:
idx_test_reports_on_issue_id_created_at_and_id:
- - index_requirements_management_test_reports_on_issue_id
+ - index_requirements_management_test_reports_on_issue_id
sbom_component_versions:
index_sbom_component_versions_on_component_id_and_version:
- - index_sbom_component_versions_on_component_id
+ - index_sbom_component_versions_on_component_id
sbom_occurrences:
index_sbom_occurrences_for_input_file_path_search:
- - index_sbom_occurrences_on_project_id_component_id
- - index_sbom_occurrences_on_project_id
+ - index_sbom_occurrences_on_project_id_component_id
+ - index_sbom_occurrences_on_project_id
idx_sbom_occurrences_on_project_id_and_source_id:
- - index_sbom_occurrences_on_project_id
+ - index_sbom_occurrences_on_project_id
index_sbom_occurrences_on_project_id_and_id:
- - index_sbom_occurrences_on_project_id
+ - index_sbom_occurrences_on_project_id
index_sbom_occurrences_on_project_id_component_id:
- - index_sbom_occurrences_on_project_id
+ - index_sbom_occurrences_on_project_id
index_sbom_occurrences_on_project_id_and_component_id_and_id:
- - index_sbom_occurrences_on_project_id_component_id
- - index_sbom_occurrences_on_project_id
+ - index_sbom_occurrences_on_project_id_component_id
+ - index_sbom_occurrences_on_project_id
index_sbom_occurrences_on_project_id_and_package_manager:
- - index_sbom_occurrences_on_project_id
+ - index_sbom_occurrences_on_project_id
search_namespace_index_assignments:
index_search_namespace_index_assignments_uniqueness_index_type:
- - index_search_namespace_index_assignments_on_namespace_id
+ - index_search_namespace_index_assignments_on_namespace_id
index_search_namespace_index_assignments_uniqueness_on_index_id:
- - index_search_namespace_index_assignments_on_namespace_id
+ - index_search_namespace_index_assignments_on_namespace_id
sprints:
sequence_is_unique_per_iterations_cadence_id:
- - index_sprints_iterations_cadence_id
+ - index_sprints_iterations_cadence_id
taggings:
taggings_idx:
- - index_taggings_on_tag_id
+ - index_taggings_on_tag_id
term_agreements:
term_agreements_unique_index:
- - index_term_agreements_on_user_id
+ - index_term_agreements_on_user_id
todos:
index_todos_on_author_id_and_created_at:
- - index_todos_on_author_id
+ - index_todos_on_author_id
user_callouts:
index_user_callouts_on_user_id_and_feature_name:
- - index_user_callouts_on_user_id
+ - index_user_callouts_on_user_id
users:
index_users_on_state_and_user_type:
- - index_users_on_state
+ - index_users_on_state
vulnerabilities:
index_vulnerabilities_project_id_state_severity_default_branch:
- - index_vulnerabilities_on_project_id_and_state_and_severity
+ - index_vulnerabilities_on_project_id_and_state_and_severity
vulnerability_external_issue_links:
idx_vulnerability_ext_issue_links_on_vulne_id_and_ext_issue:
- - index_vulnerability_external_issue_links_on_vulnerability_id
+ - index_vulnerability_external_issue_links_on_vulnerability_id
vulnerability_finding_links:
finding_link_name_url_idx:
- - finding_links_on_vulnerability_occurrence_id
+ - finding_links_on_vulnerability_occurrence_id
vulnerability_finding_signatures:
idx_vuln_signatures_uniqueness_signature_sha:
- - index_vulnerability_finding_signatures_on_finding_id
+ - index_vulnerability_finding_signatures_on_finding_id
vulnerability_flags:
index_vulnerability_flags_on_unique_columns:
- - index_vulnerability_flags_on_vulnerability_occurrence_id
+ - index_vulnerability_flags_on_vulnerability_occurrence_id
web_hook_logs:
index_web_hook_logs_on_web_hook_id_and_created_at:
- - index_web_hook_logs_part_on_web_hook_id
+ - index_web_hook_logs_part_on_web_hook_id
web_hooks:
index_web_hooks_on_project_id_recent_failures:
- - index_web_hooks_on_project_id
+ - index_web_hooks_on_project_id
work_item_hierarchy_restrictions:
index_work_item_hierarchy_restrictions_on_parent_and_child:
- - index_work_item_hierarchy_restrictions_on_parent_type_id
+ - index_work_item_hierarchy_restrictions_on_parent_type_id