diff --git a/.gitlab/ci/test-on-cng/main.gitlab-ci.yml b/.gitlab/ci/test-on-cng/main.gitlab-ci.yml
index 5430a598177..848aae25b9b 100644
--- a/.gitlab/ci/test-on-cng/main.gitlab-ci.yml
+++ b/.gitlab/ci/test-on-cng/main.gitlab-ci.yml
@@ -25,6 +25,7 @@ workflow:
--chart-sha "${GITLAB_HELM_CHART_REF}" \
--admin-token "${GITLAB_QA_ADMIN_ACCESS_TOKEN}" \
--retry 1 \
+ --resource-preset high \
--ci \
${EXTRA_DEPLOY_VALUES}
- export QA_GITLAB_URL="http://gitlab.${GITLAB_DOMAIN}"
@@ -66,6 +67,7 @@ workflow:
- |
bundle exec orchestrator create deployment "${DEPLOYMENT_TYPE}" \
--chart-sha "${GITLAB_HELM_CHART_REF}" \
+ --resource-preset high \
--ci \
--print-deploy-args \
$(cat $CI_PROJECT_DIR/EXTRA_DEPLOY_VALUES)
diff --git a/.gitlab/issue_templates/SAST Rules release.md b/.gitlab/issue_templates/SAST Rules release.md
similarity index 92%
rename from .gitlab/issue_templates/SAST Rules release.md
rename to .gitlab/issue_templates/SAST Rules release.md
index bde3ed43790..095d02d4792 100644
--- a/.gitlab/issue_templates/SAST Rules release.md
+++ b/.gitlab/issue_templates/SAST Rules release.md
@@ -6,7 +6,7 @@
/assign @gitlab-org/secure/static-analysis/reaction-rotation
-Note: If the rules release includes support for a new file type, the engineer(s) on reaction rotation will refine and schedule the for an upcoming milestone.
+Note: If the rules release includes support for a new file type, the engineer(s) on reaction rotation will refine and schedule the for an upcoming milestone.
## Actions for ~"group::static analysis" team member
@@ -22,9 +22,9 @@ Release the new Semgrep SAST ruleset version X.Y.Z by going through the sequence
```
1. [ ] If support for a new file type is required:
1. [ ] Add new extension to https://gitlab.com/gitlab-org/security-products/analyzers/semgrep/-/blob/main/plugin/plugin.go.
- 1. [ ] Update [SAST components template](https://gitlab.com/components/sast/-/blob/main/templates/sast.yml) to add support for new file type
- 1. [ ] Update the SAST templates [SAST.latest.gitlab-ci.yml](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Jobs/SAST.latest.gitlab-ci.yml)
+ 1. [ ] Update [SAST components template](https://gitlab.com/components/sast/-/blob/main/templates/sast.yml) to add support for new file type.
+ 1. [ ] Update the SAST templates [SAST.latest.gitlab-ci.yml](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Jobs/SAST.latest.gitlab-ci.yml).
and [SAST.gitlab-ci.yml](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Jobs/SAST.gitlab-ci.yml) to support new file types.
- 1. [ ] Update the [SAST documentation](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/user/application_security/sast/_index.md)
+ 1. [ ] Update the [SAST documentation](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/user/application_security/sast/_index.md).
/label ~"group::static analysis" ~"devops::application security testing" ~"section::sec" ~"Category:SAST" ~"type::feature" ~"workflow::refinement"
diff --git a/.gitlab/lint/unused_helper_methods/potential_methods_to_remove.yml b/.gitlab/lint/unused_helper_methods/potential_methods_to_remove.yml
index 7f86ff4d301..6e882666415 100644
--- a/.gitlab/lint/unused_helper_methods/potential_methods_to_remove.yml
+++ b/.gitlab/lint/unused_helper_methods/potential_methods_to_remove.yml
@@ -97,5 +97,3 @@ dismiss_two_factor_auth_recovery_settings_check;:
file: app/helpers/users/callouts_helper.rb
all_visibility_levels_restricted?:
file: app/helpers/visibility_level_helper.rb
-link_to_wiki_page:
- file: app/helpers/wiki_helper.rb
diff --git a/.rubocop_todo/rspec/named_subject.yml b/.rubocop_todo/rspec/named_subject.yml
index fc27ddbe373..16a6a40eebb 100644
--- a/.rubocop_todo/rspec/named_subject.yml
+++ b/.rubocop_todo/rspec/named_subject.yml
@@ -3128,7 +3128,6 @@ RSpec/NamedSubject:
- 'spec/services/user_agent_detail_service_spec.rb'
- 'spec/services/users/activity_service_spec.rb'
- 'spec/services/users/approve_service_spec.rb'
- - 'spec/services/users/assigned_issues_count_service_spec.rb'
- 'spec/services/users/keys_count_service_spec.rb'
- 'spec/services/users/reject_service_spec.rb'
- 'spec/services/vs_code/settings/create_or_update_service_spec.rb'
diff --git a/.rubocop_todo/style/inline_disable_annotation.yml b/.rubocop_todo/style/inline_disable_annotation.yml
index ec57be69ca4..593a8b78953 100644
--- a/.rubocop_todo/style/inline_disable_annotation.yml
+++ b/.rubocop_todo/style/inline_disable_annotation.yml
@@ -648,7 +648,6 @@ Style/InlineDisableAnnotation:
- 'app/services/todos/destroy/unauthorized_features_service.rb'
- 'app/services/topics/merge_service.rb'
- 'app/services/user_project_access_changed_service.rb'
- - 'app/services/users/assigned_issues_count_service.rb'
- 'app/services/users/batch_status_cleaner_service.rb'
- 'app/services/users/destroy_service.rb'
- 'app/services/users/last_push_event_service.rb'
diff --git a/app/assets/javascripts/gitlab_pages/components/deployments.vue b/app/assets/javascripts/gitlab_pages/components/deployments.vue
index 08406f9560c..20898e1503f 100644
--- a/app/assets/javascripts/gitlab_pages/components/deployments.vue
+++ b/app/assets/javascripts/gitlab_pages/components/deployments.vue
@@ -3,6 +3,7 @@ import { GlToggle, GlAlert } from '@gitlab/ui';
import { s__ } from '~/locale';
import CrudComponent from '~/vue_shared/components/crud_component.vue';
import getProjectPagesDeployments from '../queries/get_project_pages_deployments.graphql';
+import LiveBlock from './live_block.vue';
import PagesDeployment from './deployment.vue';
import LoadMoreDeployments from './load_more_deployments.vue';
@@ -11,6 +12,7 @@ export default {
components: {
CrudComponent,
LoadMoreDeployments,
+ LiveBlock,
PagesDeployment,
GlToggle,
GlAlert,
@@ -57,6 +59,9 @@ export default {
loadedParallelDeploymentsCount() {
return this.parallelDeployments?.nodes.length || 0;
},
+ newestDeployment() {
+ return this.primaryDeployments?.nodes[0] || null;
+ },
},
apollo: {
primaryDeployments: {
@@ -160,6 +165,8 @@ export default {
{{ $options.i18n.loadErrorMessage }}
+
+
+import { GlButton, GlSkeletonLoader } from '@gitlab/ui';
+import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
+import { SHORT_DATE_FORMAT_WITH_TIME } from '~/vue_shared/constants';
+import { s__ } from '~/locale';
+import { joinPaths } from '~/lib/utils/url_utility';
+import CrudComponent from '~/vue_shared/components/crud_component.vue';
+
+export default {
+ name: 'PrimaryDeployment',
+ components: {
+ GlButton,
+ GlSkeletonLoader,
+ TimeAgo,
+ CrudComponent,
+ },
+ i18n: {
+ createdLabel: s__('Pages|Created'),
+ deployJobLabel: s__('Pages|Deploy job'),
+ lastUpdatedLabel: s__('Pages|Last updated'),
+ liveSite: s__('Pages|Your Pages site is live at'),
+ buttonLabel: s__('Pages|Visit site'),
+ },
+ static: {
+ SHORT_DATE_FORMAT_WITH_TIME,
+ },
+ inject: ['projectFullPath', 'primaryDomain'],
+ props: {
+ deployment: {
+ type: Object,
+ required: false,
+ default: () => ({}),
+ },
+ isLoading: {
+ type: Boolean,
+ required: true,
+ },
+ },
+ computed: {
+ domainName() {
+ return this.primaryDomain || this.deployment.url;
+ },
+ ciBuildUrl() {
+ return joinPaths(
+ gon.relative_url_root || '/',
+ this.projectFullPath,
+ '/-/jobs/',
+ `${this.deployment.ciBuildId}`,
+ );
+ },
+ },
+};
+
+
+
+
+
+
+
+
+
+ {{ $options.i18n.liveSite }}
+
+ {{ domainName }}
+
+
+ {{ domainName }}
+
+ ๐
+
+
+
+
+ {{ $options.i18n.deployJobLabel }}
+ {{ deployment.ciBuildId }}
+
+
+ ยท
+ {{ $options.i18n.lastUpdatedLabel }}
+
+
+
+
+
+
+ {{ $options.i18n.buttonLabel }}
+
+
+
+
diff --git a/app/assets/javascripts/gitlab_pages/show.js b/app/assets/javascripts/gitlab_pages/show.js
index a5878de0d07..c45516569e1 100644
--- a/app/assets/javascripts/gitlab_pages/show.js
+++ b/app/assets/javascripts/gitlab_pages/show.js
@@ -27,6 +27,7 @@ export default function initPages() {
apolloProvider,
provide: {
projectFullPath: el.dataset.fullPath,
+ primaryDomain: el.dataset.primaryDomain,
},
render(createElement) {
return createElement(PagesEdit, {});
diff --git a/app/assets/javascripts/glql/components/presenters/issuable.vue b/app/assets/javascripts/glql/components/presenters/issuable.vue
index 9d2fa2b92fe..bd5772552f9 100644
--- a/app/assets/javascripts/glql/components/presenters/issuable.vue
+++ b/app/assets/javascripts/glql/components/presenters/issuable.vue
@@ -44,11 +44,6 @@ export default {
await this.$nextTick();
initIssuablePopovers([this.$refs.reference.$el]);
},
- methods: {
- truncateText(text, length = 50) {
- return text.length > length ? `${text.substring(0, length - 3)}...` : text;
- },
- },
};
@@ -65,7 +60,7 @@ export default {
:data-group-path="group"
>
- {{ truncateText(data.title) }}
+ {{ data.title }}
({{ data.reference }}
- {{ __('closed') }}
- {{ __('merged') }}
diff --git a/app/assets/javascripts/work_items/components/work_item_description.vue b/app/assets/javascripts/work_items/components/work_item_description.vue
index a9de9173ba3..54ea5abb930 100644
--- a/app/assets/javascripts/work_items/components/work_item_description.vue
+++ b/app/assets/javascripts/work_items/components/work_item_description.vue
@@ -266,13 +266,16 @@ export default {
},
mounted() {
const DEFAULT_TEMPLATE_NAME = 'default';
- if (this.isCreateFlow) {
- const templateName = !this.isNewWorkItemRoute
- ? DEFAULT_TEMPLATE_NAME
- : this.$route.query[paramName] ||
- this.$route.query[oldParamNameFromPreWorkItems] ||
- DEFAULT_TEMPLATE_NAME;
+ const templateNameFromRoute =
+ this.$route.query[paramName] || this.$route.query[oldParamNameFromPreWorkItems];
+ const templateName = !this.isNewWorkItemRoute
+ ? DEFAULT_TEMPLATE_NAME
+ : templateNameFromRoute || DEFAULT_TEMPLATE_NAME;
+ // Ensure that template is set during Create Flow only if any of the following is true:;
+ // - Template name is present in URL.
+ // - Description is empty.
+ if (this.isCreateFlow && (templateNameFromRoute || this.descriptionText.trim() === '')) {
this.selectedTemplate = {
name: templateName,
projectId: null,
@@ -301,10 +304,6 @@ export default {
}
if (this.isEditing && this.createFlow) {
this.startEditing();
- // Reset edit state as the description
- // can also be populated from localStorage
- // when creating a new work item.
- this.wasEdited = false;
}
},
error() {
@@ -332,17 +331,6 @@ export default {
if (this.descriptionTemplate === this.descriptionText) {
return;
}
- if (this.createFlow && !this.wasEdited && hasContent && this.appliedTemplate === '') {
- // If the template was fetched on component mount
- // while in create flow, we may also have populated
- // the description from localStorage. In this case,
- // we need avoid showing the warning on first load.
- // while also setting appliedTemplate to the current
- // template such that reset is possible.
- this.appliedTemplate = this.descriptionTemplate;
- this.wasEdited = true;
- return;
- }
if (!isUnchangedTemplate && (isDirty || hasContent)) {
this.showTemplateApplyWarning = true;
@@ -366,9 +354,12 @@ export default {
this.isEditing = true;
this.wasEdited = true;
- this.descriptionText = this.createFlow
- ? this.workItemDescription?.description
- : getDraft(this.autosaveKey) || this.workItemDescription?.description;
+ const draftDescription = getDraft(this.autosaveKey) || '';
+ if (draftDescription.trim() !== '') {
+ this.descriptionText = draftDescription;
+ } else {
+ this.descriptionText = this.workItemDescription?.description;
+ }
this.initialDescriptionText = this.descriptionText;
await this.$nextTick();
diff --git a/app/assets/stylesheets/components/rapid_diffs/_constants.scss b/app/assets/stylesheets/components/rapid_diffs/_constants.scss
index 4e3b7df2d23..74f0de4120a 100644
--- a/app/assets/stylesheets/components/rapid_diffs/_constants.scss
+++ b/app/assets/stylesheets/components/rapid_diffs/_constants.scss
@@ -1,3 +1,4 @@
+@use 'functions';
@import 'page_bundles/mixins_and_variables_and_functions';
$base-font-size: 16px;
@@ -13,3 +14,9 @@ $app-vertical-breakpoint: md;
$diff-file-border-width: 1px;
$diff-file-header-padding-y: $gl-spacing-scale-3;
+
+$diff-file-line-number-z-index: 1;
+$diff-file-hunk-header-z-index: functions.more-than($diff-file-line-number-z-index);
+$diff-file-highlighted-line-number-z-index: functions.more-than($diff-file-hunk-header-z-index);
+
+$diff-file-header-z-index: functions.more-than($diff-file-highlighted-line-number-z-index);
diff --git a/app/assets/stylesheets/components/rapid_diffs/_functions.scss b/app/assets/stylesheets/components/rapid_diffs/_functions.scss
new file mode 100644
index 00000000000..d53b760ea12
--- /dev/null
+++ b/app/assets/stylesheets/components/rapid_diffs/_functions.scss
@@ -0,0 +1,3 @@
+@function more-than($z-index) {
+ @return $z-index + 1;
+}
diff --git a/app/assets/stylesheets/components/rapid_diffs/diff_file_component.scss b/app/assets/stylesheets/components/rapid_diffs/diff_file_component.scss
index 97fc0c2ba56..21c8f2c6ee6 100644
--- a/app/assets/stylesheets/components/rapid_diffs/diff_file_component.scss
+++ b/app/assets/stylesheets/components/rapid_diffs/diff_file_component.scss
@@ -7,19 +7,20 @@
}
.rd-diff-file {
+ $header-padding: constants.$diff-file-header-padding-y * 2;
+ // top/bottom padding + toggle button size, excluding borders
+ $file-header-height: calc($header-padding + $gl-button-small-size);
+ --rd-diff-file-header-height: #{$file-header-height};
--rd-diff-file-border-radius: #{calc($gl-border-radius-base - 1px)};
}
.rd-diff-file[data-virtual]:not([data-collapsed]) {
- $header-padding: constants.$diff-file-header-padding-y * 2;
- // top/bottom padding + toggle button size, excluding borders
- $file-header-height: calc($header-padding + $gl-button-small-size);
// header top/bottom borders + body border
$total-borders: constants.$diff-file-border-width * 3;
content-visibility: var(--rd-content-visibility-auto, auto);
contain-intrinsic-size:
auto 0 auto
- calc(#{$file-header-height} + #{$total-borders} + (#{constants.$code-row-height-target} * var(--total-rows)));
+ calc(var(--rd-diff-file-header-height) + #{$total-borders} + (#{constants.$code-row-height-target} * var(--total-rows)));
}
// content-visibility: auto; applies paint containment, which means you can not draw outside a diff file
@@ -32,7 +33,7 @@
position: sticky;
// 1px offset to hide top border
top: calc(var(--rd-app-sticky-top, 1px) - 1px);
- z-index: 3;
+ z-index: constants.$diff-file-header-z-index;
container-type: scroll-state;
}
diff --git a/app/assets/stylesheets/components/rapid_diffs/text_file_viewers.scss b/app/assets/stylesheets/components/rapid_diffs/text_file_viewers.scss
index 2b0fc2e299e..2cedd45d25e 100644
--- a/app/assets/stylesheets/components/rapid_diffs/text_file_viewers.scss
+++ b/app/assets/stylesheets/components/rapid_diffs/text_file_viewers.scss
@@ -17,13 +17,13 @@
// stylelint-disable-next-line gitlab/no-gl-class
:root.gl-dark .rd-text-view-root {
// disable side borders when light theme is used in dark mode
- --rd-disable-border-colors: var(--code-light-theme);
+ --rd-disable-extended-borders: var(--code-light-theme);
}
// stylelint-disable-next-line gitlab/no-gl-class
:root:not(.gl-dark) .rd-text-view-root {
// disable side borders when light theme is used in dark mode
- --rd-disable-border-colors: var(--code-dark-theme);
+ --rd-disable-extended-borders: var(--code-dark-theme);
}
.rd-text-view-root,
@@ -51,8 +51,7 @@
.rd-hunk-header {
position: relative;
- // overlap line numbers
- z-index: 2;
+ z-index: constants.$diff-file-hunk-header-z-index;
// this is used when a hunk header doesn't have any text, only expand buttons
min-height: calc(1em * $code-line-height);
background-color: var(--code-diff-hunk-header-background-color, $gray-50);
@@ -82,7 +81,7 @@
}
.rd-hunk-header-parallel,
-.rd-hunk-lines-parallel, {
+.rd-hunk-lines-parallel {
grid-template-columns: 50px 1fr 50px 1fr;
}
@@ -129,11 +128,30 @@
padding: 0 10px 0 5px;
text-align: right;
background-color: var(--code-line-nubmer-background-color, $white);
+ scroll-margin-top: var(--rd-diff-file-header-height);
+ z-index: constants.$diff-file-line-number-z-index;
+}
+
+.rd-hunk-lines:has(.rd-line-number:target) {
+ --rd-disable-extended-borders: 1;
+ position: relative;
+ background-color: var(--code-highlighted-line-background-color);
+ border-radius: 0 0 var(--rd-row-bottom-right-radius) var(--rd-row-bottom-left-radius);
+ outline: 1px solid var(--code-highlighted-line-border-color);
+ z-index: constants.$diff-file-highlighted-line-number-z-index;
+}
+
+.rd-hunk-lines:has(.rd-line-number:target) .rd-line-number,
+.rd-hunk-lines:has(.rd-line-number:target) .rd-line-content {
+ // handle empty cells
+ --code-line-nubmer-background-color: var(--code-highlighted-line-background-color);
+ --rd-line-background-color: var(--code-highlighted-line-background-color);
}
// child combinator improves performance of the selector
+// duplicate .rd-hunk-lines-inline selector to override highlighted color
.rd-hunk-lines-parallel:hover > .rd-line-number:not(:empty),
-.rd-hunk-lines-inline:hover > .rd-line-number {
+.rd-hunk-lines-inline.rd-hunk-lines-inline:hover > .rd-line-number {
--rd-line-border-color: var(--code-line-nubmer-hover-border-color, #{$gl-color-purple-200});
--rd-adjacent-line-border-color: var(--rd-line-border-color);
--rd-line-background-color: var(--code-line-nubmer-hover-background-color, #{$gl-color-purple-100});
@@ -197,12 +215,12 @@
}
.rd-line-number:first-child {
- box-shadow: var(--rd-disable-border-colors, -1px 0 var(--rd-line-border-color));
+ box-shadow: var(--rd-disable-extended-borders, -1px 0 var(--rd-line-border-color));
border-bottom-left-radius: var(--rd-row-bottom-left-radius);
}
.rd-line-content:last-child {
- box-shadow: var(--rd-disable-border-colors, 1px 0 var(--rd-line-border-color));
+ box-shadow: var(--rd-disable-extended-borders, 1px 0 var(--rd-line-border-color));
border-bottom-right-radius: var(--rd-row-bottom-right-radius);
}
diff --git a/app/assets/stylesheets/highlight/_white_base.scss b/app/assets/stylesheets/highlight/_white_base.scss
index 0b01a539777..63c7db15ea7 100644
--- a/app/assets/stylesheets/highlight/_white_base.scss
+++ b/app/assets/stylesheets/highlight/_white_base.scss
@@ -92,6 +92,9 @@ $white-gc-bg: #eaf2f5;
--code-meta-diff-background-color: #{$gl-color-neutral-10};
--code-meta-diff-color: #{$gl-color-alpha-dark-24};
+ --code-highlighted-line-background-color: #{$gl-color-blue-50};
+ --code-highlighted-line-border-color: #{$gl-color-blue-300};
+
// Line numbers
.file-line-blame {
@include line-link($gl-color-neutral-1000, 'git');
diff --git a/app/assets/stylesheets/highlight/themes/dark.scss b/app/assets/stylesheets/highlight/themes/dark.scss
index ed722cba58e..c8a3785e806 100644
--- a/app/assets/stylesheets/highlight/themes/dark.scss
+++ b/app/assets/stylesheets/highlight/themes/dark.scss
@@ -165,6 +165,9 @@ $dark-il: #de935f;
--code-diff-expand-button-hover-background-color: #{$gl-color-neutral-300};
--code-diff-expand-button-hover-color: #{$gl-color-neutral-0};
+ --code-highlighted-line-background-color: #{$gl-color-orange-800};
+ --code-highlighted-line-border-color: #{$gl-color-orange-500};
+
// Highlight.js theme overrides (https://gitlab.com/gitlab-org/gitlab/-/issues/365167)
// We should be able to remove the overrides once the upstream issue is fixed (https://github.com/sourcegraph/sourcegraph/issues/23251)
@include hljs-override('title\\.class', $dark-nc);
diff --git a/app/assets/stylesheets/highlight/themes/monokai.scss b/app/assets/stylesheets/highlight/themes/monokai.scss
index 593d536aaa0..31d8d0c3c1c 100644
--- a/app/assets/stylesheets/highlight/themes/monokai.scss
+++ b/app/assets/stylesheets/highlight/themes/monokai.scss
@@ -138,6 +138,9 @@ $monokai-gh: #75715e;
--code-diff-expand-button-hover-background-color: #{$gl-color-neutral-300};
--code-diff-expand-button-hover-color: #{$gl-color-neutral-0};
+ --code-highlighted-line-background-color: #{$gl-color-orange-800};
+ --code-highlighted-line-border-color: #{$gl-color-orange-500};
+
// Highlight.js theme overrides (https://gitlab.com/gitlab-org/gitlab/-/issues/365167)
// We should be able to remove the overrides once the upstream issue is fixed (https://github.com/sourcegraph/sourcegraph/issues/23251)
@include hljs-override('string', $monokai-s);
diff --git a/app/assets/stylesheets/highlight/themes/none.scss b/app/assets/stylesheets/highlight/themes/none.scss
index 4de453afe7d..c1e5fb9950c 100644
--- a/app/assets/stylesheets/highlight/themes/none.scss
+++ b/app/assets/stylesheets/highlight/themes/none.scss
@@ -37,6 +37,9 @@ $none-code-mark: #d3e3f4;
--code-new-diff-background-color: #{$gl-color-neutral-50};
--code-new-inline-diff-background-color: #{rgba(0,0,0,0.1)};
+ --code-highlighted-line-background-color: #{$gl-color-blue-50};
+ --code-highlighted-line-border-color: #{$gl-color-blue-300};
+
// Highlight.js theme overrides (https://gitlab.com/gitlab-org/gitlab/-/issues/365167)
// We should be able to remove the overrides once the upstream issue is fixed (https://github.com/sourcegraph/sourcegraph/issues/23251)
&.blob-viewer {
diff --git a/app/assets/stylesheets/highlight/themes/solarized-dark.scss b/app/assets/stylesheets/highlight/themes/solarized-dark.scss
index 8e09cfd0ba2..a91570e2a15 100644
--- a/app/assets/stylesheets/highlight/themes/solarized-dark.scss
+++ b/app/assets/stylesheets/highlight/themes/solarized-dark.scss
@@ -141,6 +141,9 @@ $solarized-dark-il: #2aa198;
--code-diff-expand-button-hover-background-color: #{lighten($solarized-dark-pre-bg, 20%)};
--code-diff-expand-button-hover-color: #{$gl-color-neutral-0};
+ --code-highlighted-line-background-color: #{$gl-color-orange-800};
+ --code-highlighted-line-border-color: #{$gl-color-orange-500};
+
// Highlight.js theme overrides (https://gitlab.com/gitlab-org/gitlab/-/issues/365167)
// We should be able to remove the overrides once the upstream issue is fixed (https://github.com/sourcegraph/sourcegraph/issues/23251)
@include hljs-override('string', $solarized-dark-s);
diff --git a/app/assets/stylesheets/highlight/themes/solarized-light.scss b/app/assets/stylesheets/highlight/themes/solarized-light.scss
index 030859d6e25..a4c58d9878d 100644
--- a/app/assets/stylesheets/highlight/themes/solarized-light.scss
+++ b/app/assets/stylesheets/highlight/themes/solarized-light.scss
@@ -139,6 +139,9 @@ $solarized-light-il: #2aa198;
--code-diff-hunk-header-background-color: #{$solarized-light-matchline-bg};
--code-diff-hunk-header-color: #{$gl-color-alpha-dark-24};
+ --code-highlighted-line-background-color: #{$solarized-light-hll-bg};
+ --code-highlighted-line-border-color: #{darken($solarized-light-hll-bg, 15%)};
+
// Highlight.js theme overrides (https://gitlab.com/gitlab-org/gitlab/-/issues/365167)
// We should be able to remove the overrides once the upstream issue is fixed (https://github.com/sourcegraph/sourcegraph/issues/23251)
@include hljs-override('keyword', $solarized-light-k);
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index ea7ea845647..c87991e9060 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -135,20 +135,6 @@ module IssuablesHelper
html.html_safe
end
- def assigned_issuables_count(issuable_type)
- case issuable_type
- when :issues
- ::Users::AssignedIssuesCountService.new(
- current_user: current_user,
- max_limit: User::MAX_LIMIT_FOR_ASSIGNEED_ISSUES_COUNT
- ).count
- when :merge_requests
- current_user.assigned_open_merge_requests_count
- else
- raise ArgumentError, "invalid issuable `#{issuable_type}`"
- end
- end
-
def issuable_reference(issuable)
@show_full_reference ? issuable.to_reference(full: true) : issuable.to_reference(@group || @project)
end
diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb
index 55dda776001..5a7eb8c7c81 100644
--- a/app/helpers/merge_requests_helper.rb
+++ b/app/helpers/merge_requests_helper.rb
@@ -151,20 +151,6 @@ module MergeRequestsHelper
end
end
- def user_merge_requests_counts
- @user_merge_requests_counts ||= begin
- assigned_count = assigned_issuables_count(:merge_requests)
- review_requested_count = review_requested_merge_requests_count
- total_count = assigned_count + review_requested_count
-
- {
- assigned: assigned_count,
- review_requested: review_requested_count,
- total: total_count
- }
- end
- end
-
def reviewers_label(merge_request, include_value: true)
reviewers = merge_request.reviewers
@@ -360,10 +346,6 @@ module MergeRequestsHelper
private
- def review_requested_merge_requests_count
- current_user.review_requested_open_merge_requests_count
- end
-
def default_suggestion_commit_message(project)
project.suggestion_commit_message.presence || Gitlab::Suggestions::CommitMessage::DEFAULT_SUGGESTION_COMMIT_MESSAGE
end
diff --git a/app/helpers/wiki_helper.rb b/app/helpers/wiki_helper.rb
index 72bb2af7aea..6f32f9e9613 100644
--- a/app/helpers/wiki_helper.rb
+++ b/app/helpers/wiki_helper.rb
@@ -17,10 +17,6 @@ module WikiHelper
add_to_breadcrumbs(_('Wiki'), wiki_path(page.wiki))
end
- def link_to_wiki_page(page, **options)
- link_to page.human_title, wiki_page_path(page.wiki, page), **options
- end
-
def wiki_sidebar_toggle_button
render Pajamas::ButtonComponent.new(
icon: 'chevron-double-lg-left',
diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb
index 7e8e8c18627..6d00c3dbb07 100644
--- a/app/models/diff_note.rb
+++ b/app/models/diff_note.rb
@@ -8,9 +8,6 @@ class DiffNote < Note
include DiffPositionableNote
include Gitlab::Utils::StrongMemoize
- # Duplicate ignore_column from Note to make the db:check-migrations job pass
- ignore_column :attachment, remove_with: '18.1', remove_after: '2025-05-15'
-
self.allow_legacy_sti_class = true
def self.noteable_types
diff --git a/app/models/note.rb b/app/models/note.rb
index f0aa2a5a60e..186b20f3441 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -29,8 +29,6 @@ class Note < ApplicationRecord
include EachBatch
include Spammable
- ignore_column :attachment, remove_with: '18.1', remove_after: '2025-05-15'
-
cache_markdown_field :note, pipeline: :note, issuable_reference_expansion_enabled: true
redact_field :note
diff --git a/app/models/user.rb b/app/models/user.rb
index 3bf1974acfa..0923100dc3c 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -49,8 +49,6 @@ class User < ApplicationRecord
MAX_USERNAME_LENGTH = 255
MIN_USERNAME_LENGTH = 2
- MAX_LIMIT_FOR_ASSIGNEED_ISSUES_COUNT = 100
-
SECONDARY_EMAIL_ATTRIBUTES = [
:commit_email,
:notification_email,
diff --git a/app/models/users/callout.rb b/app/models/users/callout.rb
index 4a66a7d7325..93e1bd3fdb5 100644
--- a/app/models/users/callout.rb
+++ b/app/models/users/callout.rb
@@ -6,6 +6,7 @@ module Users
self.table_name = 'user_callouts'
+ # NOTE: to avoid false-positive dismissals, use new consecutive enum values for new callout IDs
enum :feature_name, {
gke_cluster_integration: 1,
gcp_signup_offer: 2,
diff --git a/app/models/users/group_callout.rb b/app/models/users/group_callout.rb
index c3f1ce3a420..5e11fb78ff6 100644
--- a/app/models/users/group_callout.rb
+++ b/app/models/users/group_callout.rb
@@ -8,6 +8,7 @@ module Users
belongs_to :group
+ # NOTE: to avoid false-positive dismissals, use new consecutive enum values for new callout IDs
enum :feature_name, {
invite_members_banner: 1,
approaching_seat_count_threshold: 2, # EE-only
diff --git a/app/models/users/project_callout.rb b/app/models/users/project_callout.rb
index 1d840de3aa0..94a8208b241 100644
--- a/app/models/users/project_callout.rb
+++ b/app/models/users/project_callout.rb
@@ -8,13 +8,14 @@ module Users
belongs_to :project
+ # NOTE: to avoid false-positive dismissals, use new consecutive enum values for new callout IDs
enum :feature_name, {
awaiting_members_banner: 1, # EE-only
web_hook_disabled: 2,
# 3 was removed https://gitlab.com/gitlab-org/gitlab/-/merge_requests/129703,
- # and cleaned up https://gitlab.com/gitlab-org/gitlab/-/merge_requests/129924, it can be replaced
+ # and cleaned up https://gitlab.com/gitlab-org/gitlab/-/merge_requests/129924
namespace_storage_pre_enforcement_banner: 4, # EE-only
- # 5,6,7 were removed https://gitlab.com/gitlab-org/gitlab/-/merge_requests/118330, they can be replaced
+ # 5,6,7 were removed https://gitlab.com/gitlab-org/gitlab/-/merge_requests/118330
license_check_deprecation_alert: 8 # EE-only
}
diff --git a/app/services/users/assigned_issues_count_service.rb b/app/services/users/assigned_issues_count_service.rb
deleted file mode 100644
index 6590902587d..00000000000
--- a/app/services/users/assigned_issues_count_service.rb
+++ /dev/null
@@ -1,63 +0,0 @@
-# frozen_string_literal: true
-
-module Users
- class AssignedIssuesCountService < ::BaseCountService
- def initialize(current_user:, max_limit: User::MAX_LIMIT_FOR_ASSIGNEED_ISSUES_COUNT)
- @current_user = current_user
- @max_limit = max_limit
- end
-
- def cache_key
- ['users', @current_user.id, 'max_assigned_open_issues_count']
- end
-
- def cache_options
- { force: false, expires_in: User::COUNT_CACHE_VALIDITY_PERIOD }
- end
-
- # rubocop: disable CodeReuse/ActiveRecord
- def uncached_count
- # When a user has many assigned issues, counting them all can be very slow.
- # As a workaround, we will short-circuit the counting query once the count reaches some threshold.
- #
- # Concretely, given a threshold, say 100 (= max_limit),
- # iterate through the first 100 issues, sorted by ID desc, assigned to the user using `issue_assignees` table.
- # For each issue iterated, use IssuesFinder to check if the issue should be counted.
- initializer = IssueAssignee
- .select(:issue_id).joins(", LATERAL (#{finder_constraint.to_sql}) as issues")
- .where(user_id: @current_user.id)
- .order(issue_id: :desc)
- .limit(1)
- recursive_finder = initializer.where("issue_assignees.issue_id < assigned_issues.issue_id")
-
- cte = <<~SQL
- WITH RECURSIVE assigned_issues AS (
- (
- #{initializer.to_sql}
- )
- UNION ALL
- (
- SELECT next_assigned_issue.issue_id
- FROM assigned_issues,
- LATERAL (
- #{recursive_finder.to_sql}
- ) next_assigned_issue
- )
- ) SELECT COUNT(*) FROM (SELECT * FROM assigned_issues LIMIT #{@max_limit}) issues
- SQL
-
- ApplicationRecord.connection.execute(cte).first["count"]
- end
- # rubocop: enable CodeReuse/ActiveRecord
-
- private
-
- # rubocop: disable CodeReuse/ActiveRecord
- def finder_constraint
- IssuesFinder.new(@current_user, assignee_id: @current_user.id, state: 'opened', non_archived: true)
- .execute
- .where("issues.id=issue_assignees.issue_id").limit(1)
- end
- # rubocop: enable CodeReuse/ActiveRecord
- end
-end
diff --git a/app/views/projects/pages/show.html.haml b/app/views/projects/pages/show.html.haml
index 9750edef259..599fc102975 100644
--- a/app/views/projects/pages/show.html.haml
+++ b/app/views/projects/pages/show.html.haml
@@ -13,7 +13,10 @@
.tab-content.gl-tab-content.gl-pt-5
.tab-pane.active#overview
- #js-pages{ data: { full_path: @project.full_path } }
+ #js-pages{ data: {
+ full_path: @project.full_path,
+ primary_domain: @project.pages_domains.present? ? @project.project_setting.pages_primary_domain : ''
+ } }
.tab-pane#domains-settings
.gl-flex.gl-flex-col.gl-gap-5
diff --git a/app/workers/database/lock_tables_worker.rb b/app/workers/database/lock_tables_worker.rb
index 253c1b2bb6a..b64925d2409 100644
--- a/app/workers/database/lock_tables_worker.rb
+++ b/app/workers/database/lock_tables_worker.rb
@@ -59,7 +59,8 @@ module Database
database_name: database_name,
with_retries: !connection.transaction_open?,
logger: nil,
- dry_run: false
+ dry_run: false,
+ force: true
)
end
end
diff --git a/app/workers/database/monitor_locked_tables_worker.rb b/app/workers/database/monitor_locked_tables_worker.rb
index 79cf2c20d38..d620451f170 100644
--- a/app/workers/database/monitor_locked_tables_worker.rb
+++ b/app/workers/database/monitor_locked_tables_worker.rb
@@ -23,7 +23,8 @@ module Database
return unless Gitlab::Database.database_mode == Gitlab::Database::MODE_MULTIPLE_DATABASES
return if Feature.disabled?(:monitor_database_locked_tables, type: :ops)
- lock_writes_results = ::Gitlab::Database::TablesLocker.new(dry_run: true, include_partitions: false).lock_writes
+ lock_writes_results = ::Gitlab::Database::TablesLocker.new(
+ dry_run: true, include_partitions: false).lock_writes
tables_lock_info_per_db = ::Gitlab::Database.database_base_models_with_gitlab_shared.keys.to_h do |db_name, _|
[db_name, INITIAL_DATABASE_RESULT.deep_dup]
diff --git a/doc/administration/auditor_users.md b/doc/administration/auditor_users.md
index 473458dae8d..4fda8428c5b 100644
--- a/doc/administration/auditor_users.md
+++ b/doc/administration/auditor_users.md
@@ -62,7 +62,7 @@ users with auditor access have the same [permissions](../user/permissions.md) as
If you are signed in with auditor access, you:
- Have full access to the projects and groups you own.
-- Have read-only access to the projects and groups you are not a member of.
+- Have read-only access to the projects and groups you are not a member of. Because of a [known issue](https://gitlab.com/gitlab-org/gitlab/-/issues/542815) this is not supported. Users must have at least the Reporter role for read-only tasks.
- Have [permissions](../user/permissions.md) based on your role to projects and groups you are a member of. For example, if you have the Developer role,
you can push commits or comment on issues.
- Can access the same resources using the GitLab UI or API.
diff --git a/doc/administration/troubleshooting/diagnostics_tools.md b/doc/administration/troubleshooting/diagnostics_tools.md
index f0b379c57dc..ce79b8ba25f 100644
--- a/doc/administration/troubleshooting/diagnostics_tools.md
+++ b/doc/administration/troubleshooting/diagnostics_tools.md
@@ -26,7 +26,7 @@ running on.
## strace-parser
-[strace-parser](https://gitlab.com/wchandler/strace-parser) is a small tool to analyze
+[strace-parser](https://gitlab.com/gitlab-com/support/toolbox/strace-parser) is a small tool to analyze
and summarize raw `strace` data.
## `kubesos`
diff --git a/doc/development/callouts.md b/doc/development/callouts.md
new file mode 100644
index 00000000000..824fefee4db
--- /dev/null
+++ b/doc/development/callouts.md
@@ -0,0 +1,163 @@
+---
+stage: none
+group: unassigned
+info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/ee/development/development_processes.html#development-guidelines-review.
+title: Callouts
+---
+
+Callouts are a mechanism for presenting notifications to users. Users can dismiss the notifications, and the notifications can stay dismissed for a predefined duration. Notification dismissal is persistent across page loads and different user devices.
+
+## Callout contexts
+
+**Global context:** Callouts can be displayed to a user regardless of where they are in the application. For example, we can show a notification that reminds the user to have two-factor authentication recovery codes stored in a safe place. Dismissing this type of callout is effective for the particular user across the whole GitLab instance, no matter where they encountered the callout.
+
+**Group and project contexts:** Callouts can also be displayed to a specific user and have a particular context binding, like a group or a project context. For example, group owners can be notified that their group is running out of available seats. Dismissing that callout would be effective for the particular user only in this particular group, while they would still see the same callout in other groups, if applicable.
+
+Regardless of the context, dismissing a callout is only effective for the given user. Other users still see their relevant callouts.
+
+## Callout IDs
+
+Callouts use unique names to identify them, and a unique value to store dismissals data. For example:
+
+```ruby
+amazing_alert: 42,
+```
+
+Here `amazing_alert` is the callout ID, and `42` is a unique number to be used to register dismissals in the database. Here's how a group callout would be saved:
+
+```plaintext
+ id | user_id | group_id | feature_name | dismissed_at
+----+---------+----------+--------------+-------------------------------
+ 0 | 1 | 4 | 42 | 2025-05-21 00:00:00.000000+00
+```
+
+To create a new callout ID, add a new key to the `feature_name` enum in the relevant context type registry file, using a unique name and a sequential value:
+
+- Global context: `app/models/users/callout.rb`. Callouts are dismissed by a user globally. Related notifications would not be displayed anywhere in the GitLab instance for that user.
+
+- Group context: `app/models/users/group_callout.rb`. Callouts are dismissed by a user in a given group. Related notifications are still shown to the user in other groups.
+
+- Project context: `app/models/users/project_callout.rb`. Callouts dismissed by a user in a given project. Related notifications are still shown to the user in other projects.
+
+**NOTE:** do not reuse old enum values, as it may lead to false-positive dismissals. Instead, create a new sequential number.
+
+### Deprecating a callout
+
+When we no longer need a callout, we can remove it from the callout ID enums. But since dismissal records in the DB use the numerical value of the enum, we need to explicitly preserve the deprecated ID from being reused, so that old dismissals don't affect the new callouts. Thus to remove a callout ID:
+
+1. Remove the key/value pair from the enum hash
+1. Leave an inline comment, mentioning the deprecated ID and the MR removing the callout
+
+For example:
+
+```diff
+- amazing_alert: 42,
++ # 42 removed in https://gitlab.com/gitlab-org/gitlab/-/merge_requests/121920
+```
+
+## Server-side rendered callouts
+
+This section describes using callouts when they are rendered on the server in `.haml` views, partials, or components.
+
+### Dismissing the callouts on the client side
+
+JavaScript helpers for callouts rely on certain selectors and data attributes to be present on the HTML of the notification, to properly call dismissal API endpoints, and hide the notification in the runtime. The wrapper of the notification needs to have a `.js-persistent-callout` CSS class with the following data-attributes:
+
+```javascript
+{
+ featureId, // Unique callout ID
+ dismissEndpoint, // Dismiss endpoint, unique for each callout context type
+ groupId, // optional, required for the group context
+ projectId, // optional, required for the project context
+ deferLinks, // optional, allows executing certain action alongside the dismissal
+}
+```
+
+For the dismissal trigger, the wrapper needs to contain at least one `.js-close` element and optionally `.deferred-link` links (if `deferLinks` is `true`). See `app/assets/javascripts/persistent_user_callout.js` for more details.
+
+#### Defining the dismissal endpoint
+
+For the JS to properly register the dismissal โ apart from the `featureId`, we need to provide the `dismissEndpoint` URL, different for each context. Here are path helpers to use for each context:
+
+- Global context: `callouts_path`
+
+- Group context: `group_callouts_path`
+
+- Project context: `project_callouts_path`
+
+### Detecting the dismissal on the server side
+
+Usually before rendering the callout, we check if it has been dismissed. `User` model on the Backend has helpers to detect dismissals in different contexts:
+
+- Global context: `user.dismissed_callout?(feature_name:, ignore_dismissal_earlier_than: nil)`
+
+- Group context: `user.dismissed_callout_for_group?(feature_name:, group:, ignore_dismissal_earlier_than: nil)`
+
+- Project context: `user.dismissed_callout_for_project?(feature_name:, project:, ignore_dismissal_earlier_than: nil)`
+
+**NOTE:** `feature_name` is the Callout ID, described above. In our example, it would be `amazing_alert`
+
+#### Setting expiration for dismissals using `ignore_dismissal_earlier_than` parameter
+
+Some callouts can be displayed once and after the dismissal should never appear again. Others need to pop-up repeatedly, even if dismissed.
+
+Without the `ignore_dismissal_earlier_than` parameter callout dismissals will stay effective indefinitely. Once the user has dismissed the callout, it would stay dismissed.
+
+If we pass `ignore_dismissal_earlier_than` a value, for example, `30.days.ago`, the dismissed callout would re-appear after this duration.
+
+**NOTE:** expired or deprecated dismissals are not automatically removed from the database. This parameter only checks if the callout has been dismissed within the defined period.
+
+### Example usage
+
+Here's an example `.haml` file:
+
+```haml
+- return if amazing_alert_callout_dismissed?(group)
+
+= render Pajamas::AlertComponent.new(title: s_('AmazingAlert|Amazing title'),
+ variant: :warning,
+ alert_options: { class: 'js-persistent-callout', data: amazing_alert_callout_data(group) }) do |c|
+ - c.with_body do
+ = s_('AmazingAlert|This is an amazing alert body.')
+```
+
+With a corresponding `.rb` helper:
+
+```ruby
+# frozen_string_literal: true
+
+module AmazingAlertHelper
+ def amazing_alert_callout_dismissed?(group)
+ user_dismissed_for_group("amazing_alert", group.root_ancestor, 30.days.ago)
+ end
+
+ def amazing_alert_callout_data(group)
+ {
+ feature_id: "amazing_alert",
+ dismiss_endpoint: group_callouts_path,
+ group_id: group.root_ancestor.id
+ }
+ end
+end
+```
+
+## Client-side rendered callouts
+
+This section describes using callouts when they are rendered on the client in `.vue` components.
+
+### Dismissing the callouts on the client side
+
+For Vue components, we have a `` wrapper, that integrates with GraphQL API to simplify dismissing and checking the dismissed state of a callout. Here's an example usage:
+
+```vue
+
+
+
+
+
+```
+
+See `app/assets/javascripts/vue_shared/components/user_callout_dismisser.vue` for more details.
diff --git a/doc/update/versions/gitlab_17_changes.md b/doc/update/versions/gitlab_17_changes.md
index d3fa24f18aa..61e384b7621 100644
--- a/doc/update/versions/gitlab_17_changes.md
+++ b/doc/update/versions/gitlab_17_changes.md
@@ -362,6 +362,9 @@ For more information, see [issue 480328](https://gitlab.com/gitlab-org/gitlab/-/
## 17.11.0
+- To avoid a [known issue](https://gitlab.com/gitlab-org/gitlab/-/issues/537325) that causes a database migration to run for a long time and use excessive CPU,
+ update to GitLab 17.11.3 or later.
+
### New encryption secrets
In GitLab 17.8, three new secrets have been added to support the new encryption framework (started to be used in 17.9):
diff --git a/doc/user/application_security/sast/_index.md b/doc/user/application_security/sast/_index.md
index c33a52db8e9..d0ab3ddcf4c 100644
--- a/doc/user/application_security/sast/_index.md
+++ b/doc/user/application_security/sast/_index.md
@@ -62,8 +62,8 @@ your workflow.
The following table lists the GitLab tiers in which each feature is available.
-| Feature | In Free & Premium | In Ultimate |
-|:-----------------------------------------------------------------------------------------|:-----------------------|:-----------------------|
+| Feature | In Free & Premium | In Ultimate |
+|:-----------------------------------------------------------------------------------------|:-------------------------------------|:------------|
| Basic scanning with [open-source analyzers](#supported-languages-and-frameworks) | {{< icon name="check-circle" >}} Yes | {{< icon name="check-circle" >}} Yes |
| Downloadable [SAST JSON report](#download-a-sast-report) | {{< icon name="check-circle" >}} Yes | {{< icon name="check-circle" >}} Yes |
| Cross-file, cross-function scanning with [GitLab Advanced SAST](gitlab_advanced_sast.md) | {{< icon name="dotted-circle" >}} No | {{< icon name="check-circle" >}} Yes |
@@ -96,27 +96,27 @@ The available scanning options depend on the GitLab tier:
For more information about our plans for language support in SAST, see the [category direction page](https://about.gitlab.com/direction/application_security_testing/static-analysis/sast/#language-support).
-| Language | Supported by [GitLab Advanced SAST](gitlab_advanced_sast.md) (Ultimate only) | Supported by another [analyzer](analyzers.md) (all tiers) |
-|-----------------------------------------|-----------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| Apex (Salesforce) | {{< icon name="dotted-circle" >}} No | {{< icon name="check-circle" >}} Yes: [PMD-Apex](https://gitlab.com/gitlab-org/security-products/analyzers/pmd-apex) |
-| C | {{< icon name="dotted-circle" >}} No; tracked in [epic 14271](https://gitlab.com/groups/gitlab-org/-/epics/14271) | {{< icon name="check-circle" >}} Yes: [Semgrep](https://gitlab.com/gitlab-org/security-products/analyzers/semgrep) with [GitLab-managed rules](rules.md#semgrep-based-analyzer) |
-| C++ | {{< icon name="dotted-circle" >}} No; tracked in [epic 14271](https://gitlab.com/groups/gitlab-org/-/epics/14271) | {{< icon name="check-circle" >}} Yes: [Semgrep](https://gitlab.com/gitlab-org/security-products/analyzers/semgrep) with [GitLab-managed rules](rules.md#semgrep-based-analyzer) |
-| C# | {{< icon name="check-circle" >}} Yes | {{< icon name="check-circle" >}} Yes: [Semgrep](https://gitlab.com/gitlab-org/security-products/analyzers/semgrep) with [GitLab-managed rules](rules.md#semgrep-based-analyzer) |
-| Elixir (Phoenix) | {{< icon name="dotted-circle" >}} No | {{< icon name="check-circle" >}} Yes: [Sobelow](https://gitlab.com/gitlab-org/security-products/analyzers/sobelow) |
-| Go | {{< icon name="check-circle" >}} Yes | {{< icon name="check-circle" >}} Yes: [Semgrep](https://gitlab.com/gitlab-org/security-products/analyzers/semgrep) with [GitLab-managed rules](rules.md#semgrep-based-analyzer) |
-| Groovy | {{< icon name="dotted-circle" >}} No | {{< icon name="check-circle" >}} Yes: [SpotBugs](https://gitlab.com/gitlab-org/security-products/analyzers/spotbugs) with the find-sec-bugs plugin1 |
-| Java | {{< icon name="check-circle" >}} Yes, including Java Server Pages (JSP) | {{< icon name="check-circle" >}} Yes: [Semgrep](https://gitlab.com/gitlab-org/security-products/analyzers/semgrep) with [GitLab-managed rules](rules.md#semgrep-based-analyzer) (including Android) |
-| JavaScript, including Node.js and React | {{< icon name="check-circle" >}} Yes | {{< icon name="check-circle" >}} Yes: [Semgrep](https://gitlab.com/gitlab-org/security-products/analyzers/semgrep) with [GitLab-managed rules](rules.md#semgrep-based-analyzer) |
-| Kotlin | {{< icon name="dotted-circle" >}} No; tracked in [epic 15173](https://gitlab.com/groups/gitlab-org/-/epics/15173) | {{< icon name="check-circle" >}} Yes: [Semgrep](https://gitlab.com/gitlab-org/security-products/analyzers/semgrep) with [GitLab-managed rules](rules.md#semgrep-based-analyzer) (including Android) |
-| Objective-C (iOS) | {{< icon name="dotted-circle" >}} No | {{< icon name="check-circle" >}} Yes: [Semgrep](https://gitlab.com/gitlab-org/security-products/analyzers/semgrep) with [GitLab-managed rules](rules.md#semgrep-based-analyzer) |
-| PHP | {{< icon name="dotted-circle" >}} No; tracked in [epic 14273](https://gitlab.com/groups/gitlab-org/-/epics/14273) | {{< icon name="check-circle" >}} Yes: [Semgrep](https://gitlab.com/gitlab-org/security-products/analyzers/semgrep) with [GitLab-managed rules](rules.md#semgrep-based-analyzer) |
-| Python | {{< icon name="check-circle" >}} Yes | {{< icon name="check-circle" >}} Yes: [Semgrep](https://gitlab.com/gitlab-org/security-products/analyzers/semgrep) with [GitLab-managed rules](rules.md#semgrep-based-analyzer) |
-| Ruby, including Ruby on Rails | {{< icon name="check-circle" >}} Yes | {{< icon name="check-circle" >}} Yes: [Semgrep](https://gitlab.com/gitlab-org/security-products/analyzers/semgrep) with [GitLab-managed rules](rules.md#semgrep-based-analyzer) |
-| Scala | {{< icon name="dotted-circle" >}} No; tracked in [epic 15174](https://gitlab.com/groups/gitlab-org/-/epics/15174) | {{< icon name="check-circle" >}} Yes: [Semgrep](https://gitlab.com/gitlab-org/security-products/analyzers/semgrep) with [GitLab-managed rules](rules.md#semgrep-based-analyzer) |
-| Swift (iOS) | {{< icon name="dotted-circle" >}} No | {{< icon name="check-circle" >}} Yes: [Semgrep](https://gitlab.com/gitlab-org/security-products/analyzers/semgrep) with [GitLab-managed rules](rules.md#semgrep-based-analyzer) |
-| TypeScript | {{< icon name="check-circle" >}} Yes | {{< icon name="check-circle" >}} Yes: [Semgrep](https://gitlab.com/gitlab-org/security-products/analyzers/semgrep) with [GitLab-managed rules](rules.md#semgrep-based-analyzer) |
-| YAML2 | {{< icon name="check-circle" >}} Yes | {{< icon name="check-circle" >}} Yes: [Semgrep](https://gitlab.com/gitlab-org/security-products/analyzers/semgrep) with [GitLab-managed rules](rules.md#semgrep-based-analyzer) |
-| Java Properties | {{< icon name="check-circle" >}} Yes | {{< icon name="check-circle" >}} Yes: [Semgrep](https://gitlab.com/gitlab-org/security-products/analyzers/semgrep) with [GitLab-managed rules](rules.md#semgrep-based-analyzer) |
+| Language | Supported by [GitLab Advanced SAST](gitlab_advanced_sast.md) (Ultimate only) | Supported by another [analyzer](analyzers.md) (all tiers) |
+|------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------|
+| Apex (Salesforce) | {{< icon name="dotted-circle" >}} No | {{< icon name="check-circle" >}} Yes: [PMD-Apex](https://gitlab.com/gitlab-org/security-products/analyzers/pmd-apex) |
+| C | {{< icon name="dotted-circle" >}} No; tracked in [epic 14271](https://gitlab.com/groups/gitlab-org/-/epics/14271) | {{< icon name="check-circle" >}} Yes: [Semgrep](https://gitlab.com/gitlab-org/security-products/analyzers/semgrep) with [GitLab-managed rules](rules.md#semgrep-based-analyzer) |
+| C++ | {{< icon name="dotted-circle" >}} No; tracked in [epic 14271](https://gitlab.com/groups/gitlab-org/-/epics/14271) | {{< icon name="check-circle" >}} Yes: [Semgrep](https://gitlab.com/gitlab-org/security-products/analyzers/semgrep) with [GitLab-managed rules](rules.md#semgrep-based-analyzer) |
+| C# | {{< icon name="check-circle" >}} Yes | {{< icon name="check-circle" >}} Yes: [Semgrep](https://gitlab.com/gitlab-org/security-products/analyzers/semgrep) with [GitLab-managed rules](rules.md#semgrep-based-analyzer) |
+| Elixir (Phoenix) | {{< icon name="dotted-circle" >}} No | {{< icon name="check-circle" >}} Yes: [Sobelow](https://gitlab.com/gitlab-org/security-products/analyzers/sobelow) |
+| Go | {{< icon name="check-circle" >}} Yes | {{< icon name="check-circle" >}} Yes: [Semgrep](https://gitlab.com/gitlab-org/security-products/analyzers/semgrep) with [GitLab-managed rules](rules.md#semgrep-based-analyzer) |
+| Groovy | {{< icon name="dotted-circle" >}} No | {{< icon name="check-circle" >}} Yes: [SpotBugs](https://gitlab.com/gitlab-org/security-products/analyzers/spotbugs) with the find-sec-bugs plugin1 |
+| Java | {{< icon name="check-circle" >}} Yes, including Java Server Pages (JSP) | {{< icon name="check-circle" >}} Yes: [Semgrep](https://gitlab.com/gitlab-org/security-products/analyzers/semgrep) with [GitLab-managed rules](rules.md#semgrep-based-analyzer) (including Android) |
+| JavaScript, including Node.js and React | {{< icon name="check-circle" >}} Yes | {{< icon name="check-circle" >}} Yes: [Semgrep](https://gitlab.com/gitlab-org/security-products/analyzers/semgrep) with [GitLab-managed rules](rules.md#semgrep-based-analyzer) |
+| Kotlin | {{< icon name="dotted-circle" >}} No; tracked in [epic 15173](https://gitlab.com/groups/gitlab-org/-/epics/15173) | {{< icon name="check-circle" >}} Yes: [Semgrep](https://gitlab.com/gitlab-org/security-products/analyzers/semgrep) with [GitLab-managed rules](rules.md#semgrep-based-analyzer) (including Android) |
+| Objective-C (iOS) | {{< icon name="dotted-circle" >}} No | {{< icon name="check-circle" >}} Yes: [Semgrep](https://gitlab.com/gitlab-org/security-products/analyzers/semgrep) with [GitLab-managed rules](rules.md#semgrep-based-analyzer) |
+| PHP | {{< icon name="dotted-circle" >}} No; tracked in [epic 14273](https://gitlab.com/groups/gitlab-org/-/epics/14273) | {{< icon name="check-circle" >}} Yes: [Semgrep](https://gitlab.com/gitlab-org/security-products/analyzers/semgrep) with [GitLab-managed rules](rules.md#semgrep-based-analyzer) |
+| Python | {{< icon name="check-circle" >}} Yes | {{< icon name="check-circle" >}} Yes: [Semgrep](https://gitlab.com/gitlab-org/security-products/analyzers/semgrep) with [GitLab-managed rules](rules.md#semgrep-based-analyzer) |
+| Ruby, including Ruby on Rails | {{< icon name="check-circle" >}} Yes | {{< icon name="check-circle" >}} Yes: [Semgrep](https://gitlab.com/gitlab-org/security-products/analyzers/semgrep) with [GitLab-managed rules](rules.md#semgrep-based-analyzer) |
+| Scala | {{< icon name="dotted-circle" >}} No; tracked in [epic 15174](https://gitlab.com/groups/gitlab-org/-/epics/15174) | {{< icon name="check-circle" >}} Yes: [Semgrep](https://gitlab.com/gitlab-org/security-products/analyzers/semgrep) with [GitLab-managed rules](rules.md#semgrep-based-analyzer) |
+| Swift (iOS) | {{< icon name="dotted-circle" >}} No | {{< icon name="check-circle" >}} Yes: [Semgrep](https://gitlab.com/gitlab-org/security-products/analyzers/semgrep) with [GitLab-managed rules](rules.md#semgrep-based-analyzer) |
+| TypeScript | {{< icon name="check-circle" >}} Yes | {{< icon name="check-circle" >}} Yes: [Semgrep](https://gitlab.com/gitlab-org/security-products/analyzers/semgrep) with [GitLab-managed rules](rules.md#semgrep-based-analyzer) |
+| YAML2 | {{< icon name="check-circle" >}} Yes | {{< icon name="check-circle" >}} Yes: [Semgrep](https://gitlab.com/gitlab-org/security-products/analyzers/semgrep) with [GitLab-managed rules](rules.md#semgrep-based-analyzer) |
+| Java Properties | {{< icon name="check-circle" >}} Yes | {{< icon name="check-circle" >}} Yes: [Semgrep](https://gitlab.com/gitlab-org/security-products/analyzers/semgrep) with [GitLab-managed rules](rules.md#semgrep-based-analyzer) |
**Footnotes**:
@@ -656,11 +656,11 @@ The `ADDITIONAL_CA_CERT_BUNDLE` value can also be configured as a [custom variab
The following are Docker image-related CI/CD variables.
-| CI/CD variable | Description |
-|---------------------------|---------------------------------------------------------------------------------------------------------------------------------------|
+| CI/CD variable | Description |
+|---------------------------|-------------|
| `SECURE_ANALYZERS_PREFIX` | Override the name of the Docker registry providing the default images (proxy). Read more about [customizing analyzers](analyzers.md). |
-| `SAST_EXCLUDED_ANALYZERS` | Names of default images that should never run. Read more about [customizing analyzers](analyzers.md). |
-| `SAST_ANALYZER_IMAGE_TAG` | Override the default version of analyzer image. Read more about [pinning the analyzer image version](#pinning-to-minor-image-version). |
+| `SAST_EXCLUDED_ANALYZERS` | Names of default images that should never run. Read more about [customizing analyzers](analyzers.md). |
+| `SAST_ANALYZER_IMAGE_TAG` | Override the default version of analyzer image. Read more about [pinning the analyzer image version](#pinning-to-minor-image-version). |
| `SAST_IMAGE_SUFFIX` | Suffix added to the image name. If set to `-fips`, `FIPS-enabled` images are used for scan. See [FIPS-enabled images](#fips-enabled-images) for more details. |
#### Vulnerability filters
@@ -780,7 +780,7 @@ The following are Docker image-related CI/CD variables.
patterns), or file or folder paths (for example, `doc,spec`). Parent directories also match patterns.
The post-filter implementation of `SAST_EXCLUDED_PATHS` is available for all SAST analyzers. Some
- SAST analyzers such as those with superscript **[2](#sast-excluded-paths-semgrep)** implement `SAST_EXCLUDED_PATHS`
+ SAST analyzers such as those with [superscript `2`](#sast-excluded-paths-semgrep) implement `SAST_EXCLUDED_PATHS`
as both a pre-filter and post-filter. A pre-filter is more efficient because it reduces the number of files
to be scanned.
@@ -812,28 +812,28 @@ The following are Docker image-related CI/CD variables.
Some analyzers can be customized with CI/CD variables.
-| CI/CD variable | Analyzer | Description |
-|-----------------------------|------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| `GITLAB_ADVANCED_SAST_ENABLED` | GitLab Advanced SAST | Set to `true` to enable [GitLab Advanced SAST](gitlab_advanced_sast.md) scanning (available in GitLab Ultimate only). Default: `false`. |
-| `SCAN_KUBERNETES_MANIFESTS` | Kubesec | Set to `"true"` to scan Kubernetes manifests. |
-| `KUBESEC_HELM_CHARTS_PATH` | Kubesec | Optional path to Helm charts that `helm` uses to generate a Kubernetes manifest that `kubesec` scans. If dependencies are defined, `helm dependency build` should be ran in a `before_script` to fetch the necessary dependencies. |
-| `KUBESEC_HELM_OPTIONS` | Kubesec | Additional arguments for the `helm` executable. |
-| `COMPILE` | SpotBugs | Set to `false` to disable project compilation and dependency fetching. |
-| `ANT_HOME` | SpotBugs | The `ANT_HOME` variable. |
-| `ANT_PATH` | SpotBugs | Path to the `ant` executable. |
-| `GRADLE_PATH` | SpotBugs | Path to the `gradle` executable. |
-| `JAVA_OPTS` | SpotBugs | Additional arguments for the `java` executable. |
-| `JAVA_PATH` | SpotBugs | Path to the `java` executable. |
-| `SAST_JAVA_VERSION` | SpotBugs | Which Java version to use. [Starting in GitLab 15.0](https://gitlab.com/gitlab-org/gitlab/-/issues/352549), supported versions are `11` and `17` (default). Before GitLab 15.0, supported versions are `8` (default) and `11`. |
-| `MAVEN_CLI_OPTS` | SpotBugs | Additional arguments for the `mvn` or `mvnw` executable. |
-| `MAVEN_PATH` | SpotBugs | Path to the `mvn` executable. |
-| `MAVEN_REPO_PATH` | SpotBugs | Path to the Maven local repository (shortcut for the `maven.repo.local` property). |
-| `SBT_PATH` | SpotBugs | Path to the `sbt` executable. |
-| `FAIL_NEVER` | SpotBugs | Set to `1` to ignore compilation failure. |
-| `SAST_SEMGREP_METRICS` | Semgrep | Set to `"false"` to disable sending anonymized scan metrics to [r2c](https://semgrep.dev). Default: `true`. |
-| `SAST_SCANNER_ALLOWED_CLI_OPTS` | Semgrep | CLI options (arguments with value, or flags) that are passed to the underlying security scanner when running scan operation. Only a limited set of [options](#security-scanner-configuration) are accepted. Separate a CLI option and its value using either a blank space or equals (`=`) character. For example: `name1 value1` or `name1=value1`. Multiple options must be separated by blank spaces. For example: `name1 value1 name2 value2`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/368565) in GitLab 15.3. |
-| `SAST_RULESET_GIT_REFERENCE` | All | Defines a path to a custom ruleset configuration. If a project has a `.gitlab/sast-ruleset.toml` file committed, that local configuration takes precedence and the file from `SAST_RULESET_GIT_REFERENCE` isn't used. This variable is available for the Ultimate tier only.|
-| `SECURE_ENABLE_LOCAL_CONFIGURATION` | All | Enables the option to use custom ruleset configuration. If `SECURE_ENABLE_LOCAL_CONFIGURATION` is set to `false`, the project's custom ruleset configuration file at `.gitlab/sast-ruleset.toml` is ignored and the file from `SAST_RULESET_GIT_REFERENCE` or the default configuration takes precedence. |
+| CI/CD variable | Analyzer | Description |
+|-------------------------------------|----------------------|-------------|
+| `GITLAB_ADVANCED_SAST_ENABLED` | GitLab Advanced SAST | Set to `true` to enable [GitLab Advanced SAST](gitlab_advanced_sast.md) scanning (available in GitLab Ultimate only). Default: `false`. |
+| `SCAN_KUBERNETES_MANIFESTS` | Kubesec | Set to `"true"` to scan Kubernetes manifests. |
+| `KUBESEC_HELM_CHARTS_PATH` | Kubesec | Optional path to Helm charts that `helm` uses to generate a Kubernetes manifest that `kubesec` scans. If dependencies are defined, `helm dependency build` should be ran in a `before_script` to fetch the necessary dependencies. |
+| `KUBESEC_HELM_OPTIONS` | Kubesec | Additional arguments for the `helm` executable. |
+| `COMPILE` | SpotBugs | Set to `false` to disable project compilation and dependency fetching. |
+| `ANT_HOME` | SpotBugs | The `ANT_HOME` variable. |
+| `ANT_PATH` | SpotBugs | Path to the `ant` executable. |
+| `GRADLE_PATH` | SpotBugs | Path to the `gradle` executable. |
+| `JAVA_OPTS` | SpotBugs | Additional arguments for the `java` executable. |
+| `JAVA_PATH` | SpotBugs | Path to the `java` executable. |
+| `SAST_JAVA_VERSION` | SpotBugs | Which Java version to use. [Starting in GitLab 15.0](https://gitlab.com/gitlab-org/gitlab/-/issues/352549), supported versions are `11` and `17` (default). Before GitLab 15.0, supported versions are `8` (default) and `11`. |
+| `MAVEN_CLI_OPTS` | SpotBugs | Additional arguments for the `mvn` or `mvnw` executable. |
+| `MAVEN_PATH` | SpotBugs | Path to the `mvn` executable. |
+| `MAVEN_REPO_PATH` | SpotBugs | Path to the Maven local repository (shortcut for the `maven.repo.local` property). |
+| `SBT_PATH` | SpotBugs | Path to the `sbt` executable. |
+| `FAIL_NEVER` | SpotBugs | Set to `1` to ignore compilation failure. |
+| `SAST_SEMGREP_METRICS` | Semgrep | Set to `"false"` to disable sending anonymized scan metrics to [r2c](https://semgrep.dev). Default: `true`. |
+| `SAST_SCANNER_ALLOWED_CLI_OPTS` | Semgrep | CLI options (arguments with value, or flags) that are passed to the underlying security scanner when running scan operation. Only a limited set of [options](#security-scanner-configuration) are accepted. Separate a CLI option and its value using either a blank space or equals (`=`) character. For example: `name1 value1` or `name1=value1`. Multiple options must be separated by blank spaces. For example: `name1 value1 name2 value2`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/368565) in GitLab 15.3. |
+| `SAST_RULESET_GIT_REFERENCE` | All | Defines a path to a custom ruleset configuration. If a project has a `.gitlab/sast-ruleset.toml` file committed, that local configuration takes precedence and the file from `SAST_RULESET_GIT_REFERENCE` isn't used. This variable is available for the Ultimate tier only. |
+| `SECURE_ENABLE_LOCAL_CONFIGURATION` | All | Enables the option to use custom ruleset configuration. If `SECURE_ENABLE_LOCAL_CONFIGURATION` is set to `false`, the project's custom ruleset configuration file at `.gitlab/sast-ruleset.toml` is ignored and the file from `SAST_RULESET_GIT_REFERENCE` or the default configuration takes precedence. |
#### Security scanner configuration
@@ -1021,13 +1021,13 @@ For details on saving and transporting Docker images as a file, see the Docker d
Support for custom certificate authorities was introduced in the following versions.
-| Analyzer | Version |
-| -------- | ------- |
-| `kubesec` | [v2.1.0](https://gitlab.com/gitlab-org/security-products/analyzers/kubesec/-/releases/v2.1.0) |
-| `pmd-apex` | [v2.1.0](https://gitlab.com/gitlab-org/security-products/analyzers/pmd-apex/-/releases/v2.1.0) |
-| `semgrep` | [v0.0.1](https://gitlab.com/gitlab-org/security-products/analyzers/semgrep/-/releases/v0.0.1) |
-| `sobelow` | [v2.2.0](https://gitlab.com/gitlab-org/security-products/analyzers/sobelow/-/releases/v2.2.0) |
-| `spotbugs` | [v2.7.1](https://gitlab.com/gitlab-org/security-products/analyzers/spotbugs/-/releases/v2.7.1) |
+| Analyzer | Version |
+|------------|---------|
+| `kubesec` | [v2.1.0](https://gitlab.com/gitlab-org/security-products/analyzers/kubesec/-/releases/v2.1.0) |
+| `pmd-apex` | [v2.1.0](https://gitlab.com/gitlab-org/security-products/analyzers/pmd-apex/-/releases/v2.1.0) |
+| `semgrep` | [v0.0.1](https://gitlab.com/gitlab-org/security-products/analyzers/semgrep/-/releases/v0.0.1) |
+| `sobelow` | [v2.2.0](https://gitlab.com/gitlab-org/security-products/analyzers/sobelow/-/releases/v2.2.0) |
+| `spotbugs` | [v2.7.1](https://gitlab.com/gitlab-org/security-products/analyzers/spotbugs/-/releases/v2.7.1) |
### Set SAST CI/CD variables to use local SAST analyzers
diff --git a/lib/gitlab/database/lock_writes_manager.rb b/lib/gitlab/database/lock_writes_manager.rb
index 025e2954b4a..7417db23fca 100644
--- a/lib/gitlab/database/lock_writes_manager.rb
+++ b/lib/gitlab/database/lock_writes_manager.rb
@@ -12,12 +12,16 @@ module Gitlab
# table_name can include schema name as a prefix. For example: 'gitlab_partitions_static.events_03',
# otherwise, it will default to current used schema, for example 'public'.
- def initialize(table_name:, connection:, database_name:, with_retries: true, logger: nil, dry_run: false)
+ def initialize(
+ table_name:, connection:, database_name:,
+ with_retries: true, logger: nil, dry_run: false, force: true
+ )
@table_name = table_name.to_s
@connection = connection
@database_name = database_name
@logger = logger
@dry_run = dry_run
+ @force = force
@with_retries = with_retries
@table_name_without_schema = ActiveRecord::ConnectionAdapters::PostgreSQL::Utils
@@ -36,6 +40,18 @@ module Gitlab
end
def lock_writes
+ unless force
+ unless table_exist?
+ logger&.info "Skipping lock_writes, because #{table_name} does not exist"
+ return result_hash(action: 'skipped')
+ end
+
+ if table_locked_for_writes?
+ logger&.info "Skipping lock_writes, because #{table_name} is already locked for writes"
+ return result_hash(action: 'skipped')
+ end
+ end
+
logger&.info Rainbow("Database: '#{database_name}', Table: '#{table_name}': Lock Writes").yellow
sql_statement = <<~SQL
CREATE OR REPLACE TRIGGER #{write_trigger_name}
@@ -55,19 +71,31 @@ module Gitlab
end
def unlock_writes
- logger&.info Rainbow("Database: '#{database_name}', Table: '#{table_name}': Allow Writes").green
- sql_statement = <<~SQL
- DROP TRIGGER IF EXISTS #{write_trigger_name} ON #{table_name};
- SQL
+ if force || table_locked_for_writes?
+ logger&.info Rainbow("Database: '#{database_name}', Table: '#{table_name}': Allow Writes").green
+ sql_statement = <<~SQL
+ DROP TRIGGER IF EXISTS #{write_trigger_name} ON #{table_name};
+ SQL
- result = process_query(sql_statement, 'unlock')
+ result = process_query(sql_statement, 'unlock')
- result_hash(action: result)
+ result_hash(action: result)
+ else
+ logger&.info "Skipping unlock_writes, because #{table_name} is already unlocked for writes"
+ result_hash(action: 'skipped')
+ end
end
private
- attr_reader :table_name, :connection, :database_name, :logger, :dry_run, :table_name_without_schema, :with_retries
+ attr_reader :table_name,
+ :connection,
+ :database_name,
+ :logger,
+ :dry_run,
+ :force,
+ :table_name_without_schema,
+ :with_retries
def table_exist?
where = if table_name.include?('.')
diff --git a/lib/gitlab/database/tables_locker.rb b/lib/gitlab/database/tables_locker.rb
index 158cad137eb..f5710604658 100644
--- a/lib/gitlab/database/tables_locker.rb
+++ b/lib/gitlab/database/tables_locker.rb
@@ -114,7 +114,8 @@ module Gitlab
database_name: database_name,
with_retries: true,
logger: @logger,
- dry_run: @dry_run
+ dry_run: @dry_run,
+ force: !@dry_run
)
end
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index c1a85fc3176..4cd9c7be7ef 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -44114,9 +44114,15 @@ msgstr ""
msgid "Pages|URL"
msgstr ""
+msgid "Pages|Visit site"
+msgstr ""
+
msgid "Pages|We did not find any projects with parallel Pages deployments in this namespace."
msgstr ""
+msgid "Pages|Your Pages site is live at"
+msgstr ""
+
msgid "Pagination|First"
msgstr ""
@@ -53353,6 +53359,9 @@ msgstr ""
msgid "ScanExecutionPolicy|%{rules} every time a pipeline runs for %{scopes} %{branches} %{branchExceptions} %{agents} %{namespaces}"
msgstr ""
+msgid "ScanExecutionPolicy|%{rules} every time a pipeline runs for %{scopes} %{branches} %{sources} %{branchExceptions} %{agents} %{namespaces}"
+msgstr ""
+
msgid "ScanExecutionPolicy|%{strategySelector} pipeline file to run from %{projectSelector}"
msgstr ""
@@ -55001,6 +55010,9 @@ msgstr ""
msgid "SecurityOrchestration|A scan execution policy exceeds the limit of %{maxScanExecutionPolicySchedules} scheduled rules per policy. Remove or consolidate rules across policies to reduce the total number of rules."
msgstr ""
+msgid "SecurityOrchestration|API request"
+msgstr ""
+
msgid "SecurityOrchestration|Actions"
msgstr ""
@@ -55127,6 +55139,9 @@ msgstr ""
msgid "SecurityOrchestration|Cannot create an empty policy"
msgstr ""
+msgid "SecurityOrchestration|ChatOps command"
+msgstr ""
+
msgid "SecurityOrchestration|Choose a project"
msgstr ""
@@ -55145,6 +55160,9 @@ msgstr ""
msgid "SecurityOrchestration|Clear all"
msgstr ""
+msgid "SecurityOrchestration|Code push"
+msgstr ""
+
msgid "SecurityOrchestration|Compliance framework has no projects"
msgstr ""
@@ -55295,6 +55313,12 @@ msgstr ""
msgid "SecurityOrchestration|Excluded projects"
msgstr ""
+msgid "SecurityOrchestration|External event"
+msgstr ""
+
+msgid "SecurityOrchestration|External pull request"
+msgstr ""
+
msgid "SecurityOrchestration|Failed to fetch the scope information. Please refresh the page to try again."
msgstr ""
@@ -55412,6 +55436,9 @@ msgstr ""
msgid "SecurityOrchestration|Logic error"
msgstr ""
+msgid "SecurityOrchestration|Manual pipeline run"
+msgstr ""
+
msgid "SecurityOrchestration|Maximum action limit for scan execution policies will be enabled in 18.0"
msgstr ""
@@ -55436,6 +55463,9 @@ msgstr ""
msgid "SecurityOrchestration|Merge request approval policy syntax changes"
msgstr ""
+msgid "SecurityOrchestration|Merge request pipeline"
+msgstr ""
+
msgid "SecurityOrchestration|Never"
msgstr ""
@@ -55666,6 +55696,9 @@ msgstr ""
msgid "SecurityOrchestration|Schedule to run for %{branchSelector}"
msgstr ""
+msgid "SecurityOrchestration|Scheduled pipeline"
+msgstr ""
+
msgid "SecurityOrchestration|Scheduled pipeline execution"
msgstr ""
@@ -55726,6 +55759,9 @@ msgstr ""
msgid "SecurityOrchestration|Select suffix"
msgstr ""
+msgid "SecurityOrchestration|Select the source"
+msgstr ""
+
msgid "SecurityOrchestration|Send a bot message when the conditions match."
msgstr ""
@@ -55873,12 +55909,18 @@ msgstr ""
msgid "SecurityOrchestration|To widen your search, change filters above or select a different security policy project."
msgstr ""
+msgid "SecurityOrchestration|Trigger"
+msgstr ""
+
msgid "SecurityOrchestration|Turn off default mode to edit scope."
msgstr ""
msgid "SecurityOrchestration|Type in custom variable"
msgstr ""
+msgid "SecurityOrchestration|Unknown source"
+msgstr ""
+
msgid "SecurityOrchestration|Unlink project"
msgstr ""
@@ -55966,6 +56008,9 @@ msgstr ""
msgid "SecurityOrchestration|Warn users with a bot comment and contact the following users as security consultants for support: %{approvers}"
msgstr ""
+msgid "SecurityOrchestration|Web UI"
+msgstr ""
+
msgid "SecurityOrchestration|What does an example message look like?"
msgstr ""
diff --git a/qa/gems/gitlab-orchestrator/lib/gitlab/orchestrator/commands/subcommands/deployment.rb b/qa/gems/gitlab-orchestrator/lib/gitlab/orchestrator/commands/subcommands/deployment.rb
index 00004bd0123..0a9e7aa4e3f 100644
--- a/qa/gems/gitlab-orchestrator/lib/gitlab/orchestrator/commands/subcommands/deployment.rb
+++ b/qa/gems/gitlab-orchestrator/lib/gitlab/orchestrator/commands/subcommands/deployment.rb
@@ -53,6 +53,14 @@ module Gitlab
desc: "Max number of retries for failed deployment",
default: 0,
type: :numeric
+ option :resource_preset,
+ desc: "Kubernetes resource definition preset",
+ default: Gitlab::Orchestrator::Deployment::ResourcePresets::DEFAULT,
+ type: :string,
+ enum: [
+ Gitlab::Orchestrator::Deployment::ResourcePresets::DEFAULT,
+ Gitlab::Orchestrator::Deployment::ResourcePresets::HIGH
+ ]
super
end
@@ -113,7 +121,8 @@ module Gitlab
:admin_token,
:host_http_port,
:host_ssh_port,
- :host_registry_port
+ :host_registry_port,
+ :resource_preset
)
installation(name, Orchestrator::Deployment::Configurations::Kind.new(**configuration_args)).create
diff --git a/qa/gems/gitlab-orchestrator/lib/gitlab/orchestrator/lib/deployment/configurations/kind.rb b/qa/gems/gitlab-orchestrator/lib/gitlab/orchestrator/lib/deployment/configurations/kind.rb
index b9c6567a0ce..67efc8e81c2 100644
--- a/qa/gems/gitlab-orchestrator/lib/gitlab/orchestrator/lib/deployment/configurations/kind.rb
+++ b/qa/gems/gitlab-orchestrator/lib/gitlab/orchestrator/lib/deployment/configurations/kind.rb
@@ -21,23 +21,27 @@ module Gitlab
fi
SH
- def initialize(
- namespace:,
- ci:,
- gitlab_domain:,
- admin_password:,
- admin_token:,
- host_http_port:,
- host_ssh_port:,
- host_registry_port:
- )
- super(namespace: namespace, ci: ci, gitlab_domain: gitlab_domain)
+ # Instance of kind deployment configuration
+ #
+ # @param **args [Hash]
+ # @option args [String] :namespace Namespace used for deployment
+ # @option args [Boolean] :ci Run for CI environment
+ # @option args [String] :gitlab_domain Custom gitlab domain
+ # @option args [String] :admin_password Initial password for admin user
+ # @option args [String] :admin_token Initial PAT token for admin user
+ # @option args [Integer] :host_http_port HTTP port for gitlab pages
+ # @option args [Integer] :host_ssh_port SSH port for gitlab
+ # @option args [Integer] :host_registry_port Registry port for gitlab
+ # @option args [String] :resource_preset resource preset name
+ def initialize(**args)
+ super(**args.slice(:namespace, :ci, :gitlab_domain))
- @admin_password = admin_password
- @admin_token = admin_token
- @host_http_port = host_http_port
- @host_ssh_port = host_ssh_port
- @host_registry_port = host_registry_port
+ @admin_password = args[:admin_password]
+ @admin_token = args[:admin_token]
+ @host_http_port = args[:host_http_port]
+ @host_ssh_port = args[:host_ssh_port]
+ @host_registry_port = args[:host_registry_port]
+ @resource_preset = args[:resource_preset]
end
# Run pre-deployment setup
@@ -96,7 +100,7 @@ module Gitlab
}
}
}
- }
+ }.deep_merge(ResourcePresets.resource_values(resource_preset))
end
# Gitlab url
@@ -108,7 +112,12 @@ module Gitlab
private
- attr_reader :admin_password, :admin_token, :host_http_port, :host_ssh_port, :host_registry_port
+ attr_reader :admin_password,
+ :admin_token,
+ :host_http_port,
+ :host_ssh_port,
+ :host_registry_port,
+ :resource_preset
# Token seed script for root user
#
diff --git a/qa/gems/gitlab-orchestrator/lib/gitlab/orchestrator/lib/deployment/installation.rb b/qa/gems/gitlab-orchestrator/lib/gitlab/orchestrator/lib/deployment/installation.rb
index 73d7ecd5bcf..22a60898580 100644
--- a/qa/gems/gitlab-orchestrator/lib/gitlab/orchestrator/lib/deployment/installation.rb
+++ b/qa/gems/gitlab-orchestrator/lib/gitlab/orchestrator/lib/deployment/installation.rb
@@ -189,7 +189,6 @@ module Gitlab
.deep_merge(license_values)
.deep_merge(env_values)
.deep_merge(configuration.values)
- .deep_merge(ResourcePresets.resource_values(ci ? ResourcePresets::HIGH : ResourcePresets::DEFAULT))
.deep_stringify_keys
.to_yaml
diff --git a/qa/gems/gitlab-orchestrator/spec/unit/gitlab/orchestrator/commands/subcommands/deployment_spec.rb b/qa/gems/gitlab-orchestrator/spec/unit/gitlab/orchestrator/commands/subcommands/deployment_spec.rb
index 92685af4e4a..f50cbefeb3e 100644
--- a/qa/gems/gitlab-orchestrator/spec/unit/gitlab/orchestrator/commands/subcommands/deployment_spec.rb
+++ b/qa/gems/gitlab-orchestrator/spec/unit/gitlab/orchestrator/commands/subcommands/deployment_spec.rb
@@ -59,7 +59,8 @@ RSpec.describe Gitlab::Orchestrator::Commands::Subcommands::Deployment do
admin_token: "ypCa3Dzb23o5nvsixwPA",
host_http_port: 80,
host_ssh_port: 22,
- host_registry_port: 5000
+ host_registry_port: 5000,
+ resource_preset: "default"
)
expect(installation_instance).to have_received(:create)
end
diff --git a/qa/gems/gitlab-orchestrator/spec/unit/gitlab/orchestrator/deployment/configurations/kind_spec.rb b/qa/gems/gitlab-orchestrator/spec/unit/gitlab/orchestrator/deployment/configurations/kind_spec.rb
index d53e1adc85b..1fa46846b2a 100644
--- a/qa/gems/gitlab-orchestrator/spec/unit/gitlab/orchestrator/deployment/configurations/kind_spec.rb
+++ b/qa/gems/gitlab-orchestrator/spec/unit/gitlab/orchestrator/deployment/configurations/kind_spec.rb
@@ -10,10 +10,13 @@ RSpec.describe Gitlab::Orchestrator::Deployment::Configurations::Kind do
admin_token: "token",
host_http_port: 80,
host_ssh_port: 22,
- host_registry_port: 5000
+ host_registry_port: 5000,
+ resource_preset: resource_preset
)
end
+ let(:resource_preset) { "default" }
+
let(:kubeclient) do
instance_double(Gitlab::Orchestrator::Kubectl::Client, create_resource: "", execute: "", patch: "")
end
@@ -146,7 +149,7 @@ RSpec.describe Gitlab::Orchestrator::Deployment::Configurations::Kind do
}
}
}
- })
+ }.deep_merge(Gitlab::Orchestrator::Deployment::ResourcePresets.resource_values(resource_preset)))
end
it "returns correct gitlab url" do
diff --git a/qa/gems/gitlab-orchestrator/spec/unit/gitlab/orchestrator/deployment/installation_spec.rb b/qa/gems/gitlab-orchestrator/spec/unit/gitlab/orchestrator/deployment/installation_spec.rb
index efb40d63608..e4c92bb5c66 100644
--- a/qa/gems/gitlab-orchestrator/spec/unit/gitlab/orchestrator/deployment/installation_spec.rb
+++ b/qa/gems/gitlab-orchestrator/spec/unit/gitlab/orchestrator/deployment/installation_spec.rb
@@ -41,16 +41,6 @@ RSpec.describe Gitlab::Orchestrator::Deployment::Installation, :aggregate_failur
)
end
- let(:resources_values) do
- presets = if ci
- Gitlab::Orchestrator::Deployment::ResourcePresets::HIGH
- else
- Gitlab::Orchestrator::Deployment::ResourcePresets::DEFAULT
- end
-
- Gitlab::Orchestrator::Deployment::ResourcePresets.resource_values(presets)
- end
-
let(:expected_values_yml) do
{
global: {
@@ -65,7 +55,7 @@ RSpec.describe Gitlab::Orchestrator::Deployment::Installation, :aggregate_failur
license: { secret: "gitlab-license" }
},
**config_values
- }.deep_merge(resources_values).deep_stringify_keys.to_yaml
+ }.deep_stringify_keys.to_yaml
end
before do
diff --git a/spec/frontend/gitlab_pages/components/live_block_spec.js b/spec/frontend/gitlab_pages/components/live_block_spec.js
new file mode 100644
index 00000000000..b9e06f06421
--- /dev/null
+++ b/spec/frontend/gitlab_pages/components/live_block_spec.js
@@ -0,0 +1,79 @@
+import VueApollo from 'vue-apollo';
+import Vue from 'vue';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
+import LiveBlock from '~/gitlab_pages/components/live_block.vue';
+import CrudComponent from '~/vue_shared/components/crud_component.vue';
+import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
+import { primaryDeployment } from '../mock_data';
+
+Vue.use(VueApollo);
+
+describe('PagesLiveBlock', () => {
+ let wrapper;
+
+ const createComponent = ({ provide = {} } = {}) => {
+ wrapper = mountExtended(LiveBlock, {
+ propsData: {
+ deployment: primaryDeployment,
+ },
+ provide: {
+ projectFullPath: 'my-group/my-project',
+ primaryDomain: null,
+ ...provide,
+ },
+ stubs: {
+ CrudComponent,
+ TimeAgo,
+ },
+ });
+ };
+
+ const findHeading = () => wrapper.findByTestId('live-heading');
+ const findHeadingLink = () => wrapper.findByTestId('live-heading-link');
+ const findDeployJobNumber = () => wrapper.findByTestId('deploy-job-number');
+ const findUpdatedAt = () => wrapper.findComponent(TimeAgo);
+ const findVisitSite = () => wrapper.findByTestId('visit-site-url');
+
+ describe('displays expected data', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('renders heading', () => {
+ expect(findHeading().text().replace(/\s+/g, ' ')).toBe(
+ `Your Pages site is live at ${primaryDeployment.url} ๐`,
+ );
+ expect(findHeadingLink().attributes('href')).toBe(primaryDeployment.url);
+ });
+
+ it('renders deploy job number and link', () => {
+ expect(findDeployJobNumber().text()).toBe(primaryDeployment.ciBuildId.toString());
+ expect(findDeployJobNumber().attributes('href')).toBe('/my-group/my-project/-/jobs/123');
+ });
+
+ it('renders updated at', () => {
+ expect(findUpdatedAt().props('time')).toBe(primaryDeployment.updatedAt);
+ });
+
+ it('renders visit site button', () => {
+ expect(findVisitSite().text()).toBe('Visit site');
+ expect(findVisitSite().attributes('href')).toBe(primaryDeployment.url);
+ });
+
+ it('uses primaryDomain when set', () => {
+ const primaryDomain = 'https://primary.domain';
+
+ createComponent({
+ provide: {
+ primaryDomain,
+ },
+ });
+
+ expect(findHeading().text().replace(/\s+/g, ' ')).toBe(
+ `Your Pages site is live at ${primaryDomain} ๐`,
+ );
+ expect(findHeadingLink().attributes('href')).toBe(primaryDomain);
+ expect(findVisitSite().attributes('href')).toBe(primaryDomain);
+ });
+ });
+});
diff --git a/spec/frontend/glql/components/presenters/issuable_spec.js b/spec/frontend/glql/components/presenters/issuable_spec.js
index b060195072b..163f843b2ed 100644
--- a/spec/frontend/glql/components/presenters/issuable_spec.js
+++ b/spec/frontend/glql/components/presenters/issuable_spec.js
@@ -30,17 +30,6 @@ describe('IssuablePresenter', () => {
expect(link.text()).toEqual(expectedText);
});
- it('truncates long titles', () => {
- const longTitle =
- 'A quick brown fox jumps over the lazy dog. A quick brown fox jumps over the lazy dog.';
- createWrapper({ data: { ...mockData, title: longTitle } });
-
- const link = findLink();
-
- expect(link.text()).toContain('A quick brown fox jumps over the lazy dog. A qu...');
- expect(link.attributes('title')).toBe(longTitle);
- });
-
it(`correctly renders the state if the ${type} is closed`, () => {
createWrapper({ data: { ...mockData, state: 'closed' } });
diff --git a/spec/frontend/work_items/components/work_item_description_spec.js b/spec/frontend/work_items/components/work_item_description_spec.js
index c80129a58d8..9ca9b99d0b1 100644
--- a/spec/frontend/work_items/components/work_item_description_spec.js
+++ b/spec/frontend/work_items/components/work_item_description_spec.js
@@ -406,19 +406,22 @@ describe('WorkItemDescription', () => {
// Mimic component mount with a pre-populated description
await createComponent({
editMode: true,
+ workItemResponseHandler: jest
+ .fn()
+ .mockResolvedValue(
+ workItemByIidResponseFactory({ description: 'Pre-filled description' }),
+ ),
workItemId: newWorkItemId(workItemQueryResponse.data.workItem.workItemType.name),
});
- findDescriptionTemplateListbox().vm.$emit('selectTemplate', {
- name: 'default',
- projectId: 1,
- catagory: 'catagory',
- });
await nextTick();
await waitForPromises();
expect(findDescriptionTemplateWarning().exists()).toBe(false);
expect(findCancelApplyTemplate().exists()).toBe(false);
expect(findApplyTemplate().exists()).toBe(false);
+
+ // No template is selected when description is pre-filled
+ expect(findDescriptionTemplateListbox().props('template')).toBeNull();
});
it('hides the warning when the cancel button is clicked', async () => {
diff --git a/spec/helpers/issuables_helper_spec.rb b/spec/helpers/issuables_helper_spec.rb
index 1716242944a..17fe1cb95d3 100644
--- a/spec/helpers/issuables_helper_spec.rb
+++ b/spec/helpers/issuables_helper_spec.rb
@@ -105,29 +105,6 @@ RSpec.describe IssuablesHelper, feature_category: :team_planning do
end
end
- describe '#assigned_issuables_count', feature_category: :team_planning do
- context 'when issuable is issues' do
- let_it_be(:user) { create(:user) }
- let_it_be(:project) { create(:project, developers: user) }
-
- subject { helper.assigned_issuables_count(:issues) }
-
- before do
- allow(helper).to receive(:current_user).and_return(user)
- end
-
- context 'when assigned issues count is over MAX_LIMIT_FOR_ASSIGNEED_ISSUES_COUNT' do
- before do
- stub_const('User::MAX_LIMIT_FOR_ASSIGNEED_ISSUES_COUNT', 2)
- end
-
- let_it_be(:issues) { create_list(:issue, 3, project: project, assignees: [user]) }
-
- it { is_expected.to eq 2 }
- end
- end
- end
-
describe '#issuables_state_counter_text' do
let_it_be(:user) { create(:user) }
diff --git a/spec/helpers/merge_requests_helper_spec.rb b/spec/helpers/merge_requests_helper_spec.rb
index 9e8af73cd4b..0ab946128bb 100644
--- a/spec/helpers/merge_requests_helper_spec.rb
+++ b/spec/helpers/merge_requests_helper_spec.rb
@@ -120,29 +120,6 @@ RSpec.describe MergeRequestsHelper, feature_category: :code_review_workflow do
end
end
- describe '#user_merge_requests_counts' do
- let(:user) do
- double(
- assigned_open_merge_requests_count: 1,
- review_requested_open_merge_requests_count: 2
- )
- end
-
- subject { helper.user_merge_requests_counts }
-
- before do
- allow(helper).to receive(:current_user).and_return(user)
- end
-
- it "returns assigned, review requested and total merge request counts" do
- expect(subject).to eq(
- assigned: user.assigned_open_merge_requests_count,
- review_requested: user.review_requested_open_merge_requests_count,
- total: user.assigned_open_merge_requests_count + user.review_requested_open_merge_requests_count
- )
- end
- end
-
describe '#reviewers_label' do
let(:merge_request) { build_stubbed(:merge_request) }
let(:reviewer1) { build_stubbed(:user, name: 'Jane Doe') }
diff --git a/spec/lib/gitlab/database/lock_writes_manager_spec.rb b/spec/lib/gitlab/database/lock_writes_manager_spec.rb
index e241075fd3d..059041ce61d 100644
--- a/spec/lib/gitlab/database/lock_writes_manager_spec.rb
+++ b/spec/lib/gitlab/database/lock_writes_manager_spec.rb
@@ -9,6 +9,7 @@ RSpec.describe Gitlab::Database::LockWritesManager, :delete, feature_category: :
let(:skip_table_creation) { false }
let(:logger) { instance_double(Logger) }
let(:dry_run) { false }
+ let(:force) { true }
subject(:lock_writes_manager) do
described_class.new(
@@ -17,7 +18,8 @@ RSpec.describe Gitlab::Database::LockWritesManager, :delete, feature_category: :
database_name: 'main',
with_retries: true,
logger: logger,
- dry_run: dry_run
+ dry_run: dry_run,
+ force: force
)
end
@@ -125,36 +127,58 @@ RSpec.describe Gitlab::Database::LockWritesManager, :delete, feature_category: :
}
end
- it 'replaces the trigger if the table is already locked' do
- subject.lock_writes
+ context 'when running in force mode' do
+ it 'replaces the trigger if the table is already locked' do
+ subject.lock_writes
- expect(connection).to receive(:execute).with(/CREATE OR REPLACE TRIGGER/)
+ expect(connection).to receive(:execute).with(/CREATE OR REPLACE TRIGGER/)
- expect do
- result = subject.lock_writes
- expect(result).to eq({ action: "locked", database: "main", dry_run: false, table: test_table })
- end.not_to change {
- number_of_triggers_on(connection, test_table)
- }
- end
-
- context 'when table does not exist' do
- let(:skip_table_creation) { true }
- let(:test_table) { non_existent_table }
-
- # In tests if we don't add the fake table to the gitlab schema then our test only schema checks will fail.
- before do
- allow(Gitlab::Database::GitlabSchema).to receive(:table_schema).and_call_original
- allow(Gitlab::Database::GitlabSchema).to receive(:table_schema).with(
- non_existent_table
- ).and_return(:gitlab_main_cell)
+ expect do
+ result = subject.lock_writes
+ expect(result).to eq({ action: "locked", database: "main", dry_run: false, table: test_table })
+ end.not_to change {
+ number_of_triggers_on(connection, test_table)
+ }
end
- it 'tries to lock the table anyways, and logs the failure' do
- expect(logger).to receive(:warn).with(
- /Failed lock_writes, because #{test_table} raised an error. Error:/
+ context 'when table does not exist' do
+ let(:skip_table_creation) { true }
+ let(:test_table) { non_existent_table }
+
+ # In tests if we don't add the fake table to the gitlab schema then our test only schema checks will fail.
+ before do
+ allow(Gitlab::Database::GitlabSchema).to receive(:table_schema).and_call_original
+ allow(Gitlab::Database::GitlabSchema).to receive(:table_schema).with(
+ non_existent_table
+ ).and_return(:gitlab_main_cell)
+ end
+
+ it 'tries to lock the table anyways, and logs the failure' do
+ expect(logger).to receive(:warn).with(
+ /Failed lock_writes, because #{test_table} raised an error. Error:/
+ )
+ expect(connection).to receive(:execute).with(/CREATE OR REPLACE TRIGGER/)
+
+ expect do
+ result = subject.lock_writes
+ expect(result).to eq({ action: "skipped", database: "main", dry_run: false, table: test_table })
+ end.not_to change {
+ number_of_triggers_on(connection, test_table)
+ }
+ end
+ end
+ end
+
+ context 'when running in force mode false' do
+ let(:force) { false }
+
+ it 'skips the operation if the table is already locked for writes' do
+ subject.lock_writes
+
+ expect(logger).to receive(:info).with(
+ "Skipping lock_writes, because #{test_table} is already locked for writes"
)
- expect(connection).to receive(:execute).with(/CREATE OR REPLACE TRIGGER/)
+ expect(connection).not_to receive(:execute).with(/CREATE OR REPLACE TRIGGER/)
expect do
result = subject.lock_writes
@@ -163,6 +187,31 @@ RSpec.describe Gitlab::Database::LockWritesManager, :delete, feature_category: :
number_of_triggers_on(connection, test_table)
}
end
+
+ context 'when table does not exist' do
+ let(:skip_table_creation) { true }
+ let(:test_table) { non_existent_table }
+
+ # In tests if we don't add the fake table to the gitlab schema then our test only schema checks will fail.
+ before do
+ allow(Gitlab::Database::GitlabSchema).to receive(:table_schema).and_call_original
+ allow(Gitlab::Database::GitlabSchema).to receive(:table_schema).with(
+ non_existent_table
+ ).and_return(:gitlab_main_cell)
+ end
+
+ it 'skips locking table' do
+ expect(logger).to receive(:info).with("Skipping lock_writes, because #{test_table} does not exist")
+ expect(connection).not_to receive(:execute).with(/CREATE OR REPLACE TRIGGER/)
+
+ expect do
+ result = subject.lock_writes
+ expect(result).to eq({ action: "skipped", database: "main", dry_run: false, table: test_table })
+ end.not_to change {
+ number_of_triggers_on(connection, test_table)
+ }
+ end
+ end
end
context 'when running in dry_run mode' do
@@ -227,15 +276,6 @@ RSpec.describe Gitlab::Database::LockWritesManager, :delete, feature_category: :
end.not_to raise_error
end
- it 'idempotently drops the lock even if it does not exist' do
- subject.unlock_writes
-
- expect(subject).to receive(:execute_sql_statement)
- expect(subject.unlock_writes).to eq(
- { action: "unlocked", database: "main", dry_run: dry_run, table: test_table }
- )
- end
-
it 'removes the write protection triggers from the gitlab_main tables on the ci database' do
expect do
subject.unlock_writes
@@ -252,14 +292,54 @@ RSpec.describe Gitlab::Database::LockWritesManager, :delete, feature_category: :
subject.unlock_writes
end
- context 'when table does not exist' do
- let(:skip_table_creation) { true }
- let(:test_table) { non_existent_table }
+ context 'when running in force mode true' do
+ it 'idempotently drops the lock even if it does not exist' do
+ subject.unlock_writes
- it 'tries to unlock the table which postgres handles gracefully' do
- expect(subject).to receive(:execute_sql_statement).and_call_original
+ expect(subject).to receive(:execute_sql_statement)
+ expect(subject.unlock_writes).to eq(
+ { action: "unlocked", database: "main", dry_run: dry_run, table: test_table }
+ )
+ end
- expect(subject.unlock_writes).to eq({ action: "unlocked", database: "main", dry_run: false, table: test_table })
+ context 'when table does not exist' do
+ let(:skip_table_creation) { true }
+ let(:test_table) { non_existent_table }
+
+ it 'tries to unlock the table which postgres handles gracefully' do
+ expect(subject).to receive(:execute_sql_statement).and_call_original
+
+ expect(subject.unlock_writes).to eq(
+ { action: "unlocked", database: "main", dry_run: false, table: test_table }
+ )
+ end
+ end
+ end
+
+ context 'when running in force mode false' do
+ let(:force) { false }
+
+ it 'skips unlocking the table if the table was already unlocked for writes' do
+ subject.unlock_writes
+
+ expect(subject).not_to receive(:execute_sql_statement)
+ expect(subject.unlock_writes).to eq(
+ { action: "skipped", database: "main", dry_run: dry_run, table: test_table }
+ )
+ end
+
+ context 'when table does not exist' do
+ let(:skip_table_creation) { true }
+ let(:test_table) { non_existent_table }
+
+ it 'skips unlocking table' do
+ subject.unlock_writes
+
+ expect(subject).not_to receive(:execute_sql_statement)
+ expect(subject.unlock_writes).to eq(
+ { action: "skipped", database: "main", dry_run: dry_run, table: test_table }
+ )
+ end
end
end
diff --git a/spec/lib/gitlab/database/tables_locker_spec.rb b/spec/lib/gitlab/database/tables_locker_spec.rb
index 1d350afb7ae..4692d2bd768 100644
--- a/spec/lib/gitlab/database/tables_locker_spec.rb
+++ b/spec/lib/gitlab/database/tables_locker_spec.rb
@@ -88,7 +88,8 @@ RSpec.describe Gitlab::Database::TablesLocker, :suppress_gitlab_schemas_validate
database_name: database_name,
with_retries: true,
logger: anything,
- dry_run: anything
+ dry_run: anything,
+ force: true
).once.and_return(lock_writes_manager)
expect(lock_writes_manager).to receive(:lock_writes).once
end
@@ -121,7 +122,8 @@ RSpec.describe Gitlab::Database::TablesLocker, :suppress_gitlab_schemas_validate
database_name: database_name,
with_retries: true,
logger: anything,
- dry_run: anything
+ dry_run: anything,
+ force: true
).once.and_return(lock_writes_manager)
expect(lock_writes_manager).to receive(:unlock_writes)
end
@@ -146,7 +148,8 @@ RSpec.describe Gitlab::Database::TablesLocker, :suppress_gitlab_schemas_validate
database_name: database_name,
with_retries: true,
logger: anything,
- dry_run: anything
+ dry_run: anything,
+ force: true
).once.and_return(lock_writes_manager)
expect(lock_writes_manager).to receive(:lock_writes)
@@ -166,7 +169,8 @@ RSpec.describe Gitlab::Database::TablesLocker, :suppress_gitlab_schemas_validate
database_name: database_name,
with_retries: true,
logger: anything,
- dry_run: anything
+ dry_run: anything,
+ force: true
).once.and_return(lock_writes_manager)
expect(lock_writes_manager).to receive(:unlock_writes)
@@ -340,7 +344,8 @@ RSpec.describe Gitlab::Database::TablesLocker, :suppress_gitlab_schemas_validate
database_name: 'ci',
with_retries: true,
logger: anything,
- dry_run: true
+ dry_run: true,
+ force: false
).and_return(default_lock_writes_manager)
expect(default_lock_writes_manager).to receive(:lock_writes)
@@ -389,7 +394,8 @@ RSpec.describe Gitlab::Database::TablesLocker, :suppress_gitlab_schemas_validate
database_name: 'geo',
with_retries: true,
logger: anything,
- dry_run: anything
+ dry_run: anything,
+ force: true
)
subject
@@ -416,7 +422,8 @@ RSpec.describe Gitlab::Database::TablesLocker, :suppress_gitlab_schemas_validate
database_name: 'sec',
with_retries: true,
logger: anything,
- dry_run: anything
+ dry_run: anything,
+ force: true
)
subject
diff --git a/spec/services/users/assigned_issues_count_service_spec.rb b/spec/services/users/assigned_issues_count_service_spec.rb
deleted file mode 100644
index 4d014c6441b..00000000000
--- a/spec/services/users/assigned_issues_count_service_spec.rb
+++ /dev/null
@@ -1,57 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Users::AssignedIssuesCountService, :use_clean_rails_memory_store_caching,
- feature_category: :team_planning do
- let_it_be(:user) { create(:user) }
- let_it_be(:max_limit) { 10 }
-
- let(:current_user) { user }
-
- subject { described_class.new(current_user: current_user, max_limit: max_limit) }
-
- it_behaves_like 'a counter caching service'
-
- context 'when user has assigned open issues from archived and closed projects' do
- before do
- project = create(:project, :public)
- archived_project = create(:project, :public, :archived)
-
- create(:issue, project: project, author: user, assignees: [user])
- create(:issue, :closed, project: project, author: user, assignees: [user])
- create(:issue, project: archived_project, author: user, assignees: [user])
- end
-
- it 'count all assigned open issues excluding those from closed or archived projects' do
- expect(subject.count).to eq(1)
- end
- end
-
- context 'when the number of assigned open issues exceeds max_limit' do
- let_it_be(:banned_user) { create(:user, :banned) }
- let_it_be(:project) { create(:project, developers: user) }
-
- context 'when user is admin', :enable_admin_mode do
- let_it_be(:admin) { create(:admin) }
- let_it_be(:issues) { create_list(:issue, max_limit + 1, project: project, assignees: [admin]) }
- let_it_be(:banned_issue) { create(:issue, project: project, assignees: [admin], author: banned_user) }
-
- let(:current_user) { admin }
-
- it 'returns the max_limit count' do
- expect(subject.count).to eq max_limit
- end
- end
-
- context 'when user is non-admin' do
- let_it_be(:issues) { create_list(:issue, max_limit + 1, project: project, assignees: [user]) }
- let_it_be(:closed_issue) { create(:issue, :closed, project: project, assignees: [user]) }
- let_it_be(:banned_issue) { create(:issue, project: project, assignees: [user], author: banned_user) }
-
- it 'returns the max_limit count' do
- expect(subject.count).to eq max_limit
- end
- end
- end
-end
diff --git a/spec/workers/database/lock_tables_worker_spec.rb b/spec/workers/database/lock_tables_worker_spec.rb
index 33531b2d36f..49c102a9be2 100644
--- a/spec/workers/database/lock_tables_worker_spec.rb
+++ b/spec/workers/database/lock_tables_worker_spec.rb
@@ -93,7 +93,7 @@ RSpec.describe Database::LockTablesWorker, feature_category: :cell do
end
with_them do
- it 'tries to lock the table anyways, and logs the failure' do
+ it 'returns as locked for the previously locked tables on the corresponding database' do
tables.each do |table_name|
lock_table(database_name, table_name)
end