Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
31de60f0ac
commit
074caf94a9
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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 }}
|
||||
</gl-alert>
|
||||
|
||||
<live-block :is-loading="!newestDeployment && $apollo.loading" :deployment="newestDeployment" />
|
||||
|
||||
<crud-component
|
||||
:title="$options.i18n.title"
|
||||
:is-loading="!primaryDeployments && $apollo.loading"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,100 @@
|
|||
<script>
|
||||
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}`,
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<crud-component header-class="gl-rounded-lg gl-border-b-0 gl-gap-y-3" body-class="gl-hidden">
|
||||
<template v-if="isLoading" #title>
|
||||
<gl-skeleton-loader v-if="isLoading" :width="400" :lines="2" />
|
||||
</template>
|
||||
<template v-else #title>
|
||||
<span data-testid="live-heading">
|
||||
{{ $options.i18n.liveSite }}
|
||||
<a
|
||||
v-if="deployment.active"
|
||||
:href="domainName"
|
||||
target="_blank"
|
||||
data-testid="live-heading-link"
|
||||
>
|
||||
{{ domainName }}
|
||||
</a>
|
||||
<span v-else class="gl-text-subtle">
|
||||
{{ domainName }}
|
||||
</span>
|
||||
🎉
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<template v-if="!isLoading" #description>
|
||||
{{ $options.i18n.deployJobLabel }}
|
||||
<a :href="ciBuildUrl" data-testid="deploy-job-number">{{ deployment.ciBuildId }}</a>
|
||||
|
||||
<template v-if="deployment.updatedAt">
|
||||
<span aria-hidden="true">·</span>
|
||||
{{ $options.i18n.lastUpdatedLabel }}
|
||||
<time-ago :time="deployment.updatedAt" data-testid="last-updated-date" />
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<template v-if="!isLoading" #actions>
|
||||
<gl-button
|
||||
icon="external-link"
|
||||
:href="domainName"
|
||||
target="_blank"
|
||||
data-testid="visit-site-url"
|
||||
>
|
||||
{{ $options.i18n.buttonLabel }}
|
||||
</gl-button>
|
||||
</template>
|
||||
</crud-component>
|
||||
</template>
|
||||
|
|
@ -27,6 +27,7 @@ export default function initPages() {
|
|||
apolloProvider,
|
||||
provide: {
|
||||
projectFullPath: el.dataset.fullPath,
|
||||
primaryDomain: el.dataset.primaryDomain,
|
||||
},
|
||||
render(createElement) {
|
||||
return createElement(PagesEdit, {});
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
|
|
@ -65,7 +60,7 @@ export default {
|
|||
:data-group-path="group"
|
||||
>
|
||||
<gl-intersperse separator="">
|
||||
<span>{{ truncateText(data.title) }}</span>
|
||||
<span>{{ data.title }}</span>
|
||||
<span> ({{ data.reference }}</span>
|
||||
<span v-if="data.state === 'closed'"> - {{ __('closed') }}</span>
|
||||
<span v-if="data.state === 'merged'"> - {{ __('merged') }}</span>
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
@function more-than($z-index) {
|
||||
@return $z-index + 1;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
|
|
|
|||
|
|
@ -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 `<user-callout-dismisser>` wrapper, that integrates with GraphQL API to simplify dismissing and checking the dismissed state of a callout. Here's an example usage:
|
||||
|
||||
```vue
|
||||
<user-callout-dismisser feature-name="my_user_callout">
|
||||
<template #default="{ dismiss, shouldShowCallout }">
|
||||
<my-callout-component
|
||||
v-if="shouldShowCallout"
|
||||
@close="dismiss"
|
||||
/>
|
||||
</template>
|
||||
</user-callout-dismisser>
|
||||
```
|
||||
|
||||
See `app/assets/javascripts/vue_shared/components/user_callout_dismisser.vue` for more details.
|
||||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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 plugin<sup><b><a href="#spotbugs-footnote">1</a></b></sup> |
|
||||
| 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) |
|
||||
| YAML<sup><b><a href="#yaml-footnote">2</a></b></sup> | {{< 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 plugin<sup><b><a href="#spotbugs-footnote">1</a></b></sup> |
|
||||
| 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) |
|
||||
| YAML<sup><b><a href="#yaml-footnote">2</a></b></sup> | {{< 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
|
||||
|
||||
|
|
|
|||
|
|
@ -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?('.')
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 ""
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
#
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -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' } });
|
||||
|
||||
|
|
|
|||
|
|
@ -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 () => {
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
|
||||
|
|
|
|||
|
|
@ -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') }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue