Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-01-26 18:12:36 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-01-26 18:12:36 +0300
commit2004f56282a00ac54f9ebc546bc828a1450e29ed (patch)
treecfa53443533539b4a1964785e7a10c5e5ee03d53
parent613a8bc141f0d9f984b34274f926df06f48e0133 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.rubocop_todo/rails/save_bang.yml12
-rw-r--r--.rubocop_todo/style/open_struct_use.yml1
-rw-r--r--app/assets/javascripts/cycle_analytics/components/metric_tile.vue51
-rw-r--r--app/assets/javascripts/cycle_analytics/components/value_stream_metrics.vue26
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipeline_triggerer.vue2
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipeline_url.vue147
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipelines_status_badge.vue9
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue25
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/time_ago.vue5
-rw-r--r--app/controllers/projects/merge_requests_controller.rb1
-rw-r--r--app/controllers/projects/pipelines_controller.rb3
-rw-r--r--config/feature_flags/development/rearrange_pipelines_table.yml8
-rw-r--r--db/post_migrate/20220125083520_remove_ci_pipelines_dast_site_profiles_pipelines_ci_pipeline_id_fk.rb19
-rw-r--r--db/post_migrate/20220125084348_remove_ci_pipelines_vulnerability_feedback_pipeline_id_fk.rb19
-rw-r--r--db/schema_migrations/202201250835201
-rw-r--r--db/schema_migrations/202201250843481
-rw-r--r--db/structure.sql6
-rw-r--r--doc/administration/feature_flags.md23
-rw-r--r--doc/administration/geo/index.md1
-rw-r--r--doc/administration/operations/fast_ssh_key_lookup.md19
-rw-r--r--doc/administration/reference_architectures/10k_users.md4
-rw-r--r--doc/administration/reference_architectures/1k_users.md1
-rw-r--r--doc/administration/reference_architectures/25k_users.md4
-rw-r--r--doc/administration/reference_architectures/2k_users.md4
-rw-r--r--doc/administration/reference_architectures/3k_users.md4
-rw-r--r--doc/administration/reference_architectures/50k_users.md4
-rw-r--r--doc/administration/reference_architectures/5k_users.md4
-rw-r--r--doc/administration/reference_architectures/index.md227
-rw-r--r--doc/administration/troubleshooting/gitlab_rails_cheat_sheet.md15
-rw-r--r--doc/api/jobs.md88
-rw-r--r--doc/ci/pipelines/settings.md4
-rw-r--r--doc/install/requirements.md4
-rw-r--r--doc/raketasks/backup_restore.md1
-rw-r--r--doc/security/index.md2
-rw-r--r--doc/update/upgrading_from_source.md4
-rw-r--r--doc/user/admin_area/reporting/spamcheck.md65
-rw-r--r--doc/user/admin_area/settings/index.md2
-rw-r--r--doc/user/clusters/agent/ci_cd_tunnel.md2
-rw-r--r--lib/api/helpers.rb2
-rw-r--r--lib/gitlab/database/gitlab_loose_foreign_keys.yml3
-rw-r--r--lib/gitlab/database/loose_foreign_keys.rb6
-rw-r--r--qa/qa/page/project/pipeline/index.rb4
-rw-r--r--spec/features/cycle_analytics_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_pipelines_spec.rb1
-rw-r--r--spec/features/projects/pipelines/pipelines_spec.rb111
-rw-r--r--spec/frontend/cycle_analytics/metric_tile_spec.js81
-rw-r--r--spec/frontend/cycle_analytics/value_stream_metrics_spec.js41
-rw-r--r--spec/frontend/pipelines/mock_data.js77
-rw-r--r--spec/frontend/pipelines/pipeline_url_spec.js297
-rw-r--r--spec/frontend/pipelines/pipelines_table_spec.js34
-rw-r--r--spec/lib/gitlab/database/loose_foreign_keys_spec.rb30
-rw-r--r--spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/fork_spec.rb4
-rw-r--r--spec/lib/gitlab/import_export/group/legacy_tree_saver_spec.rb4
-rw-r--r--spec/lib/gitlab/import_export/group/relation_factory_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/group/tree_saver_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/repo_restorer_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/saver_spec.rb8
-rw-r--r--spec/lib/gitlab/import_export/snippet_repo_restorer_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/snippet_repo_saver_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/snippets_repo_restorer_spec.rb4
-rw-r--r--spec/lib/gitlab/import_export/snippets_repo_saver_spec.rb6
-rw-r--r--spec/lib/gitlab/import_export/uploads_manager_spec.rb10
-rw-r--r--spec/requests/api/usage_data_spec.rb1
-rw-r--r--spec/services/packages/nuget/metadata_extraction_service_spec.rb4
-rw-r--r--tooling/lib/tooling/parallel_rspec_runner.rb6
67 files changed, 1213 insertions, 355 deletions
diff --git a/.rubocop_todo/rails/save_bang.yml b/.rubocop_todo/rails/save_bang.yml
index ff9b0c9bbc8..b6133edc390 100644
--- a/.rubocop_todo/rails/save_bang.yml
+++ b/.rubocop_todo/rails/save_bang.yml
@@ -15,11 +15,6 @@ Rails/SaveBang:
- spec/lib/gitlab/database/custom_structure_spec.rb
- spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb
- spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb
- - spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb
- - spec/lib/gitlab/import_export/fork_spec.rb
- - spec/lib/gitlab/import_export/group/legacy_tree_saver_spec.rb
- - spec/lib/gitlab/import_export/group/relation_factory_spec.rb
- - spec/lib/gitlab/import_export/group/tree_saver_spec.rb
- spec/lib/gitlab/import_export/importer_spec.rb
- spec/lib/gitlab/import_export/lfs_restorer_spec.rb
- spec/lib/gitlab/import_export/lfs_saver_spec.rb
@@ -27,10 +22,3 @@ Rails/SaveBang:
- spec/lib/gitlab/import_export/project/relation_factory_spec.rb
- spec/lib/gitlab/import_export/project/tree_restorer_spec.rb
- spec/lib/gitlab/import_export/project/tree_saver_spec.rb
- - spec/lib/gitlab/import_export/repo_restorer_spec.rb
- - spec/lib/gitlab/import_export/saver_spec.rb
- - spec/lib/gitlab/import_export/snippet_repo_restorer_spec.rb
- - spec/lib/gitlab/import_export/snippet_repo_saver_spec.rb
- - spec/lib/gitlab/import_export/snippets_repo_restorer_spec.rb
- - spec/lib/gitlab/import_export/snippets_repo_saver_spec.rb
- - spec/lib/gitlab/import_export/uploads_manager_spec.rb
diff --git a/.rubocop_todo/style/open_struct_use.yml b/.rubocop_todo/style/open_struct_use.yml
index ed4a53f2894..31485f51722 100644
--- a/.rubocop_todo/style/open_struct_use.yml
+++ b/.rubocop_todo/style/open_struct_use.yml
@@ -32,7 +32,6 @@ Style/OpenStructUse:
- spec/lib/gitlab/quick_actions/command_definition_spec.rb
- spec/models/design_management/design_action_spec.rb
- spec/models/design_management/design_at_version_spec.rb
- - spec/services/packages/nuget/metadata_extraction_service_spec.rb
- spec/services/projects/import_service_spec.rb
- spec/services/system_note_service_spec.rb
- spec/support/helpers/import_spec_helper.rb
diff --git a/app/assets/javascripts/cycle_analytics/components/metric_tile.vue b/app/assets/javascripts/cycle_analytics/components/metric_tile.vue
new file mode 100644
index 00000000000..845a3386f6c
--- /dev/null
+++ b/app/assets/javascripts/cycle_analytics/components/metric_tile.vue
@@ -0,0 +1,51 @@
+<script>
+import { GlSingleStat } from '@gitlab/ui/dist/charts';
+import { redirectTo } from '~/lib/utils/url_utility';
+import MetricPopover from './metric_popover.vue';
+
+export default {
+ name: 'MetricTile',
+ components: {
+ GlSingleStat,
+ MetricPopover,
+ },
+ props: {
+ metric: {
+ type: Object,
+ required: true,
+ },
+ },
+ computed: {
+ decimalPlaces() {
+ const parsedFloat = parseFloat(this.metric.value);
+ return Number.isNaN(parsedFloat) || Number.isInteger(parsedFloat) ? 0 : 1;
+ },
+ hasLinks() {
+ return this.metric.links?.length && this.metric.links[0].url;
+ },
+ },
+ methods: {
+ clickHandler({ links }) {
+ if (this.hasLinks) {
+ redirectTo(links[0].url);
+ }
+ },
+ },
+};
+</script>
+<template>
+ <div v-bind="$attrs">
+ <gl-single-stat
+ :id="metric.identifier"
+ :value="`${metric.value}`"
+ :title="metric.label"
+ :unit="metric.unit || ''"
+ :should-animate="true"
+ :animation-decimal-places="decimalPlaces"
+ :class="{ 'gl-hover-cursor-pointer': hasLinks }"
+ tabindex="0"
+ @click="clickHandler(metric)"
+ />
+ <metric-popover :metric="metric" :target="metric.identifier" />
+ </div>
+</template>
diff --git a/app/assets/javascripts/cycle_analytics/components/value_stream_metrics.vue b/app/assets/javascripts/cycle_analytics/components/value_stream_metrics.vue
index 16446c61102..abaf7fe273e 100644
--- a/app/assets/javascripts/cycle_analytics/components/value_stream_metrics.vue
+++ b/app/assets/javascripts/cycle_analytics/components/value_stream_metrics.vue
@@ -1,13 +1,12 @@
<script>
import { GlDeprecatedSkeletonLoading as GlSkeletonLoading } from '@gitlab/ui';
-import { GlSingleStat } from '@gitlab/ui/dist/charts';
import { flatten } from 'lodash';
import createFlash from '~/flash';
import { sprintf, s__ } from '~/locale';
import { redirectTo } from '~/lib/utils/url_utility';
import { METRICS_POPOVER_CONTENT } from '../constants';
import { removeFlash, prepareTimeMetricsData } from '../utils';
-import MetricPopover from './metric_popover.vue';
+import MetricTile from './metric_tile.vue';
const requestData = ({ request, endpoint, path, params, name }) => {
return request({ endpoint, params, requestPath: path })
@@ -33,9 +32,8 @@ const fetchMetricsData = (reqs = [], path, params) => {
export default {
name: 'ValueStreamMetrics',
components: {
- GlSingleStat,
GlSkeletonLoading,
- MetricPopover,
+ MetricTile,
},
props: {
requestPath: {
@@ -94,26 +92,14 @@ export default {
};
</script>
<template>
- <div class="gl-display-flex gl-flex-wrap" data-testid="vsa-time-metrics">
+ <div class="gl-display-flex gl-flex-wrap" data-testid="vsa-metrics">
<gl-skeleton-loading v-if="isLoading" class="gl-h-auto gl-py-3 gl-pr-9 gl-my-6" />
- <div
+ <metric-tile
v-for="metric in metrics"
v-show="!isLoading"
:key="metric.identifier"
+ :metric="metric"
class="gl-my-6 gl-pr-9"
- >
- <gl-single-stat
- :id="metric.identifier"
- :value="`${metric.value}`"
- :title="metric.label"
- :unit="metric.unit || ''"
- :should-animate="true"
- :animation-decimal-places="getDecimalPlaces(metric.value)"
- :class="{ 'gl-hover-cursor-pointer': hasLinks(metric.links) }"
- tabindex="0"
- @click="clickHandler(metric)"
- />
- <metric-popover :metric="metric" :target="metric.identifier" />
- </div>
+ />
</div>
</template>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_triggerer.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_triggerer.vue
index 0528e4c147c..b29c8426301 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_triggerer.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_triggerer.vue
@@ -26,7 +26,7 @@ export default {
v-if="user"
:link-href="user.path"
:img-src="user.avatar_url"
- :img-size="26"
+ :img-size="32"
:tooltip-text="user.name"
class="gl-ml-3 js-pipeline-url-user"
/>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_url.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_url.vue
index e2f30d5a8e6..d5a5c7e7b71 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_url.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_url.vue
@@ -1,15 +1,19 @@
<script>
-import { GlLink, GlPopover, GlSprintf, GlTooltipDirective, GlBadge } from '@gitlab/ui';
+import { GlIcon, GlLink, GlPopover, GlSprintf, GlTooltipDirective, GlBadge } from '@gitlab/ui';
+import { __, sprintf } from '~/locale';
import { helpPagePath } from '~/helpers/help_page_helper';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue';
import { SCHEDULE_ORIGIN } from '../../constants';
export default {
components: {
+ GlIcon,
GlLink,
GlPopover,
GlSprintf,
GlBadge,
+ TooltipOnTruncate,
},
directives: {
GlTooltip: GlTooltipDirective,
@@ -33,11 +37,12 @@ export default {
type: String,
required: true,
},
+ viewType: {
+ type: String,
+ required: true,
+ },
},
computed: {
- user() {
- return this.pipeline.user;
- },
isScheduled() {
return this.pipeline.source === SCHEDULE_ORIGIN;
},
@@ -53,12 +58,144 @@ export default {
autoDevopsHelpPath() {
return helpPagePath('topics/autodevops/index.md');
},
+ mergeRequestRef() {
+ return this.pipeline?.merge_request;
+ },
+ commitRef() {
+ return this.pipeline?.ref;
+ },
+ commitTag() {
+ return this.commitRef?.tag;
+ },
+ commitUrl() {
+ return this.pipeline?.commit?.commit_path;
+ },
+ commitShortSha() {
+ return this.pipeline?.commit?.short_id;
+ },
+ refUrl() {
+ return this.commitRef?.ref_url || this.commitRef?.path;
+ },
+ tooltipTitle() {
+ return this.mergeRequestRef?.title || this.commitRef?.name;
+ },
+ commitAuthor() {
+ let commitAuthorInformation;
+ const pipelineCommit = this.pipeline?.commit;
+ const pipelineCommitAuthor = pipelineCommit?.author;
+
+ if (!pipelineCommit) {
+ return null;
+ }
+
+ // 1. person who is an author of a commit might be a GitLab user
+ if (pipelineCommitAuthor) {
+ // 2. if person who is an author of a commit is a GitLab user
+ // they can have a GitLab avatar
+ if (pipelineCommitAuthor?.avatar_url) {
+ commitAuthorInformation = pipelineCommitAuthor;
+
+ // 3. If GitLab user does not have avatar, they might have a Gravatar
+ } else if (pipelineCommit.author_gravatar_url) {
+ commitAuthorInformation = {
+ ...pipelineCommitAuthor,
+ avatar_url: pipelineCommit.author_gravatar_url,
+ };
+ }
+ // 4. If committer is not a GitLab User, they can have a Gravatar
+ } else {
+ commitAuthorInformation = {
+ avatar_url: pipelineCommit.author_gravatar_url,
+ path: `mailto:${pipelineCommit.author_email}`,
+ username: pipelineCommit.author_name,
+ };
+ }
+
+ return commitAuthorInformation;
+ },
+ commitIcon() {
+ let name = '';
+
+ if (this.commitTag) {
+ name = 'tag';
+ } else if (this.mergeRequestRef) {
+ name = 'git-merge';
+ } else {
+ name = 'branch';
+ }
+
+ return name;
+ },
+ commitTitle() {
+ return this.pipeline?.commit?.title;
+ },
+ hasAuthor() {
+ return (
+ this.commitAuthor?.avatar_url && this.commitAuthor?.path && this.commitAuthor?.username
+ );
+ },
+ userImageAltDescription() {
+ return this.commitAuthor?.username
+ ? sprintf(__("%{username}'s avatar"), { username: this.commitAuthor.username })
+ : null;
+ },
+ rearrangePipelinesTable() {
+ return this.glFeatures?.rearrangePipelinesTable;
+ },
},
};
</script>
<template>
<div class="pipeline-tags" data-testid="pipeline-url-table-cell">
+ <template v-if="rearrangePipelinesTable">
+ <div class="commit-title gl-mb-2" data-testid="commit-title-container">
+ <span v-if="commitTitle" class="gl-display-flex">
+ <tooltip-on-truncate :title="commitTitle" class="flex-truncate-child gl-flex-grow-1">
+ <gl-link
+ :href="commitUrl"
+ class="commit-row-message gl-text-gray-900"
+ data-testid="commit-title"
+ >{{ commitTitle }}</gl-link
+ >
+ </tooltip-on-truncate>
+ </span>
+ <span v-else>{{ __("Can't find HEAD commit for this branch") }}</span>
+ </div>
+ <div class="gl-mb-2">
+ <gl-link
+ :href="pipeline.path"
+ class="gl-text-decoration-underline gl-text-blue-600!"
+ data-testid="pipeline-url-link"
+ data-qa-selector="pipeline_url_link"
+ >
+ #{{ pipeline[pipelineKey] }}
+ </gl-link>
+ <!--Commit row-->
+ <div class="icon-container gl-display-inline-block">
+ <gl-icon :name="commitIcon" />
+ </div>
+ <tooltip-on-truncate :title="tooltipTitle" truncate-target="child" placement="top">
+ <gl-link
+ v-if="mergeRequestRef"
+ :href="mergeRequestRef.path"
+ class="ref-name"
+ data-testid="merge-request-ref"
+ >{{ mergeRequestRef.iid }}</gl-link
+ >
+ <gl-link v-else :href="refUrl" class="ref-name" data-testid="commit-ref-name">{{
+ commitRef.name
+ }}</gl-link>
+ </tooltip-on-truncate>
+ <gl-icon name="commit" class="commit-icon" />
+
+ <gl-link :href="commitUrl" class="commit-sha mr-0" data-testid="commit-short-sha">{{
+ commitShortSha
+ }}</gl-link>
+ <!--End of commit row-->
+ </div>
+ </template>
<gl-link
+ v-if="!rearrangePipelinesTable"
:href="pipeline.path"
class="gl-text-decoration-underline"
data-testid="pipeline-url-link"
@@ -66,7 +203,7 @@ export default {
>
#{{ pipeline[pipelineKey] }}
</gl-link>
- <div class="label-container">
+ <div class="label-container gl-mt-1">
<gl-badge
v-if="isScheduled"
v-gl-tooltip
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_status_badge.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_status_badge.vue
index f56457a4162..54901c2d13f 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_status_badge.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_status_badge.vue
@@ -3,12 +3,16 @@ import CodeQualityWalkthrough from '~/code_quality_walkthrough/components/step.v
import { PIPELINE_STATUSES } from '~/code_quality_walkthrough/constants';
import { CHILD_VIEW } from '~/pipelines/constants';
import CiBadge from '~/vue_shared/components/ci_badge_link.vue';
+import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+import PipelinesTimeago from './time_ago.vue';
export default {
components: {
CodeQualityWalkthrough,
CiBadge,
+ PipelinesTimeago,
},
+ mixins: [glFeatureFlagsMixin()],
props: {
pipeline: {
type: Object,
@@ -40,6 +44,9 @@ export default {
codeQualityBuildPath() {
return this.pipeline?.details?.code_quality_build_path;
},
+ rearrangePipelinesTable() {
+ return this.glFeatures?.rearrangePipelinesTable;
+ },
},
};
</script>
@@ -48,11 +55,13 @@ export default {
<div>
<ci-badge
id="js-code-quality-walkthrough"
+ class="gl-mb-3"
:status="pipelineStatus"
:show-text="!isChildView"
:icon-classes="'gl-vertical-align-middle!'"
data-qa-selector="pipeline_commit_status"
/>
+ <pipelines-timeago v-if="rearrangePipelinesTable" class="gl-mt-3" :pipeline="pipeline" />
<code-quality-walkthrough
v-if="shouldRenderCodeQualityWalkthrough"
:step="codeQualityStep"
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue
index d64decc81ec..9919a18cb99 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue
@@ -1,6 +1,7 @@
<script>
import { GlTableLite, GlTooltipDirective } from '@gitlab/ui';
-import { s__ } from '~/locale';
+import { s__, __ } from '~/locale';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import eventHub from '../../event_hub';
import PipelineMiniGraph from './pipeline_mini_graph.vue';
import PipelineOperations from './pipeline_operations.vue';
@@ -33,6 +34,7 @@ export default {
directives: {
GlTooltip: GlTooltipDirective,
},
+ mixins: [glFeatureFlagMixin()],
props: {
pipelines: {
type: Array,
@@ -72,16 +74,18 @@ export default {
key: 'status',
label: s__('Pipeline|Status'),
thClass: DEFAULT_TH_CLASSES,
- columnClass: 'gl-w-10p',
+ columnClass: this.rearrangePipelinesTable ? 'gl-w-15p' : 'gl-w-10p',
tdClass: DEFAULT_TD_CLASS,
thAttr: { 'data-testid': 'status-th' },
},
{
key: 'pipeline',
- label: this.pipelineKeyOption.label,
+ label: this.rearrangePipelinesTable ? __('Pipeline') : this.pipelineKeyOption.label,
thClass: DEFAULT_TH_CLASSES,
- tdClass: `${DEFAULT_TD_CLASS} ${HIDE_TD_ON_MOBILE}`,
- columnClass: 'gl-w-10p',
+ tdClass: this.rearrangePipelinesTable
+ ? `${DEFAULT_TD_CLASS}`
+ : `${DEFAULT_TD_CLASS} ${HIDE_TD_ON_MOBILE}`,
+ columnClass: this.rearrangePipelinesTable ? 'gl-w-30p' : 'gl-w-10p',
thAttr: { 'data-testid': 'pipeline-th' },
},
{
@@ -113,7 +117,7 @@ export default {
label: s__('Pipeline|Duration'),
thClass: DEFAULT_TH_CLASSES,
tdClass: DEFAULT_TD_CLASS,
- columnClass: 'gl-w-15p',
+ columnClass: this.rearrangePipelinesTable ? 'gl-w-5p' : 'gl-w-15p',
thAttr: { 'data-testid': 'timeago-th' },
},
{
@@ -124,7 +128,13 @@ export default {
thAttr: { 'data-testid': 'actions-th' },
},
];
- return fields;
+
+ return !this.rearrangePipelinesTable
+ ? fields
+ : fields.filter((field) => !['commit', 'timeago'].includes(field.key));
+ },
+ rearrangePipelinesTable() {
+ return this.glFeatures?.rearrangePipelinesTable;
},
},
watch: {
@@ -182,6 +192,7 @@ export default {
:pipeline="item"
:pipeline-schedule-url="pipelineScheduleUrl"
:pipeline-key="pipelineKeyOption.key"
+ :view-type="viewType"
/>
</template>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/time_ago.vue b/app/assets/javascripts/pipelines/components/pipelines_list/time_ago.vue
index e6b03751350..695491798a3 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/time_ago.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/time_ago.vue
@@ -54,11 +54,14 @@ export default {
showSkipped() {
return !this.duration && !this.finishedTime && this.skipped;
},
+ shouldDisplayAsBlock() {
+ return this.glFeatures?.rearrangePipelinesTable;
+ },
},
};
</script>
<template>
- <div>
+ <div class="{ 'gl-display-block': shouldDisplayAsBlock }">
<span v-if="showInProgress" data-testid="pipeline-in-progress">
<gl-icon v-if="stuck" name="warning" class="gl-mr-2" :size="12" data-testid="warning-icon" />
<gl-icon
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 273277aa603..b6d5ce6e393 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -43,6 +43,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
push_frontend_feature_flag(:mr_attention_requests, project, default_enabled: :yaml)
push_frontend_feature_flag(:refactor_mr_widgets_extensions, @project, default_enabled: :yaml)
push_frontend_feature_flag(:rebase_without_ci_ui, @project, default_enabled: :yaml)
+ push_frontend_feature_flag(:rearrange_pipelines_table, project, default_enabled: :yaml)
# Usage data feature flags
push_frontend_feature_flag(:users_expanding_widgets_usage_data, @project, default_enabled: :yaml)
push_frontend_feature_flag(:diff_settings_usage_data, default_enabled: :yaml)
diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb
index 71dc67bb6dc..7f680bbf121 100644
--- a/app/controllers/projects/pipelines_controller.rb
+++ b/app/controllers/projects/pipelines_controller.rb
@@ -13,6 +13,9 @@ class Projects::PipelinesController < Projects::ApplicationController
before_action :authorize_create_pipeline!, only: [:new, :create, :config_variables]
before_action :authorize_update_pipeline!, only: [:retry, :cancel]
before_action :ensure_pipeline, only: [:show, :downloadable_artifacts]
+ before_action do
+ push_frontend_feature_flag(:rearrange_pipelines_table, project, default_enabled: :yaml)
+ end
before_action do
push_frontend_feature_flag(:jobs_tab_vue, @project, default_enabled: :yaml)
diff --git a/config/feature_flags/development/rearrange_pipelines_table.yml b/config/feature_flags/development/rearrange_pipelines_table.yml
new file mode 100644
index 00000000000..aba990d891f
--- /dev/null
+++ b/config/feature_flags/development/rearrange_pipelines_table.yml
@@ -0,0 +1,8 @@
+---
+name: rearrange_pipelines_table
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/72545
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/343286
+milestone: '14.8'
+type: development
+group: group::pipeline execution
+default_enabled: false
diff --git a/db/post_migrate/20220125083520_remove_ci_pipelines_dast_site_profiles_pipelines_ci_pipeline_id_fk.rb b/db/post_migrate/20220125083520_remove_ci_pipelines_dast_site_profiles_pipelines_ci_pipeline_id_fk.rb
new file mode 100644
index 00000000000..b111b95fccf
--- /dev/null
+++ b/db/post_migrate/20220125083520_remove_ci_pipelines_dast_site_profiles_pipelines_ci_pipeline_id_fk.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class RemoveCiPipelinesDastSiteProfilesPipelinesCiPipelineIdFk < Gitlab::Database::Migration[1.0]
+ disable_ddl_transaction!
+
+ def up
+ return unless foreign_key_exists?(:dast_site_profiles_pipelines, :ci_pipelines, name: "fk_53849b0ad5")
+
+ with_lock_retries do
+ execute('LOCK ci_pipelines, dast_site_profiles_pipelines IN ACCESS EXCLUSIVE MODE') if transaction_open?
+
+ remove_foreign_key_if_exists(:dast_site_profiles_pipelines, :ci_pipelines, name: "fk_53849b0ad5")
+ end
+ end
+
+ def down
+ add_concurrent_foreign_key(:dast_site_profiles_pipelines, :ci_pipelines, name: "fk_53849b0ad5", column: :ci_pipeline_id, target_column: :id, on_delete: :cascade)
+ end
+end
diff --git a/db/post_migrate/20220125084348_remove_ci_pipelines_vulnerability_feedback_pipeline_id_fk.rb b/db/post_migrate/20220125084348_remove_ci_pipelines_vulnerability_feedback_pipeline_id_fk.rb
new file mode 100644
index 00000000000..1b932d084bb
--- /dev/null
+++ b/db/post_migrate/20220125084348_remove_ci_pipelines_vulnerability_feedback_pipeline_id_fk.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class RemoveCiPipelinesVulnerabilityFeedbackPipelineIdFk < Gitlab::Database::Migration[1.0]
+ disable_ddl_transaction!
+
+ def up
+ return unless foreign_key_exists?(:vulnerability_feedback, :ci_pipelines, name: "fk_rails_20976e6fd9")
+
+ with_lock_retries do
+ execute('LOCK ci_pipelines, vulnerability_feedback IN ACCESS EXCLUSIVE MODE') if transaction_open?
+
+ remove_foreign_key_if_exists(:vulnerability_feedback, :ci_pipelines, name: "fk_rails_20976e6fd9")
+ end
+ end
+
+ def down
+ add_concurrent_foreign_key(:vulnerability_feedback, :ci_pipelines, name: "fk_rails_20976e6fd9", column: :pipeline_id, target_column: :id, on_delete: :nullify)
+ end
+end
diff --git a/db/schema_migrations/20220125083520 b/db/schema_migrations/20220125083520
new file mode 100644
index 00000000000..c3fbb6beda5
--- /dev/null
+++ b/db/schema_migrations/20220125083520
@@ -0,0 +1 @@
+2c3f7c587b2a20de1d8581584f7392fd81643af4eb7e25ffc8e08514b6ad83ab \ No newline at end of file
diff --git a/db/schema_migrations/20220125084348 b/db/schema_migrations/20220125084348
new file mode 100644
index 00000000000..9d4cbd7c5ed
--- /dev/null
+++ b/db/schema_migrations/20220125084348
@@ -0,0 +1 @@
+75eb050fc789eb5775a5d3a88c2573dca5c38e16b63cd342bf46dca55d1adaef \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index e02b257ddfa..83c94ffbc84 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -29390,9 +29390,6 @@ ALTER TABLE ONLY alert_management_alerts
ALTER TABLE ONLY path_locks
ADD CONSTRAINT fk_5265c98f24 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
-ALTER TABLE ONLY dast_site_profiles_pipelines
- ADD CONSTRAINT fk_53849b0ad5 FOREIGN KEY (ci_pipeline_id) REFERENCES ci_pipelines(id) ON DELETE CASCADE;
-
ALTER TABLE ONLY clusters_applications_prometheus
ADD CONSTRAINT fk_557e773639 FOREIGN KEY (cluster_id) REFERENCES clusters(id) ON DELETE CASCADE;
@@ -30200,9 +30197,6 @@ ALTER TABLE ONLY boards_epic_lists
ALTER TABLE ONLY approval_merge_request_rules_groups
ADD CONSTRAINT fk_rails_2020a7124a FOREIGN KEY (group_id) REFERENCES namespaces(id) ON DELETE CASCADE;
-ALTER TABLE ONLY vulnerability_feedback
- ADD CONSTRAINT fk_rails_20976e6fd9 FOREIGN KEY (pipeline_id) REFERENCES ci_pipelines(id) ON DELETE SET NULL;
-
ALTER TABLE ONLY work_item_types
ADD CONSTRAINT fk_rails_20f694a960 FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE;
diff --git a/doc/administration/feature_flags.md b/doc/administration/feature_flags.md
index 97875532f5d..85a7304b5e8 100644
--- a/doc/administration/feature_flags.md
+++ b/doc/administration/feature_flags.md
@@ -36,7 +36,7 @@ For example, data is not recorded and services do not run.
If you used a certain feature and identified a bug, a misbehavior, or an
error, it's very important that you [**provide feedback**](https://gitlab.com/gitlab-org/gitlab/-/issues/new?issue[title]=Docs%20-%20feature%20flag%20feedback%3A%20Feature%20Name&issue[description]=Describe%20the%20problem%20you%27ve%20encountered.%0A%0A%3C!--%20Don%27t%20edit%20below%20this%20line%20--%3E%0A%0A%2Flabel%20~%22docs%5C-comments%22%20) to GitLab as soon
as possible so we can improve or fix it while behind a flag. When you upgrade
-GitLab to an earlier version, the feature flag status may change.
+GitLab, the feature flag status may change.
## Risks when enabling features still in development
@@ -144,3 +144,24 @@ Feature.enabled?(:my_awesome_feature)
Feature.disabled?(:my_awesome_feature)
=> false
```
+
+When the feature is ready, GitLab removes the feature flag, and the option for
+enabling and disabling it no longer exists. The feature becomes available in all instances.
+
+### View set feature flags
+
+You can view all GitLab administrator set feature flags:
+
+```ruby
+Feature.all
+=> [#<Flipper::Feature:198220 name="my_awesome_feature", state=:on, enabled_gate_names=[:boolean], adapter=:memoizable>]
+```
+
+### Unset feature flag
+
+You can unset a feature flag so that GitLab will fall back to the current defaults for that flag:
+
+```ruby
+Feature.remove(:my_awesome_feature)
+=> true
+```
diff --git a/doc/administration/geo/index.md b/doc/administration/geo/index.md
index b5eb0ba5841..c4164284e97 100644
--- a/doc/administration/geo/index.md
+++ b/doc/administration/geo/index.md
@@ -204,6 +204,7 @@ This list of limitations only reflects the latest version of GitLab. If you are
- Real-time updates of issues/merge requests (for example, via long polling) doesn't work on the **secondary** site.
- GitLab Runners cannot register with a **secondary** site. Support for this is [planned for the future](https://gitlab.com/gitlab-org/gitlab/-/issues/3294).
- [Selective synchronization](replication/configuration.md#selective-synchronization) only limits what repositories and files are replicated. The entire PostgreSQL data is still replicated. Selective synchronization is not built to accommodate compliance / export control use cases.
+- [Pages access control](../../user/project/pages/pages_access_control.md) doesn't work on secondaries. See [GitLab issue #9336](https://gitlab.com/gitlab-org/gitlab/-/issues/9336) for details.
### Limitations on replication/verification
diff --git a/doc/administration/operations/fast_ssh_key_lookup.md b/doc/administration/operations/fast_ssh_key_lookup.md
index ddedb3fe76a..acb2df18e65 100644
--- a/doc/administration/operations/fast_ssh_key_lookup.md
+++ b/doc/administration/operations/fast_ssh_key_lookup.md
@@ -178,3 +178,22 @@ GitLab supports `authorized_keys` database lookups with [SELinux](https://en.wik
Because the SELinux policy is static, GitLab doesn't support the ability to change
internal webserver ports at the moment. Administrators would have to create a special `.te`
file for the environment, since it isn't generated dynamically.
+
+## Troubleshooting
+
+If your SSH traffic is [slow](https://github.com/linux-pam/linux-pam/issues/270)
+or causing high CPU load, be sure to check the size of `/var/log/btmp`, and ensure it is rotated on a regular basis.
+If this file is very large, GitLab SSH fast lookup can cause the bottleneck to be hit more frequently, thus decreasing performance even further.
+If you are able to, you may consider disabling [`UsePAM` in your `sshd_config`](https://linux.die.net/man/5/sshd_config) to avoid reading `/var/log/btmp` altogether.
+
+Running `strace` and `lsof` on a running `sshd: git` process can return useful debugging information. To get an `strace` on an in-progress Git over SSH connection for IP `x.x.x.x`, run:
+
+```plaintext
+sudo strace -s 10000 -p $(sudo netstat -tp | grep x.x.x.x | egrep 'ssh.*: git' | sed -e 's/.*ESTABLISHED *//' -e 's#/.*##')
+```
+
+Or get an `lsof` for a running Git over SSH process:
+
+```plaintext
+sudo lsof -p $(sudo netstat -tp | egrep 'ssh.*: git' | head -1 | sed -e 's/.*ESTABLISHED *//' -e 's#/.*##')
+```
diff --git a/doc/administration/reference_architectures/10k_users.md b/doc/administration/reference_architectures/10k_users.md
index a5c60af47b1..87fa0ff236e 100644
--- a/doc/administration/reference_architectures/10k_users.md
+++ b/doc/administration/reference_architectures/10k_users.md
@@ -12,7 +12,7 @@ full list of reference architectures, see
> - **Supported users (approximate):** 10,000
> - **High Availability:** Yes ([Praefect](#configure-praefect-postgresql) needs a third-party PostgreSQL solution for HA)
-> - **Estimated Costs:** [GCP](https://cloud.google.com/products/calculator#id=e77713f6-dc0b-4bb3-bcef-cea904ac8efd)
+> - **Estimated Costs:** [See cost table](index.md#cost-to-run)
> - **Cloud Native Hybrid Alternative:** [Yes](#cloud-native-hybrid-reference-architecture-with-helm-charts-alternative)
> - **Performance tested daily with the [GitLab Performance Tool](https://gitlab.com/gitlab-org/quality/performance)**:
> - **Test requests per second (RPS) rates:** API: 200 RPS, Web: 20 RPS, Git (Pull): 20 RPS, Git (Push): 4 RPS
@@ -2234,7 +2234,7 @@ future with further specific cloud provider details.
| Sidekiq | 4 | 4 vCPU, 15 GB memory | `n1-standard-4` | 15.5 vCPU, 50 GB memory |
| Supporting services such as NGINX, Prometheus | 2 | 4 vCPU, 15 GB memory | `n1-standard-4` | 7.75 vCPU, 25 GB memory |
-- For this setup, we **recommend** and regularly [test](index.md#testing-process-and-results)
+- For this setup, we **recommend** and regularly [test](index.md#validation-and-test-results)
[Google Kubernetes Engine (GKE)](https://cloud.google.com/kubernetes-engine) and [Amazon Elastic Kubernetes Service (EKS)](https://aws.amazon.com/eks/). Other Kubernetes services may also work, but your mileage may vary.
- Nodes configuration is shown as it is forced to ensure pod vcpu / memory ratios and avoid scaling during **performance testing**.
- In production deployments, there is no need to assign pods to nodes. A minimum of three nodes in three different availability zones is strongly recommended to align with resilient cloud architecture practices.
diff --git a/doc/administration/reference_architectures/1k_users.md b/doc/administration/reference_architectures/1k_users.md
index ed6fbe84a48..0d0e7681ffd 100644
--- a/doc/administration/reference_architectures/1k_users.md
+++ b/doc/administration/reference_architectures/1k_users.md
@@ -18,6 +18,7 @@ many organizations.
> - **Supported users (approximate):** 1,000
> - **High Availability:** No. For a highly-available environment, you can
> follow a modified [3K reference architecture](3k_users.md#supported-modifications-for-lower-user-counts-ha).
+> - **Estimated Costs:** [See cost table](index.md#cost-to-run)
> - **Cloud Native Hybrid:** No. For a cloud native hybrid environment, you
> can follow a [modified hybrid reference architecture](#cloud-native-hybrid-reference-architecture-with-helm-charts).
> - **Performance tested daily with the [GitLab Performance Tool (GPT)](https://gitlab.com/gitlab-org/quality/performance)**:
diff --git a/doc/administration/reference_architectures/25k_users.md b/doc/administration/reference_architectures/25k_users.md
index 8cc355db951..f133e48008e 100644
--- a/doc/administration/reference_architectures/25k_users.md
+++ b/doc/administration/reference_architectures/25k_users.md
@@ -12,7 +12,7 @@ full list of reference architectures, see
> - **Supported users (approximate):** 25,000
> - **High Availability:** Yes ([Praefect](#configure-praefect-postgresql) needs a third-party PostgreSQL solution for HA)
-> - **Estimated Costs:** [GCP](https://cloud.google.com/products/calculator#id=925386e1-c01c-4c0a-8d7d-ebde1824b7b0)
+> - **Estimated Costs:** [See cost table](index.md#cost-to-run)
> - **Cloud Native Hybrid Alternative:** [Yes](#cloud-native-hybrid-reference-architecture-with-helm-charts-alternative)
> - **Performance tested weekly with the [GitLab Performance Tool (GPT)](https://gitlab.com/gitlab-org/quality/performance)**:
> - **Test requests per second (RPS) rates:** API: 500 RPS, Web: 50 RPS, Git (Pull): 50 RPS, Git (Push): 10 RPS
@@ -2232,7 +2232,7 @@ future with further specific cloud provider details.
| Sidekiq | 4 | 4 vCPU, 15 GB memory | `n1-standard-4` | 15.5 vCPU, 50 GB memory |
| Supporting services such as NGINX, Prometheus | 2 | 4 vCPU, 15 GB memory | `n1-standard-4` | 7.75 vCPU, 25 GB memory |
-- For this setup, we **recommend** and regularly [test](index.md#testing-process-and-results)
+- For this setup, we **recommend** and regularly [test](index.md#validation-and-test-results)
[Google Kubernetes Engine (GKE)](https://cloud.google.com/kubernetes-engine) and [Amazon Elastic Kubernetes Service (EKS)](https://aws.amazon.com/eks/). Other Kubernetes services may also work, but your mileage may vary.
- Nodes configuration is shown as it is forced to ensure pod vcpu / memory ratios and avoid scaling during **performance testing**.
- In production deployments, there is no need to assign pods to nodes. A minimum of three nodes in three different availability zones is strongly recommended to align with resilient cloud architecture practices.
diff --git a/doc/administration/reference_architectures/2k_users.md b/doc/administration/reference_architectures/2k_users.md
index 467c64b8279..f6c484b08b1 100644
--- a/doc/administration/reference_architectures/2k_users.md
+++ b/doc/administration/reference_architectures/2k_users.md
@@ -13,7 +13,7 @@ For a full list of reference architectures, see
> - **Supported users (approximate):** 2,000
> - **High Availability:** No. For a highly-available environment, you can
> follow a modified [3K reference architecture](3k_users.md#supported-modifications-for-lower-user-counts-ha).
-> - **Estimated Costs:** [GCP](https://cloud.google.com/products/calculator#id=84d11491-d72a-493c-a16e-650931faa658)
+> - **Estimated Costs:** [See cost table](index.md#cost-to-run)
> - **Cloud Native Hybrid:** [Yes](#cloud-native-hybrid-reference-architecture-with-helm-charts-alternative)
> - **Performance tested daily with the [GitLab Performance Tool (GPT)](https://gitlab.com/gitlab-org/quality/performance)**:
> - **Test requests per second (RPS) rates:** API: 40 RPS, Web: 4 RPS, Git (Pull): 4 RPS, Git (Push): 1 RPS
@@ -1022,7 +1022,7 @@ future with further specific cloud provider details.
| Sidekiq | 2 | 2 vCPU, 7.5 GB memory | `n1-standard-2` | 3.9 vCPU, 11.8 GB memory |
| Supporting services such as NGINX, Prometheus | 2 | 1 vCPU, 3.75 GB memory | `n1-standard-1` | 1.9 vCPU, 5.5 GB memory |
-- For this setup, we **recommend** and regularly [test](index.md#testing-process-and-results)
+- For this setup, we **recommend** and regularly [test](index.md#validation-and-test-results)
[Google Kubernetes Engine (GKE)](https://cloud.google.com/kubernetes-engine) and [Amazon Elastic Kubernetes Service (EKS)](https://aws.amazon.com/eks/). Other Kubernetes services may also work, but your mileage may vary.
- Nodes configuration is shown as it is forced to ensure pod vcpu / memory ratios and avoid scaling during **performance testing**.
- In production deployments, there is no need to assign pods to nodes. A minimum of three nodes in three different availability zones is strongly recommended to align with resilient cloud architecture practices.
diff --git a/doc/administration/reference_architectures/3k_users.md b/doc/administration/reference_architectures/3k_users.md
index 01d9987739b..0aadfa4e622 100644
--- a/doc/administration/reference_architectures/3k_users.md
+++ b/doc/administration/reference_architectures/3k_users.md
@@ -22,7 +22,7 @@ For a full list of reference architectures, see
> - **Supported users (approximate):** 3,000
> - **High Availability:** Yes, although [Praefect](#configure-praefect-postgresql) needs a third-party PostgreSQL solution
-> - **Estimated Costs:** [GCP](https://cloud.google.com/products/calculator/#id=ac4838e6-9c40-4a36-ac43-6d1bc1843e08)
+> - **Estimated Costs:** [See cost table](index.md#cost-to-run)
> - **Cloud Native Hybrid Alternative:** [Yes](#cloud-native-hybrid-reference-architecture-with-helm-charts-alternative)
> - **Performance tested weekly with the [GitLab Performance Tool (GPT)](https://gitlab.com/gitlab-org/quality/performance)**:
> - **Test requests per second (RPS) rates:** API: 60 RPS, Web: 6 RPS, Git (Pull): 6 RPS, Git (Push): 1 RPS
@@ -2191,7 +2191,7 @@ future with further specific cloud provider details.
| Sidekiq | 3 | 4 vCPU, 15 GB memory | `n1-standard-4` | 11.8 vCPU, 38.9 GB memory |
| Supporting services such as NGINX, Prometheus | 2 | 2 vCPU, 7.5 GB memory | `n1-standard-2` | 3.9 vCPU, 11.8 GB memory |
-- For this setup, we **recommend** and regularly [test](index.md#testing-process-and-results)
+- For this setup, we **recommend** and regularly [test](index.md#validation-and-test-results)
[Google Kubernetes Engine (GKE)](https://cloud.google.com/kubernetes-engine) and [Amazon Elastic Kubernetes Service (EKS)](https://aws.amazon.com/eks/). Other Kubernetes services may also work, but your mileage may vary.
- Nodes configuration is shown as it is forced to ensure pod vcpu / memory ratios and avoid scaling during **performance testing**.
- In production deployments, there is no need to assign pods to nodes. A minimum of three nodes in three different availability zones is strongly recommended to align with resilient cloud architecture practices.
diff --git a/doc/administration/reference_architectures/50k_users.md b/doc/administration/reference_architectures/50k_users.md
index d5bb9c4ad64..365a634cec5 100644
--- a/doc/administration/reference_architectures/50k_users.md
+++ b/doc/administration/reference_architectures/50k_users.md
@@ -12,7 +12,7 @@ full list of reference architectures, see
> - **Supported users (approximate):** 50,000
> - **High Availability:** Yes ([Praefect](#configure-praefect-postgresql) needs a third-party PostgreSQL solution for HA)
-> - **Estimated Costs:** [GCP](https://cloud.google.com/products/calculator/#id=8006396b-88ee-40cd-a1c8-77cdefa4d3c8)
+> - **Estimated Costs:** [See cost table](index.md#cost-to-run)
> - **Cloud Native Hybrid Alternative:** [Yes](#cloud-native-hybrid-reference-architecture-with-helm-charts-alternative)
> - **Performance tested weekly with the [GitLab Performance Tool (GPT)](https://gitlab.com/gitlab-org/quality/performance)**:
> - **Test requests per second (RPS) rates:** API: 1000 RPS, Web: 100 RPS, Git (Pull): 100 RPS, Git (Push): 20 RPS
@@ -2248,7 +2248,7 @@ future with further specific cloud provider details.
| Sidekiq | 4 | 4 vCPU, 15 GB memory | `n1-standard-4` | 15.5 vCPU, 50 GB memory |
| Supporting services such as NGINX, Prometheus | 2 | 4 vCPU, 15 GB memory | `n1-standard-4` | 7.75 vCPU, 25 GB memory |
-- For this setup, we **recommend** and regularly [test](index.md#testing-process-and-results)
+- For this setup, we **recommend** and regularly [test](index.md#validation-and-test-results)
[Google Kubernetes Engine (GKE)](https://cloud.google.com/kubernetes-engine) and [Amazon Elastic Kubernetes Service (EKS)](https://aws.amazon.com/eks/). Other Kubernetes services may also work, but your mileage may vary.
- Nodes configuration is shown as it is forced to ensure pod vcpu / memory ratios and avoid scaling during **performance testing**.
- In production deployments, there is no need to assign pods to nodes. A minimum of three nodes in three different availability zones is strongly recommended to align with resilient cloud architecture practices.
diff --git a/doc/administration/reference_architectures/5k_users.md b/doc/administration/reference_architectures/5k_users.md
index 33ca4e4899f..5e029de517d 100644
--- a/doc/administration/reference_architectures/5k_users.md
+++ b/doc/administration/reference_architectures/5k_users.md
@@ -19,7 +19,7 @@ costly-to-operate environment by using the
> - **Supported users (approximate):** 5,000
> - **High Availability:** Yes ([Praefect](#configure-praefect-postgresql) needs a third-party PostgreSQL solution for HA)
-> - **Estimated Costs:** [GCP](https://cloud.google.com/products/calculator/#id=8742e8ea-c08f-4e0a-b058-02f3a1c38a2f)
+> - **Estimated Costs:** [See cost table](index.md#cost-to-run)
> - **Cloud Native Hybrid Alternative:** [Yes](#cloud-native-hybrid-reference-architecture-with-helm-charts-alternative)
> - **Performance tested weekly with the [GitLab Performance Tool (GPT)](https://gitlab.com/gitlab-org/quality/performance)**:
> - **Test requests per second (RPS) rates:** API: 100 RPS, Web: 10 RPS, Git (Pull): 10 RPS, Git (Push): 2 RPS
@@ -2167,7 +2167,7 @@ future with further specific cloud provider details.
| Sidekiq | 3 | 4 vCPU, 15 GB memory | `n1-standard-4` | 11.8 vCPU, 38.9 GB memory |
| Supporting services such as NGINX, Prometheus | 2 | 2 vCPU, 7.5 GB memory | `n1-standard-2` | 3.9 vCPU, 11.8 GB memory |
-- For this setup, we **recommend** and regularly [test](index.md#testing-process-and-results)
+- For this setup, we **recommend** and regularly [test](index.md#validation-and-test-results)
[Google Kubernetes Engine (GKE)](https://cloud.google.com/kubernetes-engine) and [Amazon Elastic Kubernetes Service (EKS)](https://aws.amazon.com/eks/). Other Kubernetes services may also work, but your mileage may vary.
- Nodes configuration is shown as it is forced to ensure pod vcpu / memory ratios and avoid scaling during **performance testing**.
- In production deployments, there is no need to assign pods to nodes. A minimum of three nodes in three different availability zones is strongly recommended to align with resilient cloud architecture practices.
diff --git a/doc/administration/reference_architectures/index.md b/doc/administration/reference_architectures/index.md
index bd796600564..2a9e4cc0e7a 100644
--- a/doc/administration/reference_architectures/index.md
+++ b/doc/administration/reference_architectures/index.md
@@ -83,6 +83,203 @@ to get assistance from Support with troubleshooting the [2,000 users](2k_users.m
and higher reference architectures.
[Read more about our definition of scaled architectures](https://about.gitlab.com/support/#definition-of-scaled-architecture).
+### Validation and test results
+
+The [Quality Engineering - Enablement team](https://about.gitlab.com/handbook/engineering/quality/quality-engineering/) does regular smoke and performance tests for the reference architectures to ensure they remain compliant.
+
+- Testing occurs against all reference architectures and cloud providers in an automated and ad-hoc fashion. This is done by two tools:
+ - The [GitLab Environment Toolkit](https://gitlab.com/gitlab-org/quality/gitlab-environment-toolkit) for building the environments.
+ - The [GitLab Performance Tool](https://gitlab.com/gitlab-org/quality/performance) for performance testing.
+- Network latency on the test environments between components on all Cloud Providers were measured at <5ms. Note that this is shared as an observation and not as an implicit recommendation.
+- We aim to have a "test smart" approach where architectures tested have a good range that can also apply to others. Testing focuses on 10k Omnibus on GCP as the testing has shown this is a good bellwether for the other architectures and cloud providers as well as Cloud Native Hybrids.
+- Testing is done publicly and all results are shared.
+
+The following table details the testing done against the reference architectures along with the frequency and results. Additional testing is continuously evaluated, and the table is updated accordingly.
+
+<style>
+table.test-coverage td {
+ border-left: 1px solid #dbdbdb;
+ border-right: 1px solid #dbdbdb;
+ border-bottom: 1px solid #dbdbdb;
+}
+
+table.test-coverage th {
+ border-left: 1px solid #dbdbdb;
+ border-right: 1px solid #dbdbdb;
+ border-bottom: 1px solid #dbdbdb;
+}
+</style>
+
+<table class="test-coverage">
+ <col>
+ <colgroup span="2"></colgroup>
+ <colgroup span="2"></colgroup>
+ <tr>
+ <th rowspan="2">Reference<br/>Architecture</th>
+ <th style="text-align: center" colspan="2" scope="colgroup">GCP (* also proxy for Bare-Metal)</th>
+ <th style="text-align: center" colspan="2" scope="colgroup">AWS</th>
+ <th style="text-align: center" colspan="2" scope="colgroup">Azure</th>
+ </tr>
+ <tr>
+ <th scope="col">Omnibus</th>
+ <th scope="col">Cloud Native Hybrid</th>
+ <th scope="col">Omnibus</th>
+ <th scope="col">Cloud Native Hybrid</th>
+ <th scope="col">Omnibus</th>
+ <th scope="col">Cloud Native Hybrid</th>
+ </tr>
+ <tr>
+ <th scope="row">1k</th>
+ <td><a href="https://gitlab.com/gitlab-org/quality/performance/-/wikis/Benchmarks/Latest/1k">Daily</a> (to be moved to Weekly)</td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td></td>
+ </tr>
+ <tr>
+ <th scope="row">2k</th>
+ <td><a href="https://gitlab.com/gitlab-org/quality/performance/-/wikis/Benchmarks/Latest/2k">Daily</a> (to be moved to Weekly)</td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td></td>
+ </tr>
+ <tr>
+ <th scope="row">3k</th>
+ <td><a href="https://gitlab.com/gitlab-org/quality/performance/-/wikis/Benchmarks/Latest/3k">Weekly</a></td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td></td>
+ </tr>
+ <tr>
+ <th scope="row">5k</th>
+ <td><a href="https://gitlab.com/gitlab-org/quality/performance/-/wikis/Benchmarks/Latest/5k">Weekly</a></td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td></td>
+ </tr>
+ <tr>
+ <th scope="row">10k</th>
+ <td><a href="https://gitlab.com/gitlab-org/quality/performance/-/wikis/Benchmarks/Latest/10k">Daily</a></td>
+ <td><a href="https://gitlab.com/gitlab-org/quality/performance/-/wikis/Past-Results/10k-Cloud-Native-Hybrid">Ad-Hoc</a></td>
+ <td><a href="https://gitlab.com/gitlab-org/quality/performance/-/wikis/Past-Results/10k">Ad-Hoc (inc Cloud Services)</a></td>
+ <td><a href="https://gitlab.com/gitlab-org/quality/performance/-/wikis/Past-Results/10k-Cloud-Native-Hybrid">Ad-Hoc</a></td>
+ <td><a href="https://gitlab.com/gitlab-org/quality/performance/-/wikis/Past-Results/10k">Ad-Hoc</a></td>
+ <td></td>
+ </tr>
+ <tr>
+ <th scope="row">25k</th>
+ <td><a href="https://gitlab.com/gitlab-org/quality/performance/-/wikis/Benchmarks/Latest/25k">Weekly</a></td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td><a href="https://gitlab.com/gitlab-org/quality/performance/-/wikis/Past-Results/25k">Ad-Hoc</a></td>
+ <td></td>
+ </tr>
+ <tr>
+ <th scope="row">50k</th>
+ <td><a href="https://gitlab.com/gitlab-org/quality/performance/-/wikis/Benchmarks/Latest/50k">Weekly</a></td>
+ <td></td>
+ <td><a href="https://gitlab.com/gitlab-org/quality/performance/-/wikis/Past-Results/50k">Ad-Hoc (inc Cloud Services)</a></td>
+ <td></td>
+ <td></td>
+ <td></td>
+ </tr>
+</table>
+
+The Standard Reference Architectures are designed to be platform agnostic, with everything being run on VMs via [Omnibus GitLab](https://docs.gitlab.com/omnibus/). While testing occurs primarily on GCP, ad-hoc testing has shown that they perform similarly on equivalently specced hardware on other Cloud Providers or if run on premises (bare-metal).
+
+### Cost to run
+
+<table class="test-coverage">
+ <col>
+ <colgroup span="2"></colgroup>
+ <colgroup span="2"></colgroup>
+ <tr>
+ <th rowspan="2">Reference<br/>Architecture</th>
+ <th style="text-align: center" colspan="2" scope="colgroup">GCP</th>
+ <th style="text-align: center" colspan="2" scope="colgroup">AWS</th>
+ <th style="text-align: center" colspan="2" scope="colgroup">Azure</th>
+ </tr>
+ <tr>
+ <th scope="col">Omnibus</th>
+ <th scope="col">Cloud Native Hybrid</th>
+ <th scope="col">Omnibus</th>
+ <th scope="col">Cloud Native Hybrid</th>
+ <th scope="col">Omnibus</th>
+ <th scope="col">Cloud Native Hybrid</th>
+ </tr>
+ <tr>
+ <th scope="row">1k</th>
+ <td><a href="https://cloud.google.com/products/calculator#id=a6d6a94a-c7dc-4c22-85c4-7c5747f272ed">Calculated cost</a></td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td></td>
+ </tr>
+ <tr>
+ <th scope="row">2k</th>
+ <td><a href="https://cloud.google.com/products/calculator#id=84d11491-d72a-493c-a16e-650931faa658">Calculated cost</a></td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td></td>
+ </tr>
+ <tr>
+ <th scope="row">3k</th>
+ <td><a href="https://cloud.google.com/products/calculator/#id=ac4838e6-9c40-4a36-ac43-6d1bc1843e08">Calculated cost</a></td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td></td>
+ </tr>
+ <tr>
+ <th scope="row">5k</th>
+ <td><a href="https://cloud.google.com/products/calculator/#id=8742e8ea-c08f-4e0a-b058-02f3a1c38a2f">Calculated cost</a></td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td></td>
+ </tr>
+ <tr>
+ <th scope="row">10k</th>
+ <td><a href="https://cloud.google.com/products/calculator#id=e77713f6-dc0b-4bb3-bcef-cea904ac8efd">Calculated cost</a></td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td></td>
+ </tr>
+ <tr>
+ <th scope="row">25k</th>
+ <td><a href="https://cloud.google.com/products/calculator#id=925386e1-c01c-4c0a-8d7d-ebde1824b7b0">Calculated cost</a></td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td></td>
+ </tr>
+ <tr>
+ <th scope="row">50k</th>
+ <td><a href="https://cloud.google.com/products/calculator/#id=8006396b-88ee-40cd-a1c8-77cdefa4d3c8">Calculated cost</a></td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td></td>
+ </tr>
+</table>
+
## Availability Components
GitLab comes with the following components for your use, listed from least to
@@ -191,33 +388,3 @@ The reference architectures for user counts [3,000](3k_users.md) and up support
In the specific case you have the requirement to achieve HA but have a lower user count, select modifications to the [3,000 user](3k_users.md) architecture are supported.
For more details, [refer to this section in the architecture's documentation](3k_users.md#supported-modifications-for-lower-user-counts-ha).
-
-## Testing process and results
-
-The [Quality Engineering - Enablement team](https://about.gitlab.com/handbook/engineering/quality/quality-engineering/) does regular smoke and performance tests for the reference architectures to ensure they remain compliant.
-
-In this section, we detail some of the process as well as the results.
-
-Note the following about the testing process:
-
-- Testing occurs against all main reference architectures and cloud providers in an automated and ad-hoc fashion.
- This is achieved through two tools built by the team:
- - The [GitLab Environment Toolkit](https://gitlab.com/gitlab-org/quality/gitlab-environment-toolkit) for building the environments.
- - The [GitLab Performance Tool](https://gitlab.com/gitlab-org/quality/performance) for performance testing.
-- Network latency on the test environments between components on all Cloud Providers were measured at <5ms. Note that this is shared as an observation and not as an implicit recommendation.
-- We aim to have a "test smart" approach where architectures tested have a good range that can also apply to others. Testing focuses on 10k Omnibus on GCP as the testing has shown this is a good bellwether for the other architectures and cloud providers as well as Cloud Native Hybrids.
-- Testing is done publicly and all results are shared.
-
-Τhe following table details the testing done against the reference architectures along with the frequency and results. Note that this list above is non exhaustive. Additional testing is continuously evaluated and iterated on, and the table is updated accordingly.
-
-| Reference<br/>Architecture<br/>Size | Bare-Metal | GCP | AWS | Azure |
-|-----------------------------|------------|-----|-----|-------|
-| 1k | <i>Refer to GCP<sup>1</sup></i> | [Standard - Weekly](https://gitlab.com/gitlab-org/quality/performance/-/wikis/Benchmarks/Latest/1k)<sup>1</sup> | - | - |
-| 2k | <i>Refer to GCP<sup>1</sup></i> | [Standard - Weekly](https://gitlab.com/gitlab-org/quality/performance/-/wikis/Benchmarks/Latest/2k)<sup>1</sup> | - | - |
-| 3k | <i>Refer to GCP<sup>1</sup></i> | [Standard - Weekly](https://gitlab.com/gitlab-org/quality/performance/-/wikis/Benchmarks/Latest/3k)<sup>1</sup> | - | - |
-| 5k | <i>Refer to GCP<sup>1</sup></i> | [Standard - Weekly](https://gitlab.com/gitlab-org/quality/performance/-/wikis/Benchmarks/Latest/5k)<sup>1</sup> | - | - |
-| 10k | <i>Refer to GCP<sup>1</sup></i> | [Standard - Daily](https://gitlab.com/gitlab-org/quality/performance/-/wikis/Benchmarks/Latest/10k)<sup>1</sup> <br/> [Standard (inc Cloud Services) - Ad-Hoc](https://gitlab.com/gitlab-org/quality/performance/-/wikis/Past-Results/10k) <br/> [Cloud Native Hybrid - Ad-Hoc](https://gitlab.com/gitlab-org/quality/performance/-/wikis/Past-Results/10k-Cloud-Native-Hybrid) | [Standard (inc Cloud Services) - Ad-Hoc](https://gitlab.com/gitlab-org/quality/performance/-/wikis/Past-Results/10k) <br/> [Cloud Native Hybrid - Ad-Hoc](https://gitlab.com/gitlab-org/quality/performance/-/wikis/Past-Results/10k-Cloud-Native-Hybrid) | [Standard - Ad-Hoc](https://gitlab.com/gitlab-org/quality/performance/-/wikis/Past-Results/10k) |
-| 25k | <i>Refer to GCP<sup>1</sup></i> | [Standard - Weekly](https://gitlab.com/gitlab-org/quality/performance/-/wikis/Benchmarks/Latest/25k)<sup>1</sup> | - | [Standard - Ad-Hoc](https://gitlab.com/gitlab-org/quality/performance/-/wikis/Past-Results/25k) |
-| 50k | <i>Refer to GCP<sup>1</sup></i> | [Standard - Weekly](https://gitlab.com/gitlab-org/quality/performance/-/wikis/Benchmarks/Latest/50k)<sup>1</sup> | [Standard (inc Cloud Services) - Ad-Hoc](https://gitlab.com/gitlab-org/quality/performance/-/wikis/Past-Results/50k) | - |
-
-1. The Standard Reference Architectures are designed to be platform agnostic, with everything being run on VMs via [Omnibus GitLab](https://docs.gitlab.com/omnibus/). While testing occurs primarily on GCP, ad-hoc testing has shown that they perform similarly on equivalently specced hardware on other Cloud Providers or if run on premises (bare-metal).
diff --git a/doc/administration/troubleshooting/gitlab_rails_cheat_sheet.md b/doc/administration/troubleshooting/gitlab_rails_cheat_sheet.md
index 0dd931eb506..3680c54f9eb 100644
--- a/doc/administration/troubleshooting/gitlab_rails_cheat_sheet.md
+++ b/doc/administration/troubleshooting/gitlab_rails_cheat_sheet.md
@@ -1377,3 +1377,18 @@ Gitlab::CurrentSettings.elasticsearch_url
Gitlab::CurrentSettings.elasticsearch_indexing
```
+
+#### Changing the Elasticsearch password
+
+```ruby
+es_url = Gitlab::CurrentSettings.current_application_settings
+
+# Confirm the current ElasticSearch URL
+es_url.elasticsearch_url
+
+# Set the ElasticSearch URL
+es_url.elasticsearch_url = "http://<username>:<password>@your.es.host:<port>"
+
+# Save the change
+es_url.save!
+```
diff --git a/doc/api/jobs.md b/doc/api/jobs.md
index 8dcd898b8c3..89018548f5f 100644
--- a/doc/api/jobs.md
+++ b/doc/api/jobs.md
@@ -10,14 +10,16 @@ info: To determine the technical writer assigned to the Stage/Group associated w
Get a list of jobs in a project. Jobs are sorted in descending order of their IDs.
+By default, this request returns 20 results at a time because the API results [are paginated](index.md#pagination)
+
```plaintext
GET /projects/:id/jobs
```
-| Attribute | Type | Required | Description |
-|-----------|--------------------------------|----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| `id` | integer/string | yes | ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user. |
-| `scope` | string **or** array of strings | no | Scope of jobs to show. Either one of or an array of the following: `created`, `pending`, `running`, `failed`, `success`, `canceled`, `skipped`, or `manual`. All jobs are returned if `scope` is not provided. |
+| Attribute | Type | Required | Description |
+|-----------|--------------------------------|------------------------|-------------|
+| `id` | integer/string | **{check-circle}** Yes | ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user. |
+| `scope` | string **or** array of strings | **{dotted-circle}** No | Scope of jobs to show. Either one of or an array of the following: `created`, `pending`, `running`, `failed`, `success`, `canceled`, `skipped`, or `manual`. All jobs are returned if `scope` is not provided. |
```shell
curl --globoff --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/jobs?scope[]=pending&scope[]=running"
@@ -155,16 +157,18 @@ Example of response
Get a list of jobs for a pipeline.
+By default, this request returns 20 results at a time because the API results [are paginated](index.md#pagination)
+
```plaintext
GET /projects/:id/pipelines/:pipeline_id/jobs
```
-| Attribute | Type | Required | Description |
-|-------------------|--------------------------------|----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| `id` | integer/string | yes | ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user. |
-| `pipeline_id` | integer | yes | ID of a pipeline. Can also be obtained in CI jobs via the [predefined CI variable](../ci/variables/predefined_variables.md) `CI_PIPELINE_ID`. |
-| `scope` | string **or** array of strings | no | Scope of jobs to show. Either one of or an array of the following: `created`, `pending`, `running`, `failed`, `success`, `canceled`, `skipped`, or `manual`. All jobs are returned if `scope` is not provided. |
-| `include_retried` | boolean | no | Include retried jobs in the response. Defaults to `false`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/272627) in GitLab 13.9. |
+| Attribute | Type | Required | Description |
+|-------------------|--------------------------------|------------------------|-------------|
+| `id` | integer/string | **{check-circle}** Yes | ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user. |
+| `pipeline_id` | integer | **{check-circle}** Yes | ID of a pipeline. Can also be obtained in CI jobs via the [predefined CI variable](../ci/variables/predefined_variables.md) `CI_PIPELINE_ID`. |
+| `scope` | string **or** array of strings | **{dotted-circle}** No | Scope of jobs to show. Either one of or an array of the following: `created`, `pending`, `running`, `failed`, `success`, `canceled`, `skipped`, or `manual`. All jobs are returned if `scope` is not provided. |
+| `include_retried` | boolean | **{dotted-circle}** No | Include retried jobs in the response. Defaults to `false`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/272627) in GitLab 13.9. |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/pipelines/6/jobs?scope[]=pending&scope[]=running"
@@ -316,11 +320,11 @@ Get a list of bridge jobs for a pipeline.
GET /projects/:id/pipelines/:pipeline_id/bridges
```
-| Attribute | Type | Required | Description |
-|---------------|--------------------------------|----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| `id` | integer/string | yes | ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user. |
-| `pipeline_id` | integer | yes | ID of a pipeline. |
-| `scope` | string **or** array of strings | no | Scope of jobs to show. Either one of or an array of the following: `created`, `pending`, `running`, `failed`, `success`, `canceled`, `skipped`, or `manual`. All jobs are returned if `scope` is not provided. |
+| Attribute | Type | Required | Description |
+|---------------|--------------------------------|------------------------|-------------|
+| `id` | integer/string | **{check-circle}** Yes | ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user. |
+| `pipeline_id` | integer | **{check-circle}** Yes | ID of a pipeline. |
+| `scope` | string **or** array of strings | **{dotted-circle}** No | Scope of jobs to show. Either one of or an array of the following: `created`, `pending`, `running`, `failed`, `success`, `canceled`, `skipped`, or `manual`. All jobs are returned if `scope` is not provided. |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/pipelines/6/bridges?scope[]=pending&scope[]=running"
@@ -483,9 +487,9 @@ GET /job/allowed_agents
Supported attributes:
-| Attribute | Type | Required | Description |
-|:------------ |:---------|:---------|:----------------------|
-| `CI_JOB_TOKEN` | string | yes | Token value associated with the GitLab-provided `CI_JOB_TOKEN` variable. |
+| Attribute | Type | Required | Description |
+|----------------|----------|------------------------|-------------|
+| `CI_JOB_TOKEN` | string | **{check-circle}** Yes | Token value associated with the GitLab-provided `CI_JOB_TOKEN` variable. |
Example request:
@@ -558,10 +562,10 @@ Get a single job of a project
GET /projects/:id/jobs/:job_id
```
-| Attribute | Type | Required | Description |
-|-----------|----------------|----------|------------------------------------------------------------------------------------------------------------------|
-| `id` | integer/string | yes | ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user. |
-| `job_id` | integer | yes | ID of a job. |
+| Attribute | Type | Required | Description |
+|-----------|----------------|------------------------|-------------|
+| `id` | integer/string | **{check-circle}** Yes | ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user. |
+| `job_id` | integer | **{check-circle}** Yes | ID of a job. |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/jobs/8"
@@ -635,10 +639,10 @@ Get a log (trace) of a specific job of a project:
GET /projects/:id/jobs/:job_id/trace
```
-| Attribute | Type | Required | Description |
-|-----------|----------------|----------|------------------------------------------------------------------------------------------------------------------|
-| `id` | integer/string | yes | ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user. |
-| `job_id` | integer | yes | ID of a job. |
+| Attribute | Type | Required | Description |
+|-----------|----------------|------------------------|-------------|
+| `id` | integer/string | **{check-circle}** Yes | ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user. |
+| `job_id` | integer | **{check-circle}** Yes | ID of a job. |
```shell
curl --location --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/jobs/8/trace"
@@ -659,10 +663,10 @@ Cancel a single job of a project
POST /projects/:id/jobs/:job_id/cancel
```
-| Attribute | Type | Required | Description |
-|-----------|----------------|----------|------------------------------------------------------------------------------------------------------------------|
-| `id` | integer/string | yes | ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user. |
-| `job_id` | integer | yes | ID of a job. |
+| Attribute | Type | Required | Description |
+|-----------|----------------|------------------------|-------------|
+| `id` | integer/string | **{check-circle}** Yes | ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user. |
+| `job_id` | integer | **{check-circle}** Yes | ID of a job. |
```shell
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/jobs/1/cancel"
@@ -709,10 +713,10 @@ Retry a single job of a project
POST /projects/:id/jobs/:job_id/retry
```
-| Attribute | Type | Required | Description |
-|-----------|----------------|----------|------------------------------------------------------------------------------------------------------------------|
-| `id` | integer/string | yes | ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user. |
-| `job_id` | integer | yes | ID of a job. |
+| Attribute | Type | Required | Description |
+|-----------|----------------|------------------------|-------------|
+| `id` | integer/string | **{check-circle}** Yes | ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user. |
+| `job_id` | integer | **{check-circle}** Yes | ID of a job. |
```shell
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/jobs/1/retry"
@@ -761,10 +765,10 @@ POST /projects/:id/jobs/:job_id/erase
Parameters
-| Attribute | Type | Required | Description |
-|-----------|----------------|----------|------------------------------------------------------------------------------------------------------------------|
-| `id` | integer/string | yes | ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user. |
-| `job_id` | integer | yes | ID of a job. |
+| Attribute | Type | Required | Description |
+|-----------|----------------|------------------------|-------------|
+| `id` | integer/string | **{check-circle}** Yes | ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user. |
+| `job_id` | integer | **{check-circle}** Yes | ID of a job. |
Example of request
@@ -818,10 +822,10 @@ Triggers a manual action to start a job.
POST /projects/:id/jobs/:job_id/play
```
-| Attribute | Type | Required | Description |
-|-----------|----------------|----------|------------------------------------------------------------------------------------------------------------------|
-| `id` | integer/string | yes | ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user. |
-| `job_id` | integer | yes | ID of a job. |
+| Attribute | Type | Required | Description |
+|-----------|----------------|------------------------|-------------|
+| `id` | integer/string | **{check-circle}** Yes | ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user. |
+| `job_id` | integer | **{check-circle}** Yes | ID of a job. |
```shell
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/jobs/1/play"
diff --git a/doc/ci/pipelines/settings.md b/doc/ci/pipelines/settings.md
index 85824dfb7c7..32049940615 100644
--- a/doc/ci/pipelines/settings.md
+++ b/doc/ci/pipelines/settings.md
@@ -108,6 +108,10 @@ To customize the path:
- Is on an external site, enter the full URL.
1. Select **Save changes**.
+NOTE:
+You cannot use your project's [pipeline editor](../pipeline_editor/index.md) to
+edit CI/CD configuration files in other projects or on an external site.
+
### Custom CI/CD configuration file examples
If the CI/CD configuration file is not in the root directory, the path must be relative to it.
diff --git a/doc/install/requirements.md b/doc/install/requirements.md
index 665e80e6e00..bce9702b032 100644
--- a/doc/install/requirements.md
+++ b/doc/install/requirements.md
@@ -331,6 +331,10 @@ NOTE:
We don't support running GitLab with JavaScript disabled in the browser and have no plans of supporting that
in the future because we have features such as issue boards which require JavaScript extensively.
+## Security
+
+After installation, be sure to read and follow guidance on [maintaining a secure GitLab installation](../security/index.md).
+
<!-- ## Troubleshooting
Include any troubleshooting steps that you can foresee. If you know beforehand what issues
diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md
index 539908cd7bc..5598a935f77 100644
--- a/doc/raketasks/backup_restore.md
+++ b/doc/raketasks/backup_restore.md
@@ -828,6 +828,7 @@ For installations from source:
The `CRON=1` environment setting directs the backup script to hide all progress
output if there aren't any errors. This is recommended to reduce cron spam.
+When troubleshooting backup problems, however, replace `CRON=1` with `--trace` to log verbosely.
### Limit backup lifetime for local files (prune old backups)
diff --git a/doc/security/index.md b/doc/security/index.md
index ab554e9135f..5dc3814ea08 100644
--- a/doc/security/index.md
+++ b/doc/security/index.md
@@ -30,3 +30,5 @@ type: index
## Securing your GitLab installation
Consider access control features like [Sign up restrictions](../user/admin_area/settings/sign_up_restrictions.md) and [Authentication options](../topics/authentication/) to harden your GitLab instance and minimize the risk of unwanted user account creation.
+
+Self-hosting GitLab customers and administrators are responsible for the security of their underlying hosts, and for keeping GitLab itself up to date. It is important to [regularly patch GitLab](../policy/maintenance.md), patch your operating system and its software, and harden your hosts in accordance with vendor guidance.
diff --git a/doc/update/upgrading_from_source.md b/doc/update/upgrading_from_source.md
index 286083ce868..22367435ae4 100644
--- a/doc/update/upgrading_from_source.md
+++ b/doc/update/upgrading_from_source.md
@@ -73,7 +73,7 @@ Download Ruby and compile it:
```shell
mkdir /tmp/ruby && cd /tmp/ruby
-curl --remote-name --progress-bar "https://cache.ruby-lang.org/pub/ruby/2.7/ruby-2.7.4.tar.gz"
+curl --remote-name --location --progress-bar "https://cache.ruby-lang.org/pub/ruby/2.7/ruby-2.7.4.tar.gz"
echo '3043099089608859fc8cce7f9fdccaa1f53a462457e3838ec3b25a7d609fbc5b ruby-2.7.4.tar.gz' | sha256sum -c - && tar xzf ruby-2.7.4.tar.gz
cd ruby-2.7.4
@@ -111,7 +111,7 @@ Download and install Go (for Linux, 64-bit):
# Remove former Go installation folder
sudo rm -rf /usr/local/go
-curl --remote-name --progress-bar "https://go.dev/dl/go1.16.10.linux-amd64.tar.gz"
+curl --remote-name --location --progress-bar "https://go.dev/dl/go1.16.10.linux-amd64.tar.gz"
echo '414cd18ce1d193769b9e97d2401ad718755ab47816e13b2a1cde203d263b55cf go1.16.10.linux-amd64.tar.gz' | shasum -a256 -c - && \
sudo tar -C /usr/local -xzf go1.16.10.linux-amd64.tar.gz
sudo ln -sf /usr/local/go/bin/{go,gofmt} /usr/local/bin/
diff --git a/doc/user/admin_area/reporting/spamcheck.md b/doc/user/admin_area/reporting/spamcheck.md
new file mode 100644
index 00000000000..02d7cd01139
--- /dev/null
+++ b/doc/user/admin_area/reporting/spamcheck.md
@@ -0,0 +1,65 @@
+---
+stage: Enablement
+group: Distribution
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
+# Spamcheck anti-spam service **(PREMIUM SELF)**
+
+> [Introduced](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/6259) in GitLab 14.8.
+
+[Spamcheck](https://gitlab.com/gitlab-org/spamcheck) is an anti-spam engine
+developed by GitLab originally to combat rising amount of spam in GitLab.com,
+and later made public to be used in self-managed GitLab instances.
+
+## Enable Spamcheck
+
+Spamcheck is only available for package-based installations:
+
+1. Edit `/etc/gitlab/gitlab.rb` and enable Spamcheck:
+
+ ```ruby
+ spamcheck['enable'] = true
+ ```
+
+1. Reconfigure GitLab:
+
+ ```shell
+ sudo gitlab-ctl reconfigure
+ ```
+
+1. Verify that the new services `spamcheck` and `spam-classifier` are
+ up and running:
+
+ ```shell
+ sudo gitlab-ctl status
+ ```
+
+## Configure GitLab to use Spamcheck
+
+1. On the top bar, select **Menu > Admin**.
+1. On the left sidebar, select **Settings > Reporting**.
+1. Expand **Spam and Anti-bot Protection**.
+1. Update the Spam Check settings:
+ 1. Check the "Enable Spam Check via external API endpoint" checkbox.
+ 1. For **URL of the external Spam Check endpoint** use `grpc://localhost:8001`.
+ 1. Leave **Spam Check API key** blank.
+1. Select **Save changes**.
+
+NOTE:
+In single-node instances, Spamcehck runs over `localhost`, and hence is running
+in an unauthenticated mode. If on multi-node instances where GitLab runs on one
+server and Spamcheck runs on another server listening over a public endpoint, it
+is recommended to enforce some sort of authentication using a reverse proxy in
+front of the Spamcheck service that can be used along with an API key. One
+example would be to use `JWT` authentication for this and specifying a bearer
+token as the API key.
+[Native authentication for Spamcheck is in the works](https://gitlab.com/gitlab-com/gl-security/engineering-and-research/automation-team/spam/spamcheck/-/issues/171).
+
+## Running Spamcheck over TLS
+
+Spamcheck service on its own can not communicate directly over TLS with GitLab.
+However, Spamcheck can be deployed behind a reverse proxy which performs TLS
+termination. In such a scenario, GitLab can be made to communicate with
+Spamcheck over TLS by specifying `tls://` scheme for the external Spamcheck URL
+instead of `grpc://` in the Admin settings.
diff --git a/doc/user/admin_area/settings/index.md b/doc/user/admin_area/settings/index.md
index 2820f3ae9df..67546ff0f54 100644
--- a/doc/user/admin_area/settings/index.md
+++ b/doc/user/admin_area/settings/index.md
@@ -160,7 +160,7 @@ The **Preferences** settings contain:
The **Reporting** settings contain:
- [Spam and Anti-bot Protection](../../../integration/recaptcha.md) -
- Enable anti-spam services, like reCAPTCHA or Akismet, and set IP limits.
+ Enable anti-spam services, like reCAPTCHA, Akismet or [Spamcheck](../reporting/spamcheck.md), and set IP limits.
- [Abuse reports](../review_abuse_reports.md) - Set notification email for abuse reports.
### Repository
diff --git a/doc/user/clusters/agent/ci_cd_tunnel.md b/doc/user/clusters/agent/ci_cd_tunnel.md
index f1c49b87383..96aa619c104 100644
--- a/doc/user/clusters/agent/ci_cd_tunnel.md
+++ b/doc/user/clusters/agent/ci_cd_tunnel.md
@@ -29,7 +29,7 @@ jobs provide a `KUBECONFIG` variable compatible with `kubectl`.
Also, each Agent has a separate context (`kubecontext`). By default,
there isn't any context selected.
-Contexts are named in the following format: `<agent-configuration-project-path>:<agent-name>`.
+Contexts are named in the following format: `<namespace>/<project-name>:<agent-name>`.
To get the list of available contexts, run `kubectl config get-contexts`.
## Share the CI/CD Tunnel provided by an Agent with other projects and groups
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 76840091112..44ed19b561d 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -561,7 +561,7 @@ module API
def increment_counter(event_name)
feature_name = "usage_data_#{event_name}"
- return unless Feature.enabled?(feature_name)
+ return unless Feature.enabled?(feature_name, default_enabled: :yaml)
Gitlab::UsageDataCounters.count(event_name)
rescue StandardError => error
diff --git a/lib/gitlab/database/gitlab_loose_foreign_keys.yml b/lib/gitlab/database/gitlab_loose_foreign_keys.yml
index 185ac8da6d7..93719c49cb2 100644
--- a/lib/gitlab/database/gitlab_loose_foreign_keys.yml
+++ b/lib/gitlab/database/gitlab_loose_foreign_keys.yml
@@ -94,9 +94,6 @@ ci_pipelines:
- table: users
column: user_id
on_delete: async_nullify
- - table: merge_requests
- column: merge_request_id
- on_delete: async_delete
ci_project_mirrors:
- table: projects
column: project_id
diff --git a/lib/gitlab/database/loose_foreign_keys.rb b/lib/gitlab/database/loose_foreign_keys.rb
index 1ecfb5ce47f..9a6aaf2ef7d 100644
--- a/lib/gitlab/database/loose_foreign_keys.rb
+++ b/lib/gitlab/database/loose_foreign_keys.rb
@@ -28,7 +28,11 @@ module Gitlab
end
def self.loose_foreign_keys_yaml
- @loose_foreign_keys_yaml ||= YAML.load_file(Rails.root.join('lib/gitlab/database/gitlab_loose_foreign_keys.yml'))
+ @loose_foreign_keys_yaml ||= YAML.load_file(self.loose_foreign_keys_yaml_path)
+ end
+
+ def self.loose_foreign_keys_yaml_path
+ @loose_foreign_keys_yaml_path ||= Rails.root.join('lib/gitlab/database/gitlab_loose_foreign_keys.yml')
end
private_class_method :build_definition
diff --git a/qa/qa/page/project/pipeline/index.rb b/qa/qa/page/project/pipeline/index.rb
index f7c5d149593..0dcaba4bc67 100644
--- a/qa/qa/page/project/pipeline/index.rb
+++ b/qa/qa/page/project/pipeline/index.rb
@@ -26,11 +26,11 @@ module QA
end
def wait_for_latest_pipeline_succeeded
- wait_for_latest_pipeline_status { has_text?('passed') }
+ wait_for_latest_pipeline_status { has_selector?(".ci-status-icon-success") }
end
def wait_for_latest_pipeline_completed
- wait_for_latest_pipeline_status { has_text?('passed') || has_text?('failed') }
+ wait_for_latest_pipeline_status { has_selector?(".ci-status-icon-success") || has_selector?(".ci-status-icon-failed") }
end
def wait_for_latest_pipeline_status
diff --git a/spec/features/cycle_analytics_spec.rb b/spec/features/cycle_analytics_spec.rb
index 8c12bc9a76d..03d61020ff0 100644
--- a/spec/features/cycle_analytics_spec.rb
+++ b/spec/features/cycle_analytics_spec.rb
@@ -11,7 +11,7 @@ RSpec.describe 'Value Stream Analytics', :js do
let_it_be(:stage_table_event_title_selector) { '[data-testid="vsa-stage-event-title"]' }
let_it_be(:stage_table_pagination_selector) { '[data-testid="vsa-stage-pagination"]' }
let_it_be(:stage_table_duration_column_header_selector) { '[data-testid="vsa-stage-header-duration"]' }
- let_it_be(:metrics_selector) { "[data-testid='vsa-time-metrics']" }
+ let_it_be(:metrics_selector) { "[data-testid='vsa-metrics']" }
let_it_be(:metric_value_selector) { "[data-testid='displayValue']" }
let(:stage_table) { find(stage_table_selector) }
diff --git a/spec/features/merge_request/user_sees_pipelines_spec.rb b/spec/features/merge_request/user_sees_pipelines_spec.rb
index 4967f58528e..266ae0d8c37 100644
--- a/spec/features/merge_request/user_sees_pipelines_spec.rb
+++ b/spec/features/merge_request/user_sees_pipelines_spec.rb
@@ -125,6 +125,7 @@ RSpec.describe 'Merge request > User sees pipelines', :js do
before do
stub_feature_flags(ci_disallow_to_create_merge_request_pipelines_in_target_project: false)
+ stub_feature_flags(rearrange_pipelines_table: false)
end
it 'creates a pipeline in the parent project when user proceeds with the warning' do
diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb
index fb45db213d0..e941ad7643e 100644
--- a/spec/features/projects/pipelines/pipelines_spec.rb
+++ b/spec/features/projects/pipelines/pipelines_spec.rb
@@ -159,7 +159,7 @@ RSpec.describe 'Pipelines', :js do
end
end
- context 'when pipeline is detached merge request pipeline' do
+ context 'when pipeline is detached merge request pipeline, with rearrange_pipelines_table feature flag turned off' do
let(:merge_request) do
create(:merge_request,
:with_detached_merge_request_pipeline,
@@ -172,6 +172,8 @@ RSpec.describe 'Pipelines', :js do
let(:target_project) { project }
before do
+ stub_feature_flags(rearrange_pipelines_table: false)
+
visit project_pipelines_path(source_project)
end
@@ -201,7 +203,47 @@ RSpec.describe 'Pipelines', :js do
end
end
- context 'when pipeline is merge request pipeline' do
+ context 'when pipeline is detached merge request pipeline, with rearrange_pipelines_table feature flag turned on' do
+ let(:merge_request) do
+ create(:merge_request,
+ :with_detached_merge_request_pipeline,
+ source_project: source_project,
+ target_project: target_project)
+ end
+
+ let!(:pipeline) { merge_request.all_pipelines.first }
+ let(:source_project) { project }
+ let(:target_project) { project }
+
+ before do
+ stub_feature_flags(rearrange_pipelines_table: true)
+
+ visit project_pipelines_path(source_project)
+ end
+
+ shared_examples_for 'detached merge request pipeline' do
+ it 'shows pipeline information without pipeline ref', :sidekiq_might_not_need_inline do
+ within '.pipeline-tags' do
+ expect(page).to have_content('detached')
+
+ expect(page).to have_link(merge_request.iid,
+ href: project_merge_request_path(project, merge_request))
+
+ expect(page).not_to have_link(pipeline.ref)
+ end
+ end
+ end
+
+ it_behaves_like 'detached merge request pipeline'
+
+ context 'when source project is a forked project' do
+ let(:source_project) { fork_project(project, user, repository: true) }
+
+ it_behaves_like 'detached merge request pipeline'
+ end
+ end
+
+ context 'when pipeline is merge request pipeline, with rearrange_pipelines_table feature flag turned off' do
let(:merge_request) do
create(:merge_request,
:with_merge_request_pipeline,
@@ -215,6 +257,8 @@ RSpec.describe 'Pipelines', :js do
let(:target_project) { project }
before do
+ stub_feature_flags(rearrange_pipelines_table: false)
+
visit project_pipelines_path(source_project)
end
@@ -244,6 +288,47 @@ RSpec.describe 'Pipelines', :js do
end
end
+ context 'when pipeline is merge request pipeline, with rearrange_pipelines_table feature flag turned on' do
+ let(:merge_request) do
+ create(:merge_request,
+ :with_merge_request_pipeline,
+ source_project: source_project,
+ target_project: target_project,
+ merge_sha: target_project.commit.sha)
+ end
+
+ let!(:pipeline) { merge_request.all_pipelines.first }
+ let(:source_project) { project }
+ let(:target_project) { project }
+
+ before do
+ stub_feature_flags(rearrange_pipelines_table: true)
+
+ visit project_pipelines_path(source_project)
+ end
+
+ shared_examples_for 'Correct merge request pipeline information' do
+ it 'does not show detached tag for the pipeline, and shows the link of the merge request, and does not show the ref of the pipeline', :sidekiq_might_not_need_inline do
+ within '.pipeline-tags' do
+ expect(page).not_to have_content('detached')
+
+ expect(page).to have_link(merge_request.iid,
+ href: project_merge_request_path(project, merge_request))
+
+ expect(page).not_to have_link(pipeline.ref)
+ end
+ end
+ end
+
+ it_behaves_like 'Correct merge request pipeline information'
+
+ context 'when source project is a forked project' do
+ let(:source_project) { fork_project(project, user, repository: true) }
+
+ it_behaves_like 'Correct merge request pipeline information'
+ end
+ end
+
context 'when pipeline has configuration errors' do
let(:pipeline) do
create(:ci_pipeline, :invalid, project: project)
@@ -587,6 +672,7 @@ RSpec.describe 'Pipelines', :js do
context 'with pipeline key selection' do
before do
+ stub_feature_flags(rearrange_pipelines_table: false)
visit project_pipelines_path(project)
wait_for_requests
end
@@ -604,6 +690,27 @@ RSpec.describe 'Pipelines', :js do
expect(page.find('[data-testid="pipeline-url-link"]')).to have_content "##{pipeline.iid}"
end
end
+
+ context 'with pipeline key selection and rearrange_pipelines_table ff on' do
+ before do
+ stub_feature_flags(rearrange_pipelines_table: true)
+ visit project_pipelines_path(project)
+ wait_for_requests
+ end
+
+ it 'changes the Pipeline ID column for Pipeline IID' do
+ page.find('[data-testid="pipeline-key-dropdown"]').click
+
+ within '.gl-new-dropdown-contents' do
+ dropdown_options = page.find_all '.gl-new-dropdown-item'
+
+ dropdown_options[1].click
+ end
+
+ expect(page.find('[data-testid="pipeline-th"]')).to have_content 'Pipeline'
+ expect(page.find('[data-testid="pipeline-url-link"]')).to have_content "##{pipeline.iid}"
+ end
+ end
end
describe 'GET /:project/-/pipelines/show' do
diff --git a/spec/frontend/cycle_analytics/metric_tile_spec.js b/spec/frontend/cycle_analytics/metric_tile_spec.js
new file mode 100644
index 00000000000..bebf3b8a65f
--- /dev/null
+++ b/spec/frontend/cycle_analytics/metric_tile_spec.js
@@ -0,0 +1,81 @@
+import { GlSingleStat } from '@gitlab/ui/dist/charts';
+import { shallowMount } from '@vue/test-utils';
+import MetricTile from '~/cycle_analytics/components/metric_tile.vue';
+import MetricPopover from '~/cycle_analytics/components/metric_popover.vue';
+import { redirectTo } from '~/lib/utils/url_utility';
+
+jest.mock('~/lib/utils/url_utility');
+
+describe('MetricTile', () => {
+ let wrapper;
+
+ const createComponent = (props = {}) => {
+ return shallowMount(MetricTile, {
+ propsData: {
+ metric: {},
+ ...props,
+ },
+ });
+ };
+
+ const findSingleStat = () => wrapper.findComponent(GlSingleStat);
+ const findPopover = () => wrapper.findComponent(MetricPopover);
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('template', () => {
+ describe('links', () => {
+ it('when the metric has links, it redirects the user on click', () => {
+ const metric = {
+ identifier: 'deploys',
+ value: '10',
+ label: 'Deploys',
+ links: [{ url: 'foo/bar' }],
+ };
+ wrapper = createComponent({ metric });
+
+ const singleStat = findSingleStat();
+ singleStat.vm.$emit('click');
+ expect(redirectTo).toHaveBeenCalledWith('foo/bar');
+ });
+
+ it("when the metric doesn't have links, it won't the user on click", () => {
+ const metric = { identifier: 'deploys', value: '10', label: 'Deploys' };
+ wrapper = createComponent({ metric });
+
+ const singleStat = findSingleStat();
+ singleStat.vm.$emit('click');
+ expect(redirectTo).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('decimal places', () => {
+ it(`will render 0 decimal places for an integer value`, () => {
+ const metric = { identifier: 'deploys', value: '10', label: 'Deploys' };
+ wrapper = createComponent({ metric });
+
+ const singleStat = findSingleStat();
+ expect(singleStat.props('animationDecimalPlaces')).toBe(0);
+ });
+
+ it(`will render 1 decimal place for a float value`, () => {
+ const metric = { identifier: 'deploys', value: '10.5', label: 'Deploys' };
+ wrapper = createComponent({ metric });
+
+ const singleStat = findSingleStat();
+ expect(singleStat.props('animationDecimalPlaces')).toBe(1);
+ });
+ });
+
+ it('renders a metric popover', () => {
+ const metric = { identifier: 'deploys', value: '10', label: 'Deploys' };
+ wrapper = createComponent({ metric });
+
+ const popover = findPopover();
+ expect(popover.exists()).toBe(true);
+ expect(popover.props()).toMatchObject({ metric, target: metric.identifier });
+ });
+ });
+});
diff --git a/spec/frontend/cycle_analytics/value_stream_metrics_spec.js b/spec/frontend/cycle_analytics/value_stream_metrics_spec.js
index 5a519a07192..a3a31ebc387 100644
--- a/spec/frontend/cycle_analytics/value_stream_metrics_spec.js
+++ b/spec/frontend/cycle_analytics/value_stream_metrics_spec.js
@@ -1,17 +1,15 @@
import { GlDeprecatedSkeletonLoading as GlSkeletonLoading } from '@gitlab/ui';
-import { GlSingleStat } from '@gitlab/ui/dist/charts';
import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
import metricsData from 'test_fixtures/projects/analytics/value_stream_analytics/summary.json';
import waitForPromises from 'helpers/wait_for_promises';
import { METRIC_TYPE_SUMMARY } from '~/api/analytics_api';
import ValueStreamMetrics from '~/cycle_analytics/components/value_stream_metrics.vue';
+import MetricTile from '~/cycle_analytics/components/metric_tile.vue';
import createFlash from '~/flash';
-import { redirectTo } from '~/lib/utils/url_utility';
import { group } from './mock_data';
jest.mock('~/flash');
-jest.mock('~/lib/utils/url_utility');
describe('ValueStreamMetrics', () => {
let wrapper;
@@ -35,7 +33,7 @@ describe('ValueStreamMetrics', () => {
});
};
- const findMetrics = () => wrapper.findAllComponents(GlSingleStat);
+ const findMetrics = () => wrapper.findAllComponents(MetricTile);
const expectToHaveRequest = (fields) => {
expect(mockGetValueStreamSummaryMetrics).toHaveBeenCalledWith({
@@ -61,7 +59,7 @@ describe('ValueStreamMetrics', () => {
expect(wrapper.findComponent(GlSkeletonLoading).exists()).toBe(true);
});
- it('renders hidden GlSingleStat components for each metric', async () => {
+ it('renders hidden MetricTile components for each metric', async () => {
await waitForPromises();
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
@@ -89,34 +87,17 @@ describe('ValueStreamMetrics', () => {
});
describe.each`
- index | value | title | unit | animationDecimalPlaces | clickable
- ${0} | ${metricsData[0].value} | ${metricsData[0].title} | ${metricsData[0].unit} | ${0} | ${false}
- ${1} | ${metricsData[1].value} | ${metricsData[1].title} | ${metricsData[1].unit} | ${0} | ${false}
- ${2} | ${metricsData[2].value} | ${metricsData[2].title} | ${metricsData[2].unit} | ${0} | ${false}
- ${3} | ${metricsData[3].value} | ${metricsData[3].title} | ${metricsData[3].unit} | ${1} | ${true}
- `('metric tiles', ({ index, value, title, unit, animationDecimalPlaces, clickable }) => {
- it(`renders a single stat component for "${title}" with value and unit`, () => {
+ index | identifier | value | label
+ ${0} | ${metricsData[0].identifier} | ${metricsData[0].value} | ${metricsData[0].title}
+ ${1} | ${metricsData[1].identifier} | ${metricsData[1].value} | ${metricsData[1].title}
+ ${2} | ${metricsData[2].identifier} | ${metricsData[2].value} | ${metricsData[2].title}
+ ${3} | ${metricsData[3].identifier} | ${metricsData[3].value} | ${metricsData[3].title}
+ `('metric tiles', ({ identifier, index, value, label }) => {
+ it(`renders a metric tile component for "${label}"`, () => {
const metric = findMetrics().at(index);
- expect(metric.props()).toMatchObject({ value, title, unit: unit ?? '' });
+ expect(metric.props('metric')).toMatchObject({ identifier, value, label });
expect(metric.isVisible()).toBe(true);
});
-
- it(`${
- clickable ? 'redirects' : "doesn't redirect"
- } when the user clicks the "${title}" metric`, () => {
- const metric = findMetrics().at(index);
- metric.vm.$emit('click');
- if (clickable) {
- expect(redirectTo).toHaveBeenCalledWith(metricsData[index].links[0].url);
- } else {
- expect(redirectTo).not.toHaveBeenCalled();
- }
- });
-
- it(`will render ${animationDecimalPlaces} decimal places for the ${title} metric with the value "${value}"`, () => {
- const metric = findMetrics().at(index);
- expect(metric.props('animationDecimalPlaces')).toBe(animationDecimalPlaces);
- });
});
it('will not display a loading icon', () => {
diff --git a/spec/frontend/pipelines/mock_data.js b/spec/frontend/pipelines/mock_data.js
index b9d20eb7ca5..5af76adedde 100644
--- a/spec/frontend/pipelines/mock_data.js
+++ b/spec/frontend/pipelines/mock_data.js
@@ -634,3 +634,80 @@ export const mockPipelineJobsQueryResponse = {
},
},
};
+
+export const mockPipeline = (projectPath) => {
+ return {
+ pipeline: {
+ id: 1,
+ user: {
+ id: 1,
+ name: 'Administrator',
+ username: 'root',
+ state: 'active',
+ avatar_url: '',
+ web_url: 'http://0.0.0.0:3000/root',
+ show_status: false,
+ path: '/root',
+ },
+ active: false,
+ source: 'merge_request_event',
+ created_at: '2021-10-19T21:17:38.698Z',
+ updated_at: '2021-10-21T18:00:42.758Z',
+ path: 'foo',
+ flags: {},
+ merge_request: {
+ iid: 1,
+ path: `/${projectPath}/1`,
+ title: 'commit',
+ source_branch: 'test-commit-name',
+ source_branch_path: `/${projectPath}`,
+ target_branch: 'main',
+ target_branch_path: `/${projectPath}/-/commit/main`,
+ },
+ ref: {
+ name: 'refs/merge-requests/1/head',
+ path: `/${projectPath}/-/commits/refs/merge-requests/1/head`,
+ tag: false,
+ branch: false,
+ merge_request: true,
+ },
+ commit: {
+ id: 'fd6df5b3229e213c97d308844a6f3e7fd71e8f8c',
+ short_id: 'fd6df5b3',
+ created_at: '2021-10-19T21:17:12.000+00:00',
+ parent_ids: ['7147906b84306e83cb3fec6582a25390b75713c6'],
+ title: 'Commit',
+ message: 'Commit',
+ author_name: 'Administrator',
+ author_email: 'admin@example.com',
+ authored_date: '2021-10-19T21:17:12.000+00:00',
+ committer_name: 'Administrator',
+ committer_email: 'admin@example.com',
+ committed_date: '2021-10-19T21:17:12.000+00:00',
+ trailers: {},
+ web_url: '',
+ author: {
+ id: 1,
+ name: 'Administrator',
+ username: 'root',
+ state: 'active',
+ avatar_url: '',
+ web_url: '',
+ show_status: false,
+ path: '/root',
+ },
+ author_gravatar_url: '',
+ commit_url: `/${projectPath}/fd6df5b3229e213c97d308844a6f3e7fd71e8f8c`,
+ commit_path: `/${projectPath}/commit/fd6df5b3229e213c97d308844a6f3e7fd71e8f8c`,
+ },
+ project: {
+ full_path: `/${projectPath}`,
+ },
+ triggered_by: null,
+ triggered: [],
+ },
+ pipelineScheduleUrl: 'foo',
+ pipelineKey: 'id',
+ viewType: 'root',
+ };
+};
diff --git a/spec/frontend/pipelines/pipeline_url_spec.js b/spec/frontend/pipelines/pipeline_url_spec.js
index 912b5afe0e1..d7423c6614b 100644
--- a/spec/frontend/pipelines/pipeline_url_spec.js
+++ b/spec/frontend/pipelines/pipeline_url_spec.js
@@ -1,41 +1,41 @@
-import { shallowMount } from '@vue/test-utils';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { trimText } from 'helpers/text_helper';
import PipelineUrlComponent from '~/pipelines/components/pipelines_list/pipeline_url.vue';
+import { mockPipeline } from './mock_data';
const projectPath = 'test/test';
describe('Pipeline Url Component', () => {
let wrapper;
- const findTableCell = () => wrapper.find('[data-testid="pipeline-url-table-cell"]');
- const findPipelineUrlLink = () => wrapper.find('[data-testid="pipeline-url-link"]');
- const findScheduledTag = () => wrapper.find('[data-testid="pipeline-url-scheduled"]');
- const findLatestTag = () => wrapper.find('[data-testid="pipeline-url-latest"]');
- const findYamlTag = () => wrapper.find('[data-testid="pipeline-url-yaml"]');
- const findFailureTag = () => wrapper.find('[data-testid="pipeline-url-failure"]');
- const findAutoDevopsTag = () => wrapper.find('[data-testid="pipeline-url-autodevops"]');
- const findAutoDevopsTagLink = () => wrapper.find('[data-testid="pipeline-url-autodevops-link"]');
- const findStuckTag = () => wrapper.find('[data-testid="pipeline-url-stuck"]');
- const findDetachedTag = () => wrapper.find('[data-testid="pipeline-url-detached"]');
- const findForkTag = () => wrapper.find('[data-testid="pipeline-url-fork"]');
- const findTrainTag = () => wrapper.find('[data-testid="pipeline-url-train"]');
-
- const defaultProps = {
- pipeline: {
- id: 1,
- path: 'foo',
- project: { full_path: `/${projectPath}` },
- flags: {},
- },
- pipelineScheduleUrl: 'foo',
- pipelineKey: 'id',
- };
-
- const createComponent = (props) => {
- wrapper = shallowMount(PipelineUrlComponent, {
+ const findTableCell = () => wrapper.findByTestId('pipeline-url-table-cell');
+ const findPipelineUrlLink = () => wrapper.findByTestId('pipeline-url-link');
+ const findScheduledTag = () => wrapper.findByTestId('pipeline-url-scheduled');
+ const findLatestTag = () => wrapper.findByTestId('pipeline-url-latest');
+ const findYamlTag = () => wrapper.findByTestId('pipeline-url-yaml');
+ const findFailureTag = () => wrapper.findByTestId('pipeline-url-failure');
+ const findAutoDevopsTag = () => wrapper.findByTestId('pipeline-url-autodevops');
+ const findAutoDevopsTagLink = () => wrapper.findByTestId('pipeline-url-autodevops-link');
+ const findStuckTag = () => wrapper.findByTestId('pipeline-url-stuck');
+ const findDetachedTag = () => wrapper.findByTestId('pipeline-url-detached');
+ const findForkTag = () => wrapper.findByTestId('pipeline-url-fork');
+ const findTrainTag = () => wrapper.findByTestId('pipeline-url-train');
+ const findRefName = () => wrapper.findByTestId('merge-request-ref');
+ const findCommitShortSha = () => wrapper.findByTestId('commit-short-sha');
+
+ const findCommitTitleContainer = () => wrapper.findByTestId('commit-title-container');
+ const findCommitTitle = (commitWrapper) => commitWrapper.find('[data-testid="commit-title"]');
+
+ const defaultProps = mockPipeline(projectPath);
+
+ const createComponent = (props, rearrangePipelinesTable = false) => {
+ wrapper = shallowMountExtended(PipelineUrlComponent, {
propsData: { ...defaultProps, ...props },
provide: {
targetProjectFullPath: projectPath,
+ glFeatures: {
+ rearrangePipelinesTable,
+ },
},
});
};
@@ -45,158 +45,169 @@ describe('Pipeline Url Component', () => {
wrapper = null;
});
- it('should render pipeline url table cell', () => {
- createComponent();
+ describe('with the rearrangePipelinesTable feature flag turned off', () => {
+ it('should render pipeline url table cell', () => {
+ createComponent();
- expect(findTableCell().exists()).toBe(true);
- });
+ expect(findTableCell().exists()).toBe(true);
+ });
- it('should render a link the provided path and id', () => {
- createComponent();
+ it('should render a link the provided path and id', () => {
+ createComponent();
- expect(findPipelineUrlLink().attributes('href')).toBe('foo');
+ expect(findPipelineUrlLink().attributes('href')).toBe('foo');
- expect(findPipelineUrlLink().text()).toBe('#1');
- });
+ expect(findPipelineUrlLink().text()).toBe('#1');
+ });
- it('should not render tags when flags are not set', () => {
- createComponent();
-
- expect(findStuckTag().exists()).toBe(false);
- expect(findLatestTag().exists()).toBe(false);
- expect(findYamlTag().exists()).toBe(false);
- expect(findAutoDevopsTag().exists()).toBe(false);
- expect(findFailureTag().exists()).toBe(false);
- expect(findScheduledTag().exists()).toBe(false);
- expect(findForkTag().exists()).toBe(false);
- expect(findTrainTag().exists()).toBe(false);
- });
+ it('should not render tags when flags are not set', () => {
+ createComponent();
+
+ expect(findStuckTag().exists()).toBe(false);
+ expect(findLatestTag().exists()).toBe(false);
+ expect(findYamlTag().exists()).toBe(false);
+ expect(findAutoDevopsTag().exists()).toBe(false);
+ expect(findFailureTag().exists()).toBe(false);
+ expect(findScheduledTag().exists()).toBe(false);
+ expect(findForkTag().exists()).toBe(false);
+ expect(findTrainTag().exists()).toBe(false);
+ });
- it('should render the stuck tag when flag is provided', () => {
- createComponent({
- pipeline: {
- flags: {
- stuck: true,
- },
- },
+ it('should render the stuck tag when flag is provided', () => {
+ const stuckPipeline = defaultProps.pipeline;
+ stuckPipeline.flags.stuck = true;
+
+ createComponent({
+ ...stuckPipeline.pipeline,
+ });
+
+ expect(findStuckTag().text()).toContain('stuck');
});
- expect(findStuckTag().text()).toContain('stuck');
- });
+ it('should render latest tag when flag is provided', () => {
+ const latestPipeline = defaultProps.pipeline;
+ latestPipeline.flags.latest = true;
- it('should render latest tag when flag is provided', () => {
- createComponent({
- pipeline: {
- flags: {
- latest: true,
- },
- },
+ createComponent({
+ ...latestPipeline,
+ });
+
+ expect(findLatestTag().text()).toContain('latest');
});
- expect(findLatestTag().text()).toContain('latest');
- });
+ it('should render a yaml badge when it is invalid', () => {
+ const yamlPipeline = defaultProps.pipeline;
+ yamlPipeline.flags.yaml_errors = true;
- it('should render a yaml badge when it is invalid', () => {
- createComponent({
- pipeline: {
- flags: {
- yaml_errors: true,
- },
- },
+ createComponent({
+ ...yamlPipeline,
+ });
+
+ expect(findYamlTag().text()).toContain('yaml invalid');
});
- expect(findYamlTag().text()).toContain('yaml invalid');
- });
+ it('should render an autodevops badge when flag is provided', () => {
+ const autoDevopsPipeline = defaultProps.pipeline;
+ autoDevopsPipeline.flags.auto_devops = true;
- it('should render an autodevops badge when flag is provided', () => {
- createComponent({
- pipeline: {
- ...defaultProps.pipeline,
- flags: {
- auto_devops: true,
- },
- },
+ createComponent({
+ ...autoDevopsPipeline,
+ });
+
+ expect(trimText(findAutoDevopsTag().text())).toBe('Auto DevOps');
+
+ expect(findAutoDevopsTagLink().attributes()).toMatchObject({
+ href: '/help/topics/autodevops/index.md',
+ target: '_blank',
+ });
});
- expect(trimText(findAutoDevopsTag().text())).toBe('Auto DevOps');
+ it('should render a detached badge when flag is provided', () => {
+ const detachedMRPipeline = defaultProps.pipeline;
+ detachedMRPipeline.flags.detached_merge_request_pipeline = true;
- expect(findAutoDevopsTagLink().attributes()).toMatchObject({
- href: '/help/topics/autodevops/index.md',
- target: '_blank',
+ createComponent({
+ ...detachedMRPipeline,
+ });
+
+ expect(findDetachedTag().text()).toContain('detached');
});
- });
- it('should render a detached badge when flag is provided', () => {
- createComponent({
- pipeline: {
- flags: {
- detached_merge_request_pipeline: true,
- },
- },
+ it('should render error badge when pipeline has a failure reason set', () => {
+ const failedPipeline = defaultProps.pipeline;
+ failedPipeline.flags.failure_reason = true;
+ failedPipeline.failure_reason = 'some reason';
+
+ createComponent({
+ ...failedPipeline,
+ });
+
+ expect(findFailureTag().text()).toContain('error');
+ expect(findFailureTag().attributes('title')).toContain('some reason');
});
- expect(findDetachedTag().text()).toContain('detached');
- });
+ it('should render scheduled badge when pipeline was triggered by a schedule', () => {
+ const scheduledPipeline = defaultProps.pipeline;
+ scheduledPipeline.source = 'schedule';
- it('should render error badge when pipeline has a failure reason set', () => {
- createComponent({
- pipeline: {
- flags: {
- failure_reason: true,
- },
- failure_reason: 'some reason',
- },
+ createComponent({
+ ...scheduledPipeline,
+ });
+
+ expect(findScheduledTag().exists()).toBe(true);
+ expect(findScheduledTag().text()).toContain('Scheduled');
});
- expect(findFailureTag().text()).toContain('error');
- expect(findFailureTag().attributes('title')).toContain('some reason');
- });
+ it('should render the fork badge when the pipeline was run in a fork', () => {
+ const forkedPipeline = defaultProps.pipeline;
+ forkedPipeline.project.full_path = '/test/forked';
- it('should render scheduled badge when pipeline was triggered by a schedule', () => {
- createComponent({
- pipeline: {
- flags: {},
- source: 'schedule',
- },
+ createComponent({
+ ...forkedPipeline,
+ });
+
+ expect(findForkTag().exists()).toBe(true);
+ expect(findForkTag().text()).toBe('fork');
});
- expect(findScheduledTag().exists()).toBe(true);
- expect(findScheduledTag().text()).toContain('Scheduled');
- });
+ it('should render the train badge when the pipeline is a merge train pipeline', () => {
+ const mergeTrainPipeline = defaultProps.pipeline;
+ mergeTrainPipeline.flags.merge_train_pipeline = true;
- it('should render the fork badge when the pipeline was run in a fork', () => {
- createComponent({
- pipeline: {
- flags: {},
- project: { fullPath: '/test/forked' },
- },
+ createComponent({
+ ...mergeTrainPipeline,
+ });
+
+ expect(findTrainTag().text()).toContain('train');
});
- expect(findForkTag().exists()).toBe(true);
- expect(findForkTag().text()).toBe('fork');
- });
+ it('should not render the train badge when the pipeline is not a merge train pipeline', () => {
+ const mergeTrainPipeline = defaultProps.pipeline;
+ mergeTrainPipeline.flags.merge_train_pipeline = false;
- it('should render the train badge when the pipeline is a merge train pipeline', () => {
- createComponent({
- pipeline: {
- flags: {
- merge_train_pipeline: true,
- },
- },
+ createComponent({
+ ...mergeTrainPipeline,
+ });
+
+ expect(findTrainTag().exists()).toBe(false);
});
- expect(findTrainTag().text()).toContain('train');
- });
+ it('should not render the commit wrapper and commit-short-sha', () => {
+ createComponent();
- it('should not render the train badge when the pipeline is not a merge train pipeline', () => {
- createComponent({
- pipeline: {
- flags: {
- merge_train_pipeline: false,
- },
- },
+ expect(findCommitTitleContainer().exists()).toBe(false);
+ expect(findCommitShortSha().exists()).toBe(false);
});
+ });
+
+ describe('with the rearrangePipelinesTable feature flag turned on', () => {
+ it('should render the commit title, commit reference and commit-short-sha', () => {
+ createComponent({}, true);
+ const commitWrapper = findCommitTitleContainer();
- expect(findTrainTag().exists()).toBe(false);
+ expect(findCommitTitle(commitWrapper).exists()).toBe(true);
+ expect(findRefName().exists()).toBe(true);
+ expect(findCommitShortSha().exists()).toBe(true);
+ });
});
});
diff --git a/spec/frontend/pipelines/pipelines_table_spec.js b/spec/frontend/pipelines/pipelines_table_spec.js
index 6fdbe907aed..3c6cb3016e8 100644
--- a/spec/frontend/pipelines/pipelines_table_spec.js
+++ b/spec/frontend/pipelines/pipelines_table_spec.js
@@ -33,13 +33,18 @@ describe('Pipelines Table', () => {
return pipelines.find((p) => p.user !== null && p.commit !== null);
};
- const createComponent = (props = {}) => {
+ const createComponent = (props = {}, rearrangePipelinesTable = false) => {
wrapper = extendedWrapper(
mount(PipelinesTable, {
propsData: {
...defaultProps,
...props,
},
+ provide: {
+ glFeatures: {
+ rearrangePipelinesTable,
+ },
+ },
}),
);
};
@@ -71,7 +76,7 @@ describe('Pipelines Table', () => {
wrapper = null;
});
- describe('Pipelines Table', () => {
+ describe('Pipelines Table with rearrangePipelinesTable feature flag turned off', () => {
beforeEach(() => {
createComponent({ pipelines: [pipeline], viewType: 'root' });
});
@@ -189,4 +194,29 @@ describe('Pipelines Table', () => {
});
});
});
+
+ describe('Pipelines Table with rearrangePipelinesTable feature flag turned on', () => {
+ beforeEach(() => {
+ createComponent({ pipelines: [pipeline], viewType: 'root' }, true);
+ });
+
+ it('should render table head with correct columns', () => {
+ expect(findStatusTh().text()).toBe('Status');
+ expect(findPipelineTh().text()).toBe('Pipeline');
+ expect(findStagesTh().text()).toBe('Stages');
+ expect(findActionsTh().text()).toBe('Actions');
+ });
+
+ describe('triggerer cell', () => {
+ it('should render the pipeline triggerer', () => {
+ expect(findTriggerer().exists()).toBe(true);
+ });
+ });
+
+ describe('commit cell', () => {
+ it('should not render commit information', () => {
+ expect(findCommit().exists()).toBe(false);
+ });
+ });
+ });
});
diff --git a/spec/lib/gitlab/database/loose_foreign_keys_spec.rb b/spec/lib/gitlab/database/loose_foreign_keys_spec.rb
index 13f2d31bc32..297bcc27bcd 100644
--- a/spec/lib/gitlab/database/loose_foreign_keys_spec.rb
+++ b/spec/lib/gitlab/database/loose_foreign_keys_spec.rb
@@ -18,6 +18,36 @@ RSpec.describe Gitlab::Database::LooseForeignKeys do
))
end
+ context 'ensure no duplicates are found' do
+ it 'does not have duplicate tables defined' do
+ # since we use hash to detect duplicate hash keys we need to parse YAML document
+ parsed = YAML.parse_file(described_class.loose_foreign_keys_yaml_path)
+ expect(parsed).to be_document
+ expect(parsed.children).to be_one, "YAML has a single document"
+
+ # require hash
+ mapping = parsed.children.first
+ expect(mapping).to be_mapping, "YAML has a top-level hash"
+
+ # find all scalars with names
+ table_names = mapping.children.select(&:scalar?).map(&:value)
+ expect(table_names).not_to be_empty, "YAML has a non-zero tables defined"
+
+ # expect to not have duplicates
+ expect(table_names).to contain_exactly(*table_names.uniq)
+ end
+
+ it 'does not have duplicate column definitions' do
+ # ignore other modifiers
+ all_definitions = definitions.map do |definition|
+ { from_table: definition.from_table, to_table: definition.to_table, column: definition.column }
+ end
+
+ # expect to not have duplicates
+ expect(all_definitions).to contain_exactly(*all_definitions.uniq)
+ end
+ end
+
describe 'ensuring database integrity' do
def base_models_for(table)
parent_table_schema = Gitlab::Database::GitlabSchema.table_schema(table)
diff --git a/spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb b/spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb
index 4867940c41e..e46b30e126b 100644
--- a/spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb
+++ b/spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb
@@ -32,8 +32,6 @@ RSpec.describe 'cross-database foreign keys' do
ci_sources_projects.source_project_id
ci_stages.project_id
ci_unit_tests.project_id
- dast_site_profiles_pipelines.ci_pipeline_id
- vulnerability_feedback.pipeline_id
).freeze
end
diff --git a/spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb b/spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb
index adb613c3abc..ce888b71d5e 100644
--- a/spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb
+++ b/spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb
@@ -240,7 +240,7 @@ RSpec.describe Gitlab::ImportExport::FastHashSerializer do
merge_request = create(:merge_request, source_project: project, milestone: milestone)
ci_build = create(:ci_build, project: project, when: nil)
- ci_build.pipeline.update(project: project)
+ ci_build.pipeline.update!(project: project)
create(:commit_status, project: project, pipeline: ci_build.pipeline)
create_list(:ci_pipeline, 5, :success, project: project)
diff --git a/spec/lib/gitlab/import_export/fork_spec.rb b/spec/lib/gitlab/import_export/fork_spec.rb
index 65c28a8b8a2..25c82588c13 100644
--- a/spec/lib/gitlab/import_export/fork_spec.rb
+++ b/spec/lib/gitlab/import_export/fork_spec.rb
@@ -38,8 +38,8 @@ RSpec.describe 'forked project import' do
allow(instance).to receive(:storage_path).and_return(export_path)
end
- saver.save
- repo_saver.save
+ saver.save # rubocop:disable Rails/SaveBang
+ repo_saver.save # rubocop:disable Rails/SaveBang
repo_restorer.restore
restorer.restore
diff --git a/spec/lib/gitlab/import_export/group/legacy_tree_saver_spec.rb b/spec/lib/gitlab/import_export/group/legacy_tree_saver_spec.rb
index e075c5acfea..31d647f883a 100644
--- a/spec/lib/gitlab/import_export/group/legacy_tree_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/group/legacy_tree_saver_spec.rb
@@ -31,7 +31,7 @@ RSpec.describe Gitlab::ImportExport::Group::LegacyTreeSaver do
# ^ These are specific for the Group::LegacyTreeSaver
context 'JSON' do
let(:saved_group_json) do
- group_tree_saver.save
+ group_tree_saver.save # rubocop:disable Rails/SaveBang
group_json(group_tree_saver.full_path)
end
@@ -88,7 +88,7 @@ RSpec.describe Gitlab::ImportExport::Group::LegacyTreeSaver do
end
before do
- user2.update(public_email: user2.email)
+ user2.update!(public_email: user2.email)
group.add_developer(user2)
end
diff --git a/spec/lib/gitlab/import_export/group/relation_factory_spec.rb b/spec/lib/gitlab/import_export/group/relation_factory_spec.rb
index 63286fc0719..8e7fe8849d4 100644
--- a/spec/lib/gitlab/import_export/group/relation_factory_spec.rb
+++ b/spec/lib/gitlab/import_export/group/relation_factory_spec.rb
@@ -9,7 +9,7 @@ RSpec.describe Gitlab::ImportExport::Group::RelationFactory do
let(:importer_user) { admin }
let(:excluded_keys) { [] }
let(:created_object) do
- described_class.create(
+ described_class.create( # rubocop:disable Rails/SaveBang
relation_sym: relation_sym,
relation_hash: relation_hash,
relation_index: 1,
diff --git a/spec/lib/gitlab/import_export/group/tree_saver_spec.rb b/spec/lib/gitlab/import_export/group/tree_saver_spec.rb
index c52daa8ccfd..de4d193a21c 100644
--- a/spec/lib/gitlab/import_export/group/tree_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/group/tree_saver_spec.rb
@@ -42,7 +42,7 @@ RSpec.describe Gitlab::ImportExport::Group::TreeSaver do
context 'exported files' do
before do
- group_tree_saver.save
+ group_tree_saver.save # rubocop:disable Rails/SaveBang
end
it 'has one group per line' do
diff --git a/spec/lib/gitlab/import_export/repo_restorer_spec.rb b/spec/lib/gitlab/import_export/repo_restorer_spec.rb
index 718a23f80a1..c0215ff5843 100644
--- a/spec/lib/gitlab/import_export/repo_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/repo_restorer_spec.rb
@@ -19,7 +19,7 @@ RSpec.describe Gitlab::ImportExport::RepoRestorer do
before do
allow(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
- bundler.save
+ bundler.save # rubocop:disable Rails/SaveBang
end
after do
diff --git a/spec/lib/gitlab/import_export/saver_spec.rb b/spec/lib/gitlab/import_export/saver_spec.rb
index 90accfaf3a9..f5eed81f73c 100644
--- a/spec/lib/gitlab/import_export/saver_spec.rb
+++ b/spec/lib/gitlab/import_export/saver_spec.rb
@@ -30,7 +30,7 @@ RSpec.describe Gitlab::ImportExport::Saver do
it 'saves the repo using object storage' do
stub_uploads_object_storage(ImportExportUploader)
- subject.save
+ subject.save # rubocop:disable Rails/SaveBang
expect(ImportExportUpload.find_by(project: project).export_file.url)
.to match(%r[\/uploads\/-\/system\/import_export_upload\/export_file.*])
@@ -59,13 +59,13 @@ RSpec.describe Gitlab::ImportExport::Saver do
upload_bytes: anything
)).and_call_original
- subject.save
+ subject.save # rubocop:disable Rails/SaveBang
end
it 'removes archive path and keeps base path untouched' do
allow(shared).to receive(:archive_path).and_return(archive_path)
- subject.save
+ subject.save # rubocop:disable Rails/SaveBang
expect(FileUtils).not_to have_received(:rm_rf).with(base_path)
expect(FileUtils).to have_received(:rm_rf).with(archive_path)
@@ -86,7 +86,7 @@ RSpec.describe Gitlab::ImportExport::Saver do
'correlation_id' => anything
)).and_call_original
- subject.save
+ subject.save # rubocop:disable Rails/SaveBang
end
end
end
diff --git a/spec/lib/gitlab/import_export/snippet_repo_restorer_spec.rb b/spec/lib/gitlab/import_export/snippet_repo_restorer_spec.rb
index 7d719b6028f..2f39cb560d0 100644
--- a/spec/lib/gitlab/import_export/snippet_repo_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/snippet_repo_restorer_spec.rb
@@ -70,7 +70,7 @@ RSpec.describe Gitlab::ImportExport::SnippetRepoRestorer do
let!(:snippet_with_repo) { create(:project_snippet, :repository, project: project, author: user) }
let(:bundle_path) { ::Gitlab::ImportExport.snippets_repo_bundle_path(shared.export_path) }
let(:snippet_bundle_path) { File.join(bundle_path, "#{snippet_with_repo.hexdigest}.bundle") }
- let(:result) { exporter.save }
+ let(:result) { exporter.save } # rubocop:disable Rails/SaveBang
let(:repository) { snippet.repository }
before do
diff --git a/spec/lib/gitlab/import_export/snippet_repo_saver_spec.rb b/spec/lib/gitlab/import_export/snippet_repo_saver_spec.rb
index 9f3e8d2fa86..9a9f40b3d0c 100644
--- a/spec/lib/gitlab/import_export/snippet_repo_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/snippet_repo_saver_spec.rb
@@ -38,7 +38,7 @@ RSpec.describe Gitlab::ImportExport::SnippetRepoSaver do
aggregate_failures do
expect(snippet.repository).not_to receive(:bundle_to_disk)
- bundler.save
+ bundler.save # rubocop:disable Rails/SaveBang
expect(Dir.empty?(bundle_path)).to be_truthy
end
diff --git a/spec/lib/gitlab/import_export/snippets_repo_restorer_spec.rb b/spec/lib/gitlab/import_export/snippets_repo_restorer_spec.rb
index 7ca365762b5..e529d36fd11 100644
--- a/spec/lib/gitlab/import_export/snippets_repo_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/snippets_repo_restorer_spec.rb
@@ -64,7 +64,7 @@ RSpec.describe Gitlab::ImportExport::SnippetsRepoRestorer do
let!(:snippet2) { create(:project_snippet, project: project, author: user) }
before do
- exporter.save
+ exporter.save # rubocop:disable Rails/SaveBang
expect(File.exist?(bundle_path(snippet1))).to be true
expect(File.exist?(bundle_path(snippet2))).to be false
@@ -78,7 +78,7 @@ RSpec.describe Gitlab::ImportExport::SnippetsRepoRestorer do
let!(:snippet2) { create(:project_snippet, :repository, project: project, author: user) }
before do
- exporter.save
+ exporter.save # rubocop:disable Rails/SaveBang
expect(File.exist?(bundle_path(snippet1))).to be true
expect(File.exist?(bundle_path(snippet2))).to be true
diff --git a/spec/lib/gitlab/import_export/snippets_repo_saver_spec.rb b/spec/lib/gitlab/import_export/snippets_repo_saver_spec.rb
index aa284c60e73..eaa58c77aff 100644
--- a/spec/lib/gitlab/import_export/snippets_repo_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/snippets_repo_saver_spec.rb
@@ -18,7 +18,7 @@ RSpec.describe Gitlab::ImportExport::SnippetsRepoSaver do
snippets_dir = ::Gitlab::ImportExport.snippets_repo_bundle_path(shared.export_path)
expect(Dir.exist?(snippets_dir)).to be_falsey
- bundler.save
+ bundler.save # rubocop:disable Rails/SaveBang
expect(Dir.exist?(snippets_dir)).to be_truthy
end
@@ -27,7 +27,7 @@ RSpec.describe Gitlab::ImportExport::SnippetsRepoSaver do
it 'does not perform any action' do
expect(Gitlab::ImportExport::SnippetRepoSaver).not_to receive(:new)
- bundler.save
+ bundler.save # rubocop:disable Rails/SaveBang
end
end
@@ -40,7 +40,7 @@ RSpec.describe Gitlab::ImportExport::SnippetsRepoSaver do
allow(Gitlab::ImportExport::SnippetRepoSaver).to receive(:new).and_return(service)
expect(service).to receive(:save).and_return(true).twice
- bundler.save
+ bundler.save # rubocop:disable Rails/SaveBang
end
context 'when one snippet cannot be saved' do
diff --git a/spec/lib/gitlab/import_export/uploads_manager_spec.rb b/spec/lib/gitlab/import_export/uploads_manager_spec.rb
index 8282ad9a070..0cfe3a69a09 100644
--- a/spec/lib/gitlab/import_export/uploads_manager_spec.rb
+++ b/spec/lib/gitlab/import_export/uploads_manager_spec.rb
@@ -31,13 +31,13 @@ RSpec.describe Gitlab::ImportExport::UploadsManager do
let(:upload) { create(:upload, :issuable_upload, :with_file, model: project) }
it 'does not cause errors' do
- manager.save
+ manager.save # rubocop:disable Rails/SaveBang
expect(shared.errors).to be_empty
end
it 'copies the file in the correct location when there is an upload' do
- manager.save
+ manager.save # rubocop:disable Rails/SaveBang
expect(File).to exist(exported_file_path)
end
@@ -56,7 +56,7 @@ RSpec.describe Gitlab::ImportExport::UploadsManager do
end
it 'excludes orphaned upload files' do
- manager.save
+ manager.save # rubocop:disable Rails/SaveBang
expect(File).not_to exist(exported_orphan_path)
end
@@ -68,7 +68,7 @@ RSpec.describe Gitlab::ImportExport::UploadsManager do
end
it 'does not cause errors' do
- manager.save
+ manager.save # rubocop:disable Rails/SaveBang
expect(shared.errors).to be_empty
end
@@ -84,7 +84,7 @@ RSpec.describe Gitlab::ImportExport::UploadsManager do
it 'ignores problematic upload and logs exception' do
expect(Gitlab::ErrorTracking).to receive(:log_exception).with(instance_of(Errno::ENAMETOOLONG), project_id: project.id)
- manager.save
+ manager.save # rubocop:disable Rails/SaveBang
expect(shared.errors).to be_empty
expect(File).not_to exist(exported_file_path)
diff --git a/spec/requests/api/usage_data_spec.rb b/spec/requests/api/usage_data_spec.rb
index d1d8e377233..aefccc4fbf7 100644
--- a/spec/requests/api/usage_data_spec.rb
+++ b/spec/requests/api/usage_data_spec.rb
@@ -86,6 +86,7 @@ RSpec.describe API::UsageData do
context 'with unknown event' do
before do
skip_feature_flags_yaml_validation
+ skip_default_enabled_yaml_check
end
it 'returns status ok' do
diff --git a/spec/services/packages/nuget/metadata_extraction_service_spec.rb b/spec/services/packages/nuget/metadata_extraction_service_spec.rb
index 8eddd27f8a2..fc21cfd502e 100644
--- a/spec/services/packages/nuget/metadata_extraction_service_spec.rb
+++ b/spec/services/packages/nuget/metadata_extraction_service_spec.rb
@@ -78,7 +78,7 @@ RSpec.describe Packages::Nuget::MetadataExtractionService do
end
context 'with invalid package file id' do
- let(:package_file) { OpenStruct.new(id: 555) }
+ let(:package_file) { double('file', id: 555) }
it { expect { subject }.to raise_error(::Packages::Nuget::MetadataExtractionService::ExtractionError, 'invalid package file') }
end
@@ -109,7 +109,7 @@ RSpec.describe Packages::Nuget::MetadataExtractionService do
context 'with a too big nuspec file' do
before do
- allow_any_instance_of(Zip::File).to receive(:glob).and_return([OpenStruct.new(size: 6.megabytes)])
+ allow_any_instance_of(Zip::File).to receive(:glob).and_return([double('file', size: 6.megabytes)])
end
it { expect { subject }.to raise_error(::Packages::Nuget::MetadataExtractionService::ExtractionError, 'nuspec file too big') }
diff --git a/tooling/lib/tooling/parallel_rspec_runner.rb b/tooling/lib/tooling/parallel_rspec_runner.rb
index d3fa5af01e4..be926385cd7 100644
--- a/tooling/lib/tooling/parallel_rspec_runner.rb
+++ b/tooling/lib/tooling/parallel_rspec_runner.rb
@@ -38,6 +38,12 @@ module Tooling
Knapsack.logger.info tests_to_run
Knapsack.logger.info
+ # Without this guard clause, we're run all the specs instead of none!
+ if tests_to_run.empty?
+ Knapsack.logger.info 'No tests to run on this node, exiting.'
+ return 0
+ end
+
Process.wait Process.spawn(*rspec_command)
Process.last_status.exitstatus
end