Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
529d3153bc
commit
963e09aecf
|
|
@ -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.'),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
>
|
||||
<gl-tab
|
||||
:query-param-value="$options.ENVIRONMENTS_SCOPE.AVAILABLE"
|
||||
@click="setScope($options.ENVIRONMENTS_SCOPE.AVAILABLE)"
|
||||
:query-param-value="$options.ENVIRONMENTS_SCOPE.ACTIVE"
|
||||
@click="setScope($options.ENVIRONMENTS_SCOPE.ACTIVE)"
|
||||
>
|
||||
<template #title>
|
||||
<span>{{ $options.i18n.available }}</span>
|
||||
<span>{{ $options.i18n.active }}</span>
|
||||
<gl-badge size="sm" class="gl-tab-counter-badge">
|
||||
{{ availableCount }}
|
||||
{{ activeCount }}
|
||||
</gl-badge>
|
||||
</template>
|
||||
</gl-tab>
|
||||
|
|
|
|||
|
|
@ -111,7 +111,6 @@ export default {
|
|||
<kubernetes-status-bar
|
||||
:cluster-health-status="clusterHealthStatus"
|
||||
:configuration="k8sAccessConfiguration"
|
||||
:namespace="namespace"
|
||||
:environment-name="environmentName"
|
||||
:flux-resource-path="fluxResourcePath"
|
||||
class="gl-mb-3" />
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
query getEnvironmentFolder($environment: NestedLocalEnvironment, $scope: String, $search: String) {
|
||||
folder(environment: $environment, scope: $scope, search: $search) @client {
|
||||
availableCount
|
||||
activeCount
|
||||
environments
|
||||
stoppedCount
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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 ||
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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?
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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!
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -0,0 +1 @@
|
|||
c8dbdeb4ffcb7f5dc1c719a09a1f6c41188f584c80331a4482542a873d3ad12d
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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: <your_access_token>" \
|
||||
|
|
|
|||
|
|
@ -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`. |
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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`.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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 () => {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 },
|
||||
);
|
||||
|
|
|
|||
|
|
@ -122,7 +122,6 @@ describe('~/environments/components/kubernetes_overview.vue', () => {
|
|||
expect(findKubernetesStatusBar().props()).toEqual({
|
||||
clusterHealthStatus: 'success',
|
||||
configuration,
|
||||
namespace: kubernetesNamespace,
|
||||
environmentName: resolvedEnvironment.name,
|
||||
fluxResourcePath: fluxResourcePathMock,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue