summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--app/assets/javascripts/jobs/bridge/app.vue118
-rw-r--r--app/assets/javascripts/jobs/bridge/components/constants.js1
-rw-r--r--app/assets/javascripts/jobs/bridge/components/empty_state.vue45
-rw-r--r--app/assets/javascripts/jobs/bridge/components/sidebar.vue105
-rw-r--r--app/assets/javascripts/jobs/bridge/graphql/queries/pipeline.query.graphql70
-rw-r--r--app/assets/javascripts/jobs/index.js41
-rw-r--r--app/assets/javascripts/projects/pipelines/charts/components/app.vue15
-rw-r--r--app/assets/javascripts/surveys/merge_request_experience/app.vue6
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss7
-rw-r--r--app/controllers/projects/jobs_controller.rb4
-rw-r--r--app/finders/issuable_finder.rb2
-rw-r--r--app/helpers/emails_helper.rb94
-rw-r--r--app/models/concerns/milestoneable.rb2
-rw-r--r--app/models/merge_request_diff_file.rb31
-rw-r--r--app/presenters/terraform/module_version_presenter.rb50
-rw-r--r--app/views/admin/application_settings/_eks.html.haml2
-rw-r--r--app/views/admin/application_settings/_external_authorization_service_form.html.haml2
-rw-r--r--app/views/admin/application_settings/_floc.html.haml2
-rw-r--r--app/views/admin/application_settings/_gitpod.html.haml2
-rw-r--r--app/views/admin/application_settings/_jira_connect_application_key.html.haml2
-rw-r--r--app/views/admin/application_settings/_kroki.html.haml2
-rw-r--r--app/views/admin/application_settings/_mailgun.html.haml2
-rw-r--r--app/views/admin/application_settings/_plantuml.html.haml2
-rw-r--r--app/views/admin/application_settings/_snowplow.html.haml2
-rw-r--r--app/views/admin/application_settings/_sourcegraph.html.haml2
-rw-r--r--app/views/admin/application_settings/_third_party_offers.html.haml2
-rw-r--r--app/views/admin/application_settings/general.html.haml16
-rw-r--r--app/views/layouts/mailer.html.haml4
-rw-r--r--app/views/layouts/mailer.text.erb2
-rw-r--r--app/views/layouts/notify.html.haml11
-rw-r--r--app/views/layouts/notify.text.erb2
-rw-r--r--app/views/notify/approved_merge_request_email.html.haml4
-rw-r--r--app/views/notify/merge_when_pipeline_succeeds_email.html.haml4
-rw-r--r--app/views/notify/unapproved_merge_request_email.html.haml4
-rw-r--r--app/views/projects/jobs/show.html.haml5
-rw-r--r--app/views/pwa/offline.html.haml2
-rw-r--r--config/feature_flags/development/ci_docker_image_pull_policy.yml2
-rw-r--r--config/feature_flags/development/trigger_job_retry_action.yml8
-rw-r--r--db/docs/namespace_bans.yml9
-rw-r--r--db/migrate/20220628120708_create_namespace_bans.rb15
-rw-r--r--db/migrate/20220628121644_add_namespace_bans_namespace_id_foreign_key.rb15
-rw-r--r--db/migrate/20220628121712_add_namespace_bans_user_id_foreign_key.rb15
-rw-r--r--db/migrate/20220629220129_increase_webauthn_xid_length.rb17
-rw-r--r--db/post_migrate/20220706132238_add_indices_on_security_scans_info_column.rb27
-rw-r--r--db/schema_migrations/202206281207081
-rw-r--r--db/schema_migrations/202206281216441
-rw-r--r--db/schema_migrations/202206281217121
-rw-r--r--db/schema_migrations/202206292201291
-rw-r--r--db/schema_migrations/202207061322381
-rw-r--r--db/structure.sql38
-rw-r--r--doc/administration/monitoring/prometheus/gitlab_metrics.md1
-rw-r--r--doc/ci/yaml/index.md6
-rw-r--r--doc/development/integrations/secure.md35
-rw-r--r--doc/user/analytics/ci_cd_analytics.md16
-rw-r--r--doc/user/project/integrations/webhook_events.md22
-rw-r--r--lib/api/entities/terraform/module_version.rb18
-rw-r--r--lib/api/terraform/modules/v1/packages.rb10
-rw-r--r--lib/api/usage_data.rb2
-rw-r--r--lib/gitlab/data_builder/issuable.rb2
-rw-r--r--lib/gitlab/database/gitlab_schemas.yml1
-rw-r--r--lib/gitlab/hook_data/merge_request_builder.rb3
-rw-r--r--lib/gitlab/import_export/project/import_export.yml2
-rw-r--r--lib/gitlab/metrics/memory.rb15
-rw-r--r--lib/gitlab/metrics/samplers/ruby_sampler.rb12
-rw-r--r--lib/unnested_in_filters/rewriter.rb6
-rw-r--r--locale/gitlab.pot41
-rw-r--r--metrics_server/dependencies.rb1
-rw-r--r--qa/qa/page/group/settings/package_registries.rb6
-rw-r--r--spec/features/unsubscribe_links_spec.rb2
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/packages/terraform/modules/v1/single_version.json55
-rw-r--r--spec/frontend/jobs/bridge/app_spec.js146
-rw-r--r--spec/frontend/jobs/bridge/components/empty_state_spec.js58
-rw-r--r--spec/frontend/jobs/bridge/components/sidebar_spec.js99
-rw-r--r--spec/frontend/jobs/bridge/mock_data.js102
-rw-r--r--spec/frontend/projects/pipelines/charts/components/app_spec.js79
-rw-r--r--spec/helpers/emails_helper_spec.rb2
-rw-r--r--spec/lib/gitlab/hook_data/merge_request_builder_spec.rb52
-rw-r--r--spec/lib/gitlab/metrics/memory_spec.rb40
-rw-r--r--spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb6
-rw-r--r--spec/lib/unnested_in_filters/rewriter_spec.rb15
-rw-r--r--spec/mailers/emails/admin_notification_spec.rb2
-rw-r--r--spec/mailers/emails/profile_spec.rb8
-rw-r--r--spec/mailers/notify_spec.rb9
-rw-r--r--spec/models/merge_request_diff_file_spec.rb51
-rw-r--r--spec/requests/api/terraform/modules/v1/packages_spec.rb69
-rw-r--r--spec/support/shared_examples/finders/issues_finder_shared_examples.rb11
-rw-r--r--spec/support/shared_examples/requests/api/terraform/modules/v1/packages_shared_examples.rb16
-rw-r--r--spec/views/projects/commits/_commit.html.haml_spec.rb2
-rw-r--r--spec/views/projects/jobs/show.html.haml_spec.rb15
-rw-r--r--workhorse/go.mod4
-rw-r--r--workhorse/go.sum10
92 files changed, 847 insertions, 1025 deletions
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 0fa805d2ff5..4008a63e5c9 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-ac72695adc90343b7255869818376f505bde8315
+6944c95243e2b6ed8f783ae903cb9b03ad50e0f9
diff --git a/app/assets/javascripts/jobs/bridge/app.vue b/app/assets/javascripts/jobs/bridge/app.vue
deleted file mode 100644
index c639e49083b..00000000000
--- a/app/assets/javascripts/jobs/bridge/app.vue
+++ /dev/null
@@ -1,118 +0,0 @@
-<script>
-import { GlLoadingIcon } from '@gitlab/ui';
-import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
-import { getIdFromGraphQLId } from '~/graphql_shared/utils';
-import { __, sprintf } from '~/locale';
-import CiHeader from '~/vue_shared/components/header_ci_component.vue';
-import getPipelineQuery from './graphql/queries/pipeline.query.graphql';
-import BridgeEmptyState from './components/empty_state.vue';
-import BridgeSidebar from './components/sidebar.vue';
-import { SIDEBAR_COLLAPSE_BREAKPOINTS } from './components/constants';
-
-export default {
- name: 'BridgePageApp',
- components: {
- BridgeEmptyState,
- BridgeSidebar,
- CiHeader,
- GlLoadingIcon,
- },
- inject: ['buildId', 'projectFullPath', 'pipelineIid'],
- apollo: {
- pipeline: {
- query: getPipelineQuery,
- variables() {
- return {
- fullPath: this.projectFullPath,
- iid: this.pipelineIid,
- };
- },
- update(data) {
- if (!data?.project?.pipeline) {
- return null;
- }
-
- const { pipeline } = data.project;
- const stages = pipeline?.stages.edges.map((edge) => edge.node) || [];
- const jobs = stages.map((stage) => stage.jobs.nodes).flat();
-
- return {
- ...pipeline,
- commit: {
- ...pipeline.commit,
- commit_path: pipeline.commit.webPath,
- short_id: pipeline.commit.shortId,
- },
- id: getIdFromGraphQLId(pipeline.id),
- jobs,
- stages,
- };
- },
- },
- },
- data() {
- return {
- isSidebarExpanded: true,
- pipeline: {},
- };
- },
- computed: {
- bridgeJob() {
- return (
- this.pipeline.jobs?.filter(
- (job) => getIdFromGraphQLId(job.id) === Number(this.buildId),
- )[0] || {}
- );
- },
- bridgeName() {
- return sprintf(__('Job %{jobName}'), { jobName: this.bridgeJob.name });
- },
- isPipelineLoading() {
- return this.$apollo.queries.pipeline.loading;
- },
- },
- created() {
- window.addEventListener('resize', this.onResize);
- },
- mounted() {
- this.onResize();
- },
- methods: {
- toggleSidebar() {
- this.isSidebarExpanded = !this.isSidebarExpanded;
- },
- onResize() {
- const breakpoint = bp.getBreakpointSize();
- if (SIDEBAR_COLLAPSE_BREAKPOINTS.includes(breakpoint)) {
- this.isSidebarExpanded = false;
- } else if (!this.isSidebarExpanded) {
- this.isSidebarExpanded = true;
- }
- },
- },
-};
-</script>
-<template>
- <div>
- <gl-loading-icon v-if="isPipelineLoading" size="lg" class="gl-mt-4" />
- <div v-else>
- <ci-header
- class="gl-border-b-1 gl-border-b-solid gl-border-b-gray-100"
- :status="bridgeJob.detailedStatus"
- :time="bridgeJob.createdAt"
- :user="pipeline.user"
- :has-sidebar-button="true"
- :item-name="bridgeName"
- @clickedSidebarButton="toggleSidebar"
- />
- <bridge-empty-state :downstream-pipeline-path="bridgeJob.downstreamPipeline.path" />
- <bridge-sidebar
- v-if="isSidebarExpanded"
- :bridge-job="bridgeJob"
- :commit="pipeline.commit"
- :is-sidebar-expanded="isSidebarExpanded"
- @toggleSidebar="toggleSidebar"
- />
- </div>
- </div>
-</template>
diff --git a/app/assets/javascripts/jobs/bridge/components/constants.js b/app/assets/javascripts/jobs/bridge/components/constants.js
deleted file mode 100644
index 33310b3157a..00000000000
--- a/app/assets/javascripts/jobs/bridge/components/constants.js
+++ /dev/null
@@ -1 +0,0 @@
-export const SIDEBAR_COLLAPSE_BREAKPOINTS = ['xs', 'sm'];
diff --git a/app/assets/javascripts/jobs/bridge/components/empty_state.vue b/app/assets/javascripts/jobs/bridge/components/empty_state.vue
deleted file mode 100644
index bd07d863719..00000000000
--- a/app/assets/javascripts/jobs/bridge/components/empty_state.vue
+++ /dev/null
@@ -1,45 +0,0 @@
-<script>
-import { GlButton } from '@gitlab/ui';
-import { __ } from '~/locale';
-
-export default {
- name: 'BridgeEmptyState',
- i18n: {
- title: __('This job triggers a downstream pipeline'),
- linkBtnText: __('View downstream pipeline'),
- },
- components: {
- GlButton,
- },
- inject: {
- emptyStateIllustrationPath: {
- type: String,
- require: true,
- },
- },
- props: {
- downstreamPipelinePath: {
- type: String,
- required: false,
- default: undefined,
- },
- },
-};
-</script>
-
-<template>
- <div class="gl-display-flex gl-flex-direction-column gl-align-items-center gl-mt-11">
- <img :src="emptyStateIllustrationPath" />
- <h1 class="gl-font-size-h1">{{ $options.i18n.title }}</h1>
- <gl-button
- v-if="downstreamPipelinePath"
- class="gl-mt-3"
- category="secondary"
- variant="confirm"
- size="medium"
- :href="downstreamPipelinePath"
- >
- {{ $options.i18n.linkBtnText }}
- </gl-button>
- </div>
-</template>
diff --git a/app/assets/javascripts/jobs/bridge/components/sidebar.vue b/app/assets/javascripts/jobs/bridge/components/sidebar.vue
deleted file mode 100644
index 3ba07cf55d1..00000000000
--- a/app/assets/javascripts/jobs/bridge/components/sidebar.vue
+++ /dev/null
@@ -1,105 +0,0 @@
-<script>
-import { GlButton, GlDropdown, GlDropdownItem } from '@gitlab/ui';
-import { __ } from '~/locale';
-import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
-import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue';
-import { JOB_SIDEBAR } from '../../constants';
-import CommitBlock from '../../components/commit_block.vue';
-
-export default {
- styles: {
- width: '290px',
- },
- name: 'BridgeSidebar',
- i18n: {
- ...JOB_SIDEBAR,
- retryButton: __('Retry'),
- retryTriggerJob: __('Retry the trigger job'),
- retryDownstreamPipeline: __('Retry the downstream pipeline'),
- },
- sectionClass: ['gl-border-t-solid', 'gl-border-t-1', 'gl-border-t-gray-100', 'gl-py-5'],
- components: {
- CommitBlock,
- GlButton,
- GlDropdown,
- GlDropdownItem,
- TooltipOnTruncate,
- },
- mixins: [glFeatureFlagsMixin()],
- props: {
- bridgeJob: {
- type: Object,
- required: true,
- },
- commit: {
- type: Object,
- required: true,
- },
- },
- data() {
- return {
- topPosition: 0,
- };
- },
- computed: {
- rootStyle() {
- return { ...this.$options.styles, top: `${this.topPosition}px` };
- },
- },
- mounted() {
- this.setTopPosition();
- },
- methods: {
- onSidebarButtonClick() {
- this.$emit('toggleSidebar');
- },
- setTopPosition() {
- const navbarEl = document.querySelector('.js-navbar');
-
- if (navbarEl) {
- this.topPosition = navbarEl.getBoundingClientRect().bottom;
- }
- },
- },
-};
-</script>
-<template>
- <aside
- class="gl-fixed gl-right-0 gl-px-5 gl-bg-gray-10 gl-h-full gl-border-l-solid gl-border-1 gl-border-gray-100 gl-z-index-200 gl-overflow-hidden"
- :style="rootStyle"
- >
- <div class="gl-py-5 gl-display-flex gl-align-items-center">
- <tooltip-on-truncate :title="bridgeJob.name" truncate-target="child"
- ><h4 class="gl-mb-0 gl-mr-2 gl-text-truncate">
- {{ bridgeJob.name }}
- </h4>
- </tooltip-on-truncate>
- <!-- TODO: implement retry actions -->
- <div
- v-if="glFeatures.triggerJobRetryAction"
- class="gl-flex-grow-1 gl-flex-shrink-0 gl-text-right"
- >
- <gl-dropdown
- :text="$options.i18n.retryButton"
- category="primary"
- variant="confirm"
- right
- size="medium"
- >
- <gl-dropdown-item>{{ $options.i18n.retryTriggerJob }}</gl-dropdown-item>
- <gl-dropdown-item>{{ $options.i18n.retryDownstreamPipeline }}</gl-dropdown-item>
- </gl-dropdown>
- </div>
- <gl-button
- :aria-label="$options.i18n.toggleSidebar"
- data-testid="sidebar-expansion-toggle"
- category="tertiary"
- class="gl-md-display-none gl-ml-2"
- icon="chevron-double-lg-right"
- @click="onSidebarButtonClick"
- />
- </div>
- <commit-block :commit="commit" :class="$options.sectionClass" />
- <!-- TODO: show stage dropdown, jobs list -->
- </aside>
-</template>
diff --git a/app/assets/javascripts/jobs/bridge/graphql/queries/pipeline.query.graphql b/app/assets/javascripts/jobs/bridge/graphql/queries/pipeline.query.graphql
deleted file mode 100644
index 338ca9f16c7..00000000000
--- a/app/assets/javascripts/jobs/bridge/graphql/queries/pipeline.query.graphql
+++ /dev/null
@@ -1,70 +0,0 @@
-query getPipelineData($fullPath: ID!, $iid: ID!) {
- project(fullPath: $fullPath) {
- id
- pipeline(iid: $iid) {
- id
- iid
- path
- sha
- ref
- refPath
- commit {
- id
- shortId
- title
- webPath
- }
- detailedStatus {
- id
- icon
- group
- }
- stages {
- edges {
- node {
- id
- name
- jobs {
- nodes {
- id
- createdAt
- name
- scheduledAt
- startedAt
- status
- triggered
- detailedStatus {
- id
- detailsPath
- icon
- group
- text
- tooltip
- }
- downstreamPipeline {
- id
- path
- }
- stage {
- id
- name
- }
- }
- }
- }
- }
- }
- user {
- id
- avatarUrl
- name
- username
- webPath
- webUrl
- status {
- message
- }
- }
- }
- }
-}
diff --git a/app/assets/javascripts/jobs/index.js b/app/assets/javascripts/jobs/index.js
index 26dd38bbe08..8fb4c480ef9 100644
--- a/app/assets/javascripts/jobs/index.js
+++ b/app/assets/javascripts/jobs/index.js
@@ -1,7 +1,4 @@
import Vue from 'vue';
-import VueApollo from 'vue-apollo';
-import createDefaultClient from '~/lib/graphql';
-import BridgeApp from './bridge/app.vue';
import JobApp from './components/job_app.vue';
import createStore from './store';
@@ -51,43 +48,7 @@ const initializeJobPage = (element) => {
});
};
-const initializeBridgePage = (el) => {
- const {
- buildId,
- downstreamPipelinePath,
- emptyStateIllustrationPath,
- pipelineIid,
- projectFullPath,
- } = el.dataset;
-
- Vue.use(VueApollo);
- const apolloProvider = new VueApollo({
- defaultClient: createDefaultClient(),
- });
-
- return new Vue({
- el,
- apolloProvider,
- provide: {
- buildId,
- downstreamPipelinePath,
- emptyStateIllustrationPath,
- pipelineIid,
- projectFullPath,
- },
- render(h) {
- return h(BridgeApp);
- },
- });
-};
-
export default () => {
const jobElement = document.getElementById('js-job-page');
- const bridgeElement = document.getElementById('js-bridge-page');
-
- if (jobElement) {
- initializeJobPage(jobElement);
- } else {
- initializeBridgePage(bridgeElement);
- }
+ initializeJobPage(jobElement);
};
diff --git a/app/assets/javascripts/projects/pipelines/charts/components/app.vue b/app/assets/javascripts/projects/pipelines/charts/components/app.vue
index 35e7554aee2..016301368af 100644
--- a/app/assets/javascripts/projects/pipelines/charts/components/app.vue
+++ b/app/assets/javascripts/projects/pipelines/charts/components/app.vue
@@ -14,6 +14,8 @@ export default {
LeadTimeCharts: () => import('ee_component/dora/components/lead_time_charts.vue'),
TimeToRestoreServiceCharts: () =>
import('ee_component/dora/components/time_to_restore_service_charts.vue'),
+ ChangeFailureRateCharts: () =>
+ import('ee_component/dora/components/change_failure_rate_charts.vue'),
ProjectQualitySummary: () => import('ee_component/project_quality_summary/app.vue'),
},
piplelinesTabEvent: 'p_analytics_ci_cd_pipelines',
@@ -40,7 +42,12 @@ export default {
const chartsToShow = ['pipelines'];
if (this.shouldRenderDoraCharts) {
- chartsToShow.push('deployment-frequency', 'lead-time', 'time-to-restore-service');
+ chartsToShow.push(
+ 'deployment-frequency',
+ 'lead-time',
+ 'time-to-restore-service',
+ 'change-failure-rate',
+ );
}
if (this.shouldRenderQualitySummary) {
@@ -105,6 +112,12 @@ export default {
>
<time-to-restore-service-charts />
</gl-tab>
+ <gl-tab
+ :title="s__('DORA4Metrics|Change failure rate')"
+ data-testid="change-failure-rate-tab"
+ >
+ <change-failure-rate-charts />
+ </gl-tab>
</template>
<gl-tab v-if="shouldRenderQualitySummary" :title="s__('QualitySummary|Project quality')">
<project-quality-summary />
diff --git a/app/assets/javascripts/surveys/merge_request_experience/app.vue b/app/assets/javascripts/surveys/merge_request_experience/app.vue
index 5b1d9be9563..85eed6ae82a 100644
--- a/app/assets/javascripts/surveys/merge_request_experience/app.vue
+++ b/app/assets/javascripts/surveys/merge_request_experience/app.vue
@@ -1,5 +1,5 @@
<script>
-import { GlButton, GlSprintf, GlSafeHtmlDirective } from '@gitlab/ui';
+import { GlButton, GlSprintf, GlSafeHtmlDirective, GlTooltipDirective } from '@gitlab/ui';
import gitlabLogo from '@gitlab/svgs/dist/illustrations/gitlab_logo.svg';
import { s__, __ } from '~/locale';
import UserCalloutDismisser from '~/vue_shared/components/user_callout_dismisser.vue';
@@ -29,6 +29,7 @@ export default {
},
directives: {
safeHtml: GlSafeHtmlDirective,
+ tooltip: GlTooltipDirective,
},
mixins: [Tracking.mixin()],
i18n: {
@@ -92,7 +93,7 @@ export default {
>
<template #default="{ dismiss }">
<aside
- class="gl-fixed gl-bottom-0 gl-right-0 gl-z-index-9999 gl-p-5"
+ class="mr-experience-survey-wrapper gl-fixed gl-bottom-0 gl-right-0 gl-p-5"
:aria-label="$options.i18n.survey"
>
<transition name="survey-slide-up">
@@ -101,6 +102,7 @@ export default {
class="mr-experience-survey-body gl-relative gl-display-flex gl-flex-direction-column gl-bg-white gl-p-5 gl-border gl-rounded-base"
>
<gl-button
+ v-tooltip="$options.i18n.close"
:aria-label="$options.i18n.close"
variant="default"
category="tertiary"
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index ebf3c81d9d3..96fe6caeea2 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -350,6 +350,13 @@ $comparison-empty-state-height: 62px;
}
}
+.mr-experience-survey-wrapper {
+ // setting this explicitly because:
+ // diff-files-holder has z-index 203
+ // z-index 9999 utility class breaks tooltips
+ z-index: 210;
+}
+
.mr-experience-survey-body {
width: 300px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
diff --git a/app/controllers/projects/jobs_controller.rb b/app/controllers/projects/jobs_controller.rb
index 2eae13b5265..1031bf3e60a 100644
--- a/app/controllers/projects/jobs_controller.rb
+++ b/app/controllers/projects/jobs_controller.rb
@@ -22,10 +22,6 @@ class Projects::JobsController < Projects::ApplicationController
before_action :push_jobs_table_vue_search, only: [:index]
before_action :reject_if_build_artifacts_size_refreshing!, only: [:erase]
- before_action do
- push_frontend_feature_flag(:trigger_job_retry_action, @project)
- end
-
layout 'project'
feature_category :continuous_integration
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index 5ca417455fb..8ecf0c158e0 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -436,7 +436,7 @@ class IssuableFinder
elsif not_params.filter_by_started_milestone?
items.joins(:milestone).merge(Milestone.not_started)
else
- items.without_particular_milestone(not_params[:milestone_title])
+ items.without_particular_milestones(not_params[:milestone_title])
end
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/app/helpers/emails_helper.rb b/app/helpers/emails_helper.rb
index dc4e2e9709a..54733fa9101 100644
--- a/app/helpers/emails_helper.rb
+++ b/app/helpers/emails_helper.rb
@@ -116,19 +116,16 @@ module EmailsHelper
end
end
- # "You are receiving this email because #{reason} on #{gitlab_host}."
- def notification_reason_text(reason)
- gitlab_host = Gitlab.config.gitlab.host
-
- case reason
- when NotificationReason::OWN_ACTIVITY
- _("You're receiving this email because of your activity on %{host}.") % { host: gitlab_host }
- when NotificationReason::ASSIGNED
- _("You're receiving this email because you have been assigned an item on %{host}.") % { host: gitlab_host }
- when NotificationReason::MENTIONED
- _("You're receiving this email because you have been mentioned on %{host}.") % { host: gitlab_host }
+ # "You are receiving this email because ... on #{host}. ..."
+ def notification_reason_text(reason: nil, show_manage_notifications_link: false, show_help_link: false, manage_label_subscriptions_url: nil, unsubscribe_url: nil, format: :text)
+ if unsubscribe_url && show_manage_notifications_link && show_help_link
+ notification_reason_text_with_unsubscribe_and_manage_notifications_and_help_links(reason: reason, unsubscribe_url: unsubscribe_url, format: format)
+ elsif !reason && manage_label_subscriptions_url && show_help_link
+ notification_reason_text_with_manage_label_subscriptions_and_help_links(manage_label_subscriptions_url: manage_label_subscriptions_url, format: format)
+ elsif show_manage_notifications_link && show_help_link
+ notification_reason_text_with_manage_notifications_and_help_links(reason: reason, format: format)
else
- _("You're receiving this email because of your account on %{host}.") % { host: gitlab_host }
+ notification_reason_text_without_links(reason: reason, format: format)
end
end
@@ -259,9 +256,7 @@ module EmailsHelper
end
def instance_access_request_text(user, format: nil)
- gitlab_host = Gitlab.config.gitlab.host
-
- _('%{username} has asked for a GitLab account on your instance %{host}:') % { username: sanitize_name(user.name), host: gitlab_host }
+ _('%{username} has asked for a GitLab account on your instance %{host}:').html_safe % { username: sanitize_name(user.name), host: gitlab_host_link(format) }
end
def instance_access_request_link(user, format: nil)
@@ -325,6 +320,75 @@ module EmailsHelper
def email_header_and_footer_enabled?
current_appearance&.email_header_and_footer_enabled?
end
+
+ def gitlab_host_link(format)
+ case format
+ when :html
+ generate_link(Gitlab.config.gitlab.host, Gitlab.config.gitlab.url)
+ when :text
+ Gitlab.config.gitlab.host
+ end
+ end
+
+ def notification_reason_text_with_unsubscribe_and_manage_notifications_and_help_links(reason:, unsubscribe_url:, format:)
+ unsubscribe_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: unsubscribe_url }
+ unsubscribe_link_end = '</a>'.html_safe
+
+ manage_notifications_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer" class="mng-notif-link">'.html_safe % { url: profile_notifications_url }
+ manage_notifications_link_end = '</a>'.html_safe
+
+ help_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer" class="help-link">'.html_safe % { url: help_url }
+ help_link_end = '</a>'.html_safe
+
+ case reason
+ when NotificationReason::OWN_ACTIVITY
+ _("You're receiving this email because of your activity on %{host}. %{unsubscribe_link_start}Unsubscribe%{unsubscribe_link_end} from this thread &middot; %{manage_notifications_link_start}Manage all notifications%{manage_notifications_link_end} &middot; %{help_link_start}Help%{help_link_end}").html_safe % { host: gitlab_host_link(format), unsubscribe_link_start: unsubscribe_link_start, unsubscribe_link_end: unsubscribe_link_end, manage_notifications_link_start: manage_notifications_link_start, manage_notifications_link_end: manage_notifications_link_end, help_link_start: help_link_start, help_link_end: help_link_end }
+ when NotificationReason::ASSIGNED
+ _("You're receiving this email because you have been assigned an item on %{host}. %{unsubscribe_link_start}Unsubscribe%{unsubscribe_link_end} from this thread &middot; %{manage_notifications_link_start}Manage all notifications%{manage_notifications_link_end} &middot; %{help_link_start}Help%{help_link_end}").html_safe % { host: gitlab_host_link(format), unsubscribe_link_start: unsubscribe_link_start, unsubscribe_link_end: unsubscribe_link_end, manage_notifications_link_start: manage_notifications_link_start, manage_notifications_link_end: manage_notifications_link_end, help_link_start: help_link_start, help_link_end: help_link_end }
+ when NotificationReason::MENTIONED
+ _("You're receiving this email because you have been mentioned on %{host}. %{unsubscribe_link_start}Unsubscribe%{unsubscribe_link_end} from this thread &middot; %{manage_notifications_link_start}Manage all notifications%{manage_notifications_link_end} &middot; %{help_link_start}Help%{help_link_end}").html_safe % { host: gitlab_host_link(format), unsubscribe_link_start: unsubscribe_link_start, unsubscribe_link_end: unsubscribe_link_end, manage_notifications_link_start: manage_notifications_link_start, manage_notifications_link_end: manage_notifications_link_end, help_link_start: help_link_start, help_link_end: help_link_end }
+ else
+ _("You're receiving this email because of your account on %{host}. %{unsubscribe_link_start}Unsubscribe%{unsubscribe_link_end} from this thread &middot; %{manage_notifications_link_start}Manage all notifications%{manage_notifications_link_end} &middot; %{help_link_start}Help%{help_link_end}").html_safe % { host: gitlab_host_link(format), unsubscribe_link_start: unsubscribe_link_start, unsubscribe_link_end: unsubscribe_link_end, manage_notifications_link_start: manage_notifications_link_start, manage_notifications_link_end: manage_notifications_link_end, help_link_start: help_link_start, help_link_end: help_link_end }
+ end
+ end
+
+ def notification_reason_text_with_manage_label_subscriptions_and_help_links(manage_label_subscriptions_url:, format:)
+ manage_label_subscriptions_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer" class="mng-notif-link">'.html_safe % { url: manage_label_subscriptions_url }
+ manage_label_subscriptions_link_end = '</a>'.html_safe
+
+ help_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer" class="help-link">'.html_safe % { url: help_url }
+ help_link_end = '</a>'.html_safe
+
+ _("You're receiving this email because of your account on %{host}. %{manage_label_subscriptions_link_start}Manage label subscriptions%{manage_label_subscriptions_link_end} &middot; %{help_link_start}Help%{help_link_end}").html_safe % { host: gitlab_host_link(format), manage_label_subscriptions_link_start: manage_label_subscriptions_link_start, manage_label_subscriptions_link_end: manage_label_subscriptions_link_end, help_link_start: help_link_start, help_link_end: help_link_end }
+ end
+
+ def notification_reason_text_with_manage_notifications_and_help_links(reason:, format:)
+ manage_notifications_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer" class="mng-notif-link">'.html_safe % { url: profile_notifications_url }
+ manage_notifications_link_end = '</a>'.html_safe
+
+ help_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer" class="help-link">'.html_safe % { url: help_url }
+ help_link_end = '</a>'.html_safe
+
+ case reason
+ when NotificationReason::MENTIONED
+ _("You're receiving this email because you have been mentioned on %{host}. %{manage_notifications_link_start}Manage all notifications%{manage_notifications_link_end} &middot; %{help_link_start}Help%{help_link_end}").html_safe % { host: gitlab_host_link(format), manage_notifications_link_start: manage_notifications_link_start, manage_notifications_link_end: manage_notifications_link_end, help_link_start: help_link_start, help_link_end: help_link_end }
+ else
+ _("You're receiving this email because of your account on %{host}. %{manage_notifications_link_start}Manage all notifications%{manage_notifications_link_end} &middot; %{help_link_start}Help%{help_link_end}").html_safe % { host: gitlab_host_link(format), manage_notifications_link_start: manage_notifications_link_start, manage_notifications_link_end: manage_notifications_link_end, help_link_start: help_link_start, help_link_end: help_link_end }
+ end
+ end
+
+ def notification_reason_text_without_links(reason:, format:)
+ case reason
+ when NotificationReason::OWN_ACTIVITY
+ _("You're receiving this email because of your activity on %{host}.").html_safe % { host: gitlab_host_link(format) }
+ when NotificationReason::ASSIGNED
+ _("You're receiving this email because you have been assigned an item on %{host}.").html_safe % { host: gitlab_host_link(format) }
+ when NotificationReason::MENTIONED
+ _("You're receiving this email because you have been mentioned on %{host}.").html_safe % { host: gitlab_host_link(format) }
+ else
+ _("You're receiving this email because of your account on %{host}.").html_safe % { host: gitlab_host_link(format) }
+ end
+ end
end
EmailsHelper.prepend_mod_with('EmailsHelper')
diff --git a/app/models/concerns/milestoneable.rb b/app/models/concerns/milestoneable.rb
index 12041b103f6..14c54d99ef3 100644
--- a/app/models/concerns/milestoneable.rb
+++ b/app/models/concerns/milestoneable.rb
@@ -16,7 +16,7 @@ module Milestoneable
scope :any_milestone, -> { where.not(milestone_id: nil) }
scope :with_milestone, ->(title) { left_joins_milestones.where(milestones: { title: title }) }
- scope :without_particular_milestone, ->(title) { left_outer_joins(:milestone).where("milestones.title != ? OR milestone_id IS NULL", title) }
+ scope :without_particular_milestones, ->(titles) { left_outer_joins(:milestone).where("milestones.title NOT IN (?) OR milestone_id IS NULL", titles) }
scope :any_release, -> { joins_milestone_releases }
scope :with_release, -> (tag, project_id) { joins_milestone_releases.where( milestones: { releases: { tag: tag, project_id: project_id } } ) }
scope :without_particular_release, -> (tag, project_id) { joins_milestone_releases.where.not(milestones: { releases: { tag: tag, project_id: project_id } }) }
diff --git a/app/models/merge_request_diff_file.rb b/app/models/merge_request_diff_file.rb
index a5c392f6a36..36902e43a77 100644
--- a/app/models/merge_request_diff_file.rb
+++ b/app/models/merge_request_diff_file.rb
@@ -15,7 +15,12 @@ class MergeRequestDiffFile < ApplicationRecord
end
def utf8_diff
- fetched_diff = diff
+ fetched_diff = if Feature.enabled?(:externally_stored_diffs_caching_export) &&
+ merge_request_diff&.stored_externally?
+ diff_export
+ else
+ diff
+ end
return '' if fetched_diff.blank?
@@ -46,14 +51,13 @@ class MergeRequestDiffFile < ApplicationRecord
end
end
+ private
+
# This method is meant to be used during Project Export.
- # It is identical to the behaviour in #diff & #utf8_diff with the only
+ # It is identical to the behaviour in #diff with the only
# difference of caching externally stored diffs on local disk in
# temp storage location in order to improve diff export performance.
def diff_export
- return utf8_diff unless Feature.enabled?(:externally_stored_diffs_caching_export)
- return utf8_diff unless merge_request_diff&.stored_externally?
-
content = merge_request_diff.cached_external_diff do |file|
file.seek(external_diff_offset)
@@ -69,14 +73,17 @@ class MergeRequestDiffFile < ApplicationRecord
end
end
- if content.respond_to?(:encoding)
- content = encode_utf8(content)
- end
+ content
+ rescue StandardError => e
+ log_payload = {
+ message: 'Cached external diff export failed',
+ merge_request_diff_file_id: id,
+ merge_request_diff_id: merge_request_diff&.id
+ }
- return '' if content.blank?
+ Gitlab::ExceptionLogFormatter.format!(e, log_payload)
+ Gitlab::AppLogger.warn(log_payload)
- content
- rescue StandardError
- utf8_diff
+ diff
end
end
diff --git a/app/presenters/terraform/module_version_presenter.rb b/app/presenters/terraform/module_version_presenter.rb
new file mode 100644
index 00000000000..776a4d8ab82
--- /dev/null
+++ b/app/presenters/terraform/module_version_presenter.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+module Terraform
+ class ModuleVersionPresenter < Gitlab::View::Presenter::Simple
+ attr_accessor :package, :system
+
+ def initialize(package, system)
+ @package = package
+ @system = system
+ end
+
+ def name
+ package.name
+ end
+
+ def provider
+ system
+ end
+
+ def providers
+ [
+ provider
+ ]
+ end
+
+ def root
+ {
+ 'dependencies' => []
+ }
+ end
+
+ def source
+ package&.project&.web_url
+ end
+
+ def submodules
+ []
+ end
+
+ def version
+ package.version
+ end
+
+ def versions
+ [
+ version
+ ]
+ end
+ end
+end
diff --git a/app/views/admin/application_settings/_eks.html.haml b/app/views/admin/application_settings/_eks.html.haml
index 370d3cea07c..68eb33d6552 100644
--- a/app/views/admin/application_settings/_eks.html.haml
+++ b/app/views/admin/application_settings/_eks.html.haml
@@ -1,7 +1,7 @@
- expanded = integration_expanded?('eks_')
%section.settings.as-eks.no-animate#js-eks-settings{ class: ('expanded' if expanded) }
.settings-header
- %h4
+ %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
= _('Amazon EKS')
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
= expanded ? _('Collapse') : _('Expand')
diff --git a/app/views/admin/application_settings/_external_authorization_service_form.html.haml b/app/views/admin/application_settings/_external_authorization_service_form.html.haml
index 4d0faf69958..f287dba9866 100644
--- a/app/views/admin/application_settings/_external_authorization_service_form.html.haml
+++ b/app/views/admin/application_settings/_external_authorization_service_form.html.haml
@@ -1,6 +1,6 @@
%section.settings.as-external-auth.no-animate#js-external-auth-settings{ class: ('expanded' if expanded) }
.settings-header
- %h4
+ %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
= s_('ExternalAuthorization|External authorization')
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
= expanded ? _('Collapse') : _('Expand')
diff --git a/app/views/admin/application_settings/_floc.html.haml b/app/views/admin/application_settings/_floc.html.haml
index b5a63aa0847..d63eb2bd09d 100644
--- a/app/views/admin/application_settings/_floc.html.haml
+++ b/app/views/admin/application_settings/_floc.html.haml
@@ -2,7 +2,7 @@
%section.settings.no-animate#js-floc-settings{ class: ('expanded' if expanded) }
.settings-header
- %h4
+ %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
= s_('FloC|Federated Learning of Cohorts')
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
= expanded ? _('Collapse') : _('Expand')
diff --git a/app/views/admin/application_settings/_gitpod.html.haml b/app/views/admin/application_settings/_gitpod.html.haml
index cd4493d8016..cc1e3f968cb 100644
--- a/app/views/admin/application_settings/_gitpod.html.haml
+++ b/app/views/admin/application_settings/_gitpod.html.haml
@@ -2,7 +2,7 @@
%section.settings.no-animate#js-gitpod-settings{ class: ('expanded' if expanded) }
.settings-header
- %h4
+ %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
= _('Gitpod')
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
= expanded ? _('Collapse') : _('Expand')
diff --git a/app/views/admin/application_settings/_jira_connect_application_key.html.haml b/app/views/admin/application_settings/_jira_connect_application_key.html.haml
index e395741dcaa..e3df408cd4c 100644
--- a/app/views/admin/application_settings/_jira_connect_application_key.html.haml
+++ b/app/views/admin/application_settings/_jira_connect_application_key.html.haml
@@ -2,7 +2,7 @@
%section.settings.no-animate#js-jira_connect-settings{ class: ('expanded' if expanded) }
.settings-header
- %h4
+ %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
= s_('JiraConnect|GitLab for Jira App')
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
= expanded ? _('Collapse') : _('Expand')
diff --git a/app/views/admin/application_settings/_kroki.html.haml b/app/views/admin/application_settings/_kroki.html.haml
index b1dd8a282ec..c0ac924407f 100644
--- a/app/views/admin/application_settings/_kroki.html.haml
+++ b/app/views/admin/application_settings/_kroki.html.haml
@@ -1,7 +1,7 @@
- expanded = integration_expanded?('kroki_')
%section.settings.as-kroki.no-animate#js-kroki-settings{ class: ('expanded' if expanded) }
.settings-header
- %h4
+ %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
= _('Kroki')
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
= expanded ? _('Collapse') : _('Expand')
diff --git a/app/views/admin/application_settings/_mailgun.html.haml b/app/views/admin/application_settings/_mailgun.html.haml
index 1239d9178ae..cbe7e1c5bb6 100644
--- a/app/views/admin/application_settings/_mailgun.html.haml
+++ b/app/views/admin/application_settings/_mailgun.html.haml
@@ -1,7 +1,7 @@
- expanded = integration_expanded?('mailgun_')
%section.settings.as-mailgun.no-animate#js-mailgun-settings{ class: ('expanded' if expanded) }
.settings-header
- %h4
+ %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
= _('Mailgun')
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
= expanded ? _('Collapse') : _('Expand')
diff --git a/app/views/admin/application_settings/_plantuml.html.haml b/app/views/admin/application_settings/_plantuml.html.haml
index 9e22efce453..8be37ff1dda 100644
--- a/app/views/admin/application_settings/_plantuml.html.haml
+++ b/app/views/admin/application_settings/_plantuml.html.haml
@@ -1,7 +1,7 @@
- expanded = integration_expanded?('plantuml_')
%section.settings.as-plantuml.no-animate#js-plantuml-settings{ class: ('expanded' if expanded) }
.settings-header
- %h4
+ %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
= _('PlantUML')
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
= expanded ? _('Collapse') : _('Expand')
diff --git a/app/views/admin/application_settings/_snowplow.html.haml b/app/views/admin/application_settings/_snowplow.html.haml
index e9387ab3f26..8684b909853 100644
--- a/app/views/admin/application_settings/_snowplow.html.haml
+++ b/app/views/admin/application_settings/_snowplow.html.haml
@@ -1,7 +1,7 @@
- expanded = integration_expanded?('snowplow_')
%section.settings.as-snowplow.no-animate#js-snowplow-settings{ class: ('expanded' if expanded), data: { qa_selector: 'snowplow_settings_content' } }
.settings-header
- %h4
+ %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
= _('Snowplow')
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
= expanded ? _('Collapse') : _('Expand')
diff --git a/app/views/admin/application_settings/_sourcegraph.html.haml b/app/views/admin/application_settings/_sourcegraph.html.haml
index a0cbbecb943..43ff2bc02f5 100644
--- a/app/views/admin/application_settings/_sourcegraph.html.haml
+++ b/app/views/admin/application_settings/_sourcegraph.html.haml
@@ -3,7 +3,7 @@
%section.settings.as-sourcegraph.no-animate#js-sourcegraph-settings{ class: ('expanded' if expanded) }
.settings-header
- %h4
+ %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
= _('Sourcegraph')
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
= expanded ? _('Collapse') : _('Expand')
diff --git a/app/views/admin/application_settings/_third_party_offers.html.haml b/app/views/admin/application_settings/_third_party_offers.html.haml
index a868a397992..397b47eefaa 100644
--- a/app/views/admin/application_settings/_third_party_offers.html.haml
+++ b/app/views/admin/application_settings/_third_party_offers.html.haml
@@ -1,7 +1,7 @@
- expanded = integration_expanded?('hide_third_party_')
%section.settings.as-third-party-offers.no-animate#js-third-party-offers-settings{ class: ('expanded' if expanded) }
.settings-header
- %h4
+ %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
= _('Customer experience improvement and third-party offers')
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
= expanded ? _('Collapse') : _('Expand')
diff --git a/app/views/admin/application_settings/general.html.haml b/app/views/admin/application_settings/general.html.haml
index 52d39172556..ce679fb3d77 100644
--- a/app/views/admin/application_settings/general.html.haml
+++ b/app/views/admin/application_settings/general.html.haml
@@ -4,7 +4,7 @@
%section.settings.as-visibility-access.no-animate#js-visibility-settings{ class: ('expanded' if expanded_by_default?) }
.settings-header
- %h4
+ %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
= _('Visibility and access controls')
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
= expanded_by_default? ? _('Collapse') : _('Expand')
@@ -15,7 +15,7 @@
%section.settings.as-account-limit.no-animate#js-account-settings{ class: ('expanded' if expanded_by_default?), data: { qa_selector: 'account_and_limit_settings_content' } }
.settings-header
- %h4
+ %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
= _('Account and limit')
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
= expanded_by_default? ? _('Collapse') : _('Expand')
@@ -26,7 +26,7 @@
%section.settings.as-diff-limits.no-animate#js-merge-request-settings{ class: ('expanded' if expanded_by_default?) }
.settings-header
- %h4
+ %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
= _('Diff limits')
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
= expanded_by_default? ? _('Collapse') : _('Expand')
@@ -37,7 +37,7 @@
%section.settings.as-signup.no-animate#js-signup-settings{ class: ('expanded' if expanded_by_default?), data: { qa_selector: 'sign_up_restrictions_settings_content' } }
.settings-header
- %h4
+ %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
= _('Sign-up restrictions')
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
= expanded_by_default? ? _('Collapse') : _('Expand')
@@ -48,7 +48,7 @@
%section.settings.as-signin.no-animate#js-signin-settings{ class: ('expanded' if expanded_by_default?) }
.settings-header
- %h4
+ %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
= _('Sign-in restrictions')
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
= expanded_by_default? ? _('Collapse') : _('Expand')
@@ -60,7 +60,7 @@
%section.settings.as-terms.no-animate#js-terms-settings{ class: ('expanded' if expanded_by_default?) }
.settings-header
- %h4
+ %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
= _('Terms of Service and Privacy Policy')
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
= expanded_by_default? ? _('Collapse') : _('Expand')
@@ -74,7 +74,7 @@
%section.settings.as-terminal.no-animate#js-terminal-settings{ class: ('expanded' if expanded_by_default?) }
.settings-header
- %h4
+ %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
= _('Web terminal')
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
= expanded_by_default? ? _('Collapse') : _('Expand')
@@ -86,7 +86,7 @@
%section.settings.no-animate#js-web-ide-settings{ class: ('expanded' if expanded_by_default?) }
.settings-header
- %h4
+ %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
= _('Web IDE')
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
= expanded_by_default? ? _('Collapse') : _('Expand')
diff --git a/app/views/layouts/mailer.html.haml b/app/views/layouts/mailer.html.haml
index 580b8e67a3c..8452f0d9976 100644
--- a/app/views/layouts/mailer.html.haml
+++ b/app/views/layouts/mailer.html.haml
@@ -3,8 +3,6 @@
%td
%img.footer-logo{ alt: "GitLab", src: image_url('mailers/gitlab_logo_black_text.png') }
%div
- - manage_notifications_link = link_to(_("Manage all notifications"), profile_notifications_url, class: 'mng-notif-link')
- - help_link = link_to(_("Help"), help_url, class: 'help-link')
- = _("You're receiving this email because of your account on %{host}. %{manage_notifications_link} &middot; %{help_link}").html_safe % { host: Gitlab.config.gitlab.host, manage_notifications_link: manage_notifications_link, help_link: help_link }
+ = notification_reason_text(show_manage_notifications_link: true, show_help_link: true, format: :html)
= render 'layouts/mailer'
diff --git a/app/views/layouts/mailer.text.erb b/app/views/layouts/mailer.text.erb
index 1a06ea68bcd..24553734e49 100644
--- a/app/views/layouts/mailer.text.erb
+++ b/app/views/layouts/mailer.text.erb
@@ -3,7 +3,7 @@
<%= yield -%>
-- <%# signature marker %>
-<%= _("You're receiving this email because of your account on %{host}.") % { host: Gitlab.config.gitlab.host } %>
+<%= notification_reason_text %>
<%= render_if_exists 'layouts/mailer/additional_text' %>
<%= text_footer_message %>
diff --git a/app/views/layouts/notify.html.haml b/app/views/layouts/notify.html.haml
index d9f16a89fbc..d05b6951fbf 100644
--- a/app/views/layouts/notify.html.haml
+++ b/app/views/layouts/notify.html.haml
@@ -26,16 +26,7 @@
- else
#{link_to _("View it on GitLab"), @target_url}.
%br
- -# Don't link the host in the line below, one link in the email is easier to quickly click than two.
- = notification_reason_text(@reason)
- If you'd like to receive fewer emails, you can
- - if @labels_url
- adjust your #{link_to 'label subscriptions', @labels_url}.
- - else
- - if @unsubscribe_url
- = link_to "unsubscribe", @unsubscribe_url
- from this thread or
- adjust your notification settings.
+ = notification_reason_text(reason: @reason, show_manage_notifications_link: !@labels_url, show_help_link: true, manage_label_subscriptions_url: @labels_url, unsubscribe_url: @unsubscribe_url, format: :html)
= email_action @target_url
diff --git a/app/views/layouts/notify.text.erb b/app/views/layouts/notify.text.erb
index 49ad0b5abc5..4eae96dc376 100644
--- a/app/views/layouts/notify.text.erb
+++ b/app/views/layouts/notify.text.erb
@@ -11,7 +11,7 @@
<% end -%>
<% end -%>
-<%= notification_reason_text(@reason) %>
+<%= notification_reason_text(reason: @reason) %>
<%= render_if_exists 'layouts/mailer/additional_text' %>
<%= text_footer_message -%>
diff --git a/app/views/notify/approved_merge_request_email.html.haml b/app/views/notify/approved_merge_request_email.html.haml
index c51fe02370d..28da1182d49 100644
--- a/app/views/notify/approved_merge_request_email.html.haml
+++ b/app/views/notify/approved_merge_request_email.html.haml
@@ -152,6 +152,4 @@
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;" }
%img{ alt: "GitLab", src: image_url('mailers/gitlab_logo_black_text.png'), style: "display:block;margin:0 auto 1em;", width: "90" }/
%div
- - manage_notifications_link = link_to(_("Manage all notifications"), profile_notifications_url, style: "color:#3777b0;text-decoration:none;")
- - help_link = link_to(_("Help"), help_url, style: "color:#3777b0;text-decoration:none;")
- = _("You're receiving this email because of your account on %{host}. %{manage_notifications_link} &middot; %{help_link}").html_safe % { host: Gitlab.config.gitlab.host, manage_notifications_link: manage_notifications_link, help_link: help_link }
+ = notification_reason_text(show_manage_notifications_link: true, show_help_link: true, format: :html)
diff --git a/app/views/notify/merge_when_pipeline_succeeds_email.html.haml b/app/views/notify/merge_when_pipeline_succeeds_email.html.haml
index 550d386c843..f6b517d6e34 100644
--- a/app/views/notify/merge_when_pipeline_succeeds_email.html.haml
+++ b/app/views/notify/merge_when_pipeline_succeeds_email.html.haml
@@ -148,6 +148,4 @@
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;" }
%img{ alt: "GitLab", src: image_url('mailers/gitlab_logo_black_text.png'), style: "display:block;margin:0 auto 1em;", width: "90" }
%div
- - manage_notifications_link = link_to(_("Manage all notifications"), profile_notifications_url, style: "color:#3777b0;text-decoration:none;")
- - help_link = link_to(_("Help"), help_url, style: "color:#3777b0;text-decoration:none;")
- = _("You're receiving this email because of your account on %{host}. %{manage_notifications_link} &middot; %{help_link}").html_safe % { host: Gitlab.config.gitlab.host, manage_notifications_link: manage_notifications_link, help_link: help_link }
+ = notification_reason_text(show_manage_notifications_link: true, show_help_link: true, format: :html)
diff --git a/app/views/notify/unapproved_merge_request_email.html.haml b/app/views/notify/unapproved_merge_request_email.html.haml
index ae58ccd3995..0b8fbe14228 100644
--- a/app/views/notify/unapproved_merge_request_email.html.haml
+++ b/app/views/notify/unapproved_merge_request_email.html.haml
@@ -151,6 +151,4 @@
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;" }
%img{ alt: "GitLab", src: image_url('mailers/gitlab_logo_black_text.png'), style: "display:block;margin:0 auto 1em;", width: "90" }/
%div
- - manage_notifications_link = link_to(_("Manage all notifications"), profile_notifications_url, style: "color:#3777b0;text-decoration:none;")
- - help_link = link_to(_("Help"), help_url, style: "color:#3777b0;text-decoration:none;")
- = _("You're receiving this email because of your account on %{host}. %{manage_notifications_link} &middot; %{help_link}").html_safe % { host: Gitlab.config.gitlab.host, manage_notifications_link: manage_notifications_link, help_link: help_link }
+ = notification_reason_text(show_manage_notifications_link: true, show_help_link: true, format: :html)
diff --git a/app/views/projects/jobs/show.html.haml b/app/views/projects/jobs/show.html.haml
index fedc1291a92..5f249f693ff 100644
--- a/app/views/projects/jobs/show.html.haml
+++ b/app/views/projects/jobs/show.html.haml
@@ -7,7 +7,4 @@
= render_if_exists "shared/shared_runners_minutes_limit_flash_message"
-- if @build.is_a? ::Ci::Build
- #js-job-page{ data: jobs_data }
-- else
- #js-bridge-page{ data: bridge_data(@build, @project) }
+#js-job-page{ data: jobs_data }
diff --git a/app/views/pwa/offline.html.haml b/app/views/pwa/offline.html.haml
index 5eae546bea9..cd1fca5d2b5 100644
--- a/app/views/pwa/offline.html.haml
+++ b/app/views/pwa/offline.html.haml
@@ -1,5 +1,5 @@
= link_to root_path do
- = render 'shared/logo.svg'
+ = render partial: 'shared/logo', formats: :svg
%h1= _('Offline')
.container
%h3= _('You are currently offline, or the GitLab instance is not reachable.')
diff --git a/config/feature_flags/development/ci_docker_image_pull_policy.yml b/config/feature_flags/development/ci_docker_image_pull_policy.yml
index 09e01fb5232..5bdcdf03d27 100644
--- a/config/feature_flags/development/ci_docker_image_pull_policy.yml
+++ b/config/feature_flags/development/ci_docker_image_pull_policy.yml
@@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/363186
milestone: '15.1'
type: development
group: group::pipeline authoring
-default_enabled: false
+default_enabled: true
diff --git a/config/feature_flags/development/trigger_job_retry_action.yml b/config/feature_flags/development/trigger_job_retry_action.yml
deleted file mode 100644
index 79a8593fd05..00000000000
--- a/config/feature_flags/development/trigger_job_retry_action.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: trigger_job_retry_action
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/77951
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/349966
-milestone: '14.7'
-type: development
-group: group::pipeline authoring
-default_enabled: false
diff --git a/db/docs/namespace_bans.yml b/db/docs/namespace_bans.yml
new file mode 100644
index 00000000000..7e11738ab81
--- /dev/null
+++ b/db/docs/namespace_bans.yml
@@ -0,0 +1,9 @@
+---
+table_name: namespace_bans
+classes:
+ - NamespaceBan
+feature_categories:
+ - instance_resiliency
+description: Contains users banned from namespaces
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/91271
+milestone: "15.2"
diff --git a/db/migrate/20220628120708_create_namespace_bans.rb b/db/migrate/20220628120708_create_namespace_bans.rb
new file mode 100644
index 00000000000..657d13f6448
--- /dev/null
+++ b/db/migrate/20220628120708_create_namespace_bans.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class CreateNamespaceBans < Gitlab::Database::Migration[2.0]
+ UNIQUE_INDEX_NAME = 'index_namespace_bans_on_namespace_id_and_user_id'
+
+ def change
+ create_table :namespace_bans do |t|
+ t.bigint :namespace_id, null: false
+ t.bigint :user_id, null: false, index: true
+ t.timestamps_with_timezone
+
+ t.index [:namespace_id, :user_id], unique: true, name: UNIQUE_INDEX_NAME
+ end
+ end
+end
diff --git a/db/migrate/20220628121644_add_namespace_bans_namespace_id_foreign_key.rb b/db/migrate/20220628121644_add_namespace_bans_namespace_id_foreign_key.rb
new file mode 100644
index 00000000000..30928123618
--- /dev/null
+++ b/db/migrate/20220628121644_add_namespace_bans_namespace_id_foreign_key.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class AddNamespaceBansNamespaceIdForeignKey < Gitlab::Database::Migration[2.0]
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_foreign_key :namespace_bans, :namespaces, column: :namespace_id, on_delete: :cascade
+ end
+
+ def down
+ with_lock_retries do
+ remove_foreign_key :namespace_bans, column: :namespace_id
+ end
+ end
+end
diff --git a/db/migrate/20220628121712_add_namespace_bans_user_id_foreign_key.rb b/db/migrate/20220628121712_add_namespace_bans_user_id_foreign_key.rb
new file mode 100644
index 00000000000..16a73c29cae
--- /dev/null
+++ b/db/migrate/20220628121712_add_namespace_bans_user_id_foreign_key.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class AddNamespaceBansUserIdForeignKey < Gitlab::Database::Migration[2.0]
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_foreign_key :namespace_bans, :users, column: :user_id, on_delete: :cascade
+ end
+
+ def down
+ with_lock_retries do
+ remove_foreign_key :namespace_bans, column: :user_id
+ end
+ end
+end
diff --git a/db/migrate/20220629220129_increase_webauthn_xid_length.rb b/db/migrate/20220629220129_increase_webauthn_xid_length.rb
new file mode 100644
index 00000000000..c5b107ce3f6
--- /dev/null
+++ b/db/migrate/20220629220129_increase_webauthn_xid_length.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class IncreaseWebauthnXidLength < Gitlab::Database::Migration[2.0]
+ disable_ddl_transaction!
+
+ def up
+ new_constraint_name = check_constraint_name(:webauthn_registrations, :credential_xid, 'max_length_v3')
+ add_text_limit :webauthn_registrations, :credential_xid, 1364, constraint_name: new_constraint_name
+
+ prev_constraint_name = check_constraint_name(:webauthn_registrations, :credential_xid, 'max_length_v2')
+ remove_text_limit :webauthn_registrations, :credential_xid, constraint_name: prev_constraint_name
+ end
+
+ def down
+ # no-op: Danger of failling if there are records with length(credential_xid) > 1364
+ end
+end
diff --git a/db/post_migrate/20220706132238_add_indices_on_security_scans_info_column.rb b/db/post_migrate/20220706132238_add_indices_on_security_scans_info_column.rb
new file mode 100644
index 00000000000..48dc5b5d84a
--- /dev/null
+++ b/db/post_migrate/20220706132238_add_indices_on_security_scans_info_column.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+class AddIndicesOnSecurityScansInfoColumn < Gitlab::Database::Migration[2.0]
+ INDEX_NAME_ON_ERRORS = :index_security_scans_on_length_of_errors
+ INDEX_NAME_ON_WARNINGS = :index_security_scans_on_length_of_warnings
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index(
+ :security_scans,
+ "pipeline_id, jsonb_array_length(COALESCE((security_scans.info -> 'errors'::text), '[]'::jsonb))",
+ name: INDEX_NAME_ON_ERRORS
+ )
+
+ add_concurrent_index(
+ :security_scans,
+ "pipeline_id, jsonb_array_length(COALESCE((security_scans.info -> 'warnings'::text), '[]'::jsonb))",
+ name: INDEX_NAME_ON_WARNINGS
+ )
+ end
+
+ def down
+ remove_concurrent_index_by_name :security_scans, INDEX_NAME_ON_ERRORS
+ remove_concurrent_index_by_name :security_scans, INDEX_NAME_ON_WARNINGS
+ end
+end
diff --git a/db/schema_migrations/20220628120708 b/db/schema_migrations/20220628120708
new file mode 100644
index 00000000000..b6961491c93
--- /dev/null
+++ b/db/schema_migrations/20220628120708
@@ -0,0 +1 @@
+75027a5b09491b156837707af20406b2672d5ee3ce2272ecf1496e98da2861bf \ No newline at end of file
diff --git a/db/schema_migrations/20220628121644 b/db/schema_migrations/20220628121644
new file mode 100644
index 00000000000..cac9b9d9a0b
--- /dev/null
+++ b/db/schema_migrations/20220628121644
@@ -0,0 +1 @@
+90b9b47ef3671b73117205264589f895a083b0d00db00e684b25e60673d2e840 \ No newline at end of file
diff --git a/db/schema_migrations/20220628121712 b/db/schema_migrations/20220628121712
new file mode 100644
index 00000000000..e1c0ed37cff
--- /dev/null
+++ b/db/schema_migrations/20220628121712
@@ -0,0 +1 @@
+d64a9c41376bbb3bc2c9df846668b1a67b0bed1b1410d97dba17c19a2f322b38 \ No newline at end of file
diff --git a/db/schema_migrations/20220629220129 b/db/schema_migrations/20220629220129
new file mode 100644
index 00000000000..580da9df82a
--- /dev/null
+++ b/db/schema_migrations/20220629220129
@@ -0,0 +1 @@
+2f5e08212b2f733ce5812d7154879768532e31e642b647648d1c03fd4ddf8b13 \ No newline at end of file
diff --git a/db/schema_migrations/20220706132238 b/db/schema_migrations/20220706132238
new file mode 100644
index 00000000000..32ae901bfb9
--- /dev/null
+++ b/db/schema_migrations/20220706132238
@@ -0,0 +1 @@
+ea387b35bfb7f15a036aca9413b8fd15ede6b16048fa9e9be5a62b9e21ca362d \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index fb278f182b7..5bdd509b107 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -17493,6 +17493,23 @@ CREATE TABLE namespace_aggregation_schedules (
namespace_id integer NOT NULL
);
+CREATE TABLE namespace_bans (
+ id bigint NOT NULL,
+ namespace_id bigint NOT NULL,
+ user_id bigint NOT NULL,
+ created_at timestamp with time zone NOT NULL,
+ updated_at timestamp with time zone NOT NULL
+);
+
+CREATE SEQUENCE namespace_bans_id_seq
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
+ALTER SEQUENCE namespace_bans_id_seq OWNED BY namespace_bans.id;
+
CREATE TABLE namespace_ci_cd_settings (
namespace_id bigint NOT NULL,
allow_stale_runner_pruning boolean DEFAULT false NOT NULL
@@ -22447,7 +22464,7 @@ CREATE TABLE webauthn_registrations (
public_key text NOT NULL,
u2f_registration_id integer,
CONSTRAINT check_2f02e74321 CHECK ((char_length(name) <= 255)),
- CONSTRAINT check_e54008d9ce CHECK ((char_length(credential_xid) <= 340))
+ CONSTRAINT check_f5ab2b551a CHECK ((char_length(credential_xid) <= 1364))
);
CREATE SEQUENCE webauthn_registrations_id_seq
@@ -23223,6 +23240,8 @@ ALTER TABLE ONLY milestones ALTER COLUMN id SET DEFAULT nextval('milestones_id_s
ALTER TABLE ONLY namespace_admin_notes ALTER COLUMN id SET DEFAULT nextval('namespace_admin_notes_id_seq'::regclass);
+ALTER TABLE ONLY namespace_bans ALTER COLUMN id SET DEFAULT nextval('namespace_bans_id_seq'::regclass);
+
ALTER TABLE ONLY namespace_statistics ALTER COLUMN id SET DEFAULT nextval('namespace_statistics_id_seq'::regclass);
ALTER TABLE ONLY namespaces ALTER COLUMN id SET DEFAULT nextval('namespaces_id_seq'::regclass);
@@ -25234,6 +25253,9 @@ ALTER TABLE ONLY namespace_admin_notes
ALTER TABLE ONLY namespace_aggregation_schedules
ADD CONSTRAINT namespace_aggregation_schedules_pkey PRIMARY KEY (namespace_id);
+ALTER TABLE ONLY namespace_bans
+ ADD CONSTRAINT namespace_bans_pkey PRIMARY KEY (id);
+
ALTER TABLE ONLY namespace_ci_cd_settings
ADD CONSTRAINT namespace_ci_cd_settings_pkey PRIMARY KEY (namespace_id);
@@ -28780,6 +28802,10 @@ CREATE INDEX index_namespace_admin_notes_on_namespace_id ON namespace_admin_note
CREATE UNIQUE INDEX index_namespace_aggregation_schedules_on_namespace_id ON namespace_aggregation_schedules USING btree (namespace_id);
+CREATE UNIQUE INDEX index_namespace_bans_on_namespace_id_and_user_id ON namespace_bans USING btree (namespace_id, user_id);
+
+CREATE INDEX index_namespace_bans_on_user_id ON namespace_bans USING btree (user_id);
+
CREATE UNIQUE INDEX index_namespace_root_storage_statistics_on_namespace_id ON namespace_root_storage_statistics USING btree (namespace_id);
CREATE UNIQUE INDEX index_namespace_statistics_on_namespace_id ON namespace_statistics USING btree (namespace_id);
@@ -29540,6 +29566,10 @@ CREATE INDEX index_security_scans_on_created_at ON security_scans USING btree (c
CREATE INDEX index_security_scans_on_date_created_at_and_id ON security_scans USING btree (date(timezone('UTC'::text, created_at)), id);
+CREATE INDEX index_security_scans_on_length_of_errors ON security_scans USING btree (pipeline_id, jsonb_array_length(COALESCE((info -> 'errors'::text), '[]'::jsonb)));
+
+CREATE INDEX index_security_scans_on_length_of_warnings ON security_scans USING btree (pipeline_id, jsonb_array_length(COALESCE((info -> 'warnings'::text), '[]'::jsonb)));
+
CREATE INDEX index_security_scans_on_pipeline_id ON security_scans USING btree (pipeline_id);
CREATE INDEX index_security_scans_on_project_id ON security_scans USING btree (project_id);
@@ -31777,6 +31807,9 @@ ALTER TABLE ONLY protected_environment_approval_rules
ALTER TABLE ONLY ci_pipeline_schedule_variables
ADD CONSTRAINT fk_41c35fda51 FOREIGN KEY (pipeline_schedule_id) REFERENCES ci_pipeline_schedules(id) ON DELETE CASCADE;
+ALTER TABLE ONLY namespace_bans
+ ADD CONSTRAINT fk_4275fbb1d7 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
+
ALTER TABLE ONLY geo_event_log
ADD CONSTRAINT fk_42c3b54bed FOREIGN KEY (cache_invalidation_event_id) REFERENCES geo_cache_invalidation_events(id) ON DELETE CASCADE;
@@ -32164,6 +32197,9 @@ ALTER TABLE ONLY deployments
ALTER TABLE ONLY routes
ADD CONSTRAINT fk_bb2e5b8968 FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE;
+ALTER TABLE ONLY namespace_bans
+ ADD CONSTRAINT fk_bcc024eef2 FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE;
+
ALTER TABLE ONLY gitlab_subscriptions
ADD CONSTRAINT fk_bd0c4019c3 FOREIGN KEY (hosted_plan_id) REFERENCES plans(id) ON DELETE CASCADE;
diff --git a/doc/administration/monitoring/prometheus/gitlab_metrics.md b/doc/administration/monitoring/prometheus/gitlab_metrics.md
index 4f8fbd0c07e..b19b342c665 100644
--- a/doc/administration/monitoring/prometheus/gitlab_metrics.md
+++ b/doc/administration/monitoring/prometheus/gitlab_metrics.md
@@ -347,6 +347,7 @@ Some basic Ruby runtime metrics are available:
|:---------------------------------------- |:--------- |:----- |:----------- |
| `ruby_gc_duration_seconds` | Counter | 11.1 | Time spent by Ruby in GC |
| `ruby_gc_stat_...` | Gauge | 11.1 | Various metrics from [GC.stat](https://ruby-doc.org/core-2.6.5/GC.html#method-c-stat) |
+| `ruby_gc_stat_ext_heap_fragmentation` | Gauge | 15.2 | Degree of Ruby heap fragmentation as live objects versus eden slots (range 0 to 1) |
| `ruby_file_descriptors` | Gauge | 11.1 | File descriptors per process |
| `ruby_sampler_duration_seconds` | Counter | 11.1 | Time spent collecting stats |
| `ruby_process_cpu_seconds_total` | Gauge | 12.0 | Total amount of CPU time per process |
diff --git a/doc/ci/yaml/index.md b/doc/ci/yaml/index.md
index ad600761f75..2a7236e460b 100644
--- a/doc/ci/yaml/index.md
+++ b/doc/ci/yaml/index.md
@@ -3530,12 +3530,12 @@ in that container.
#### `service:pull_policy`
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/21619) in GitLab 15.1 [with a flag](../../administration/feature_flags.md) named `ci_docker_image_pull_policy`. Disabled by default.
+> - [Enabled on GitLab.com and self-managed](https://gitlab.com/gitlab-org/gitlab/-/issues/363186) in GitLab 15.2.
> - Requires GitLab Runner 15.1 or later.
FLAG:
-On self-managed GitLab, by default this feature is not available. To make it available,
-ask an administrator to [enable the feature flag](../../administration/feature_flags.md) named `ci_docker_image_pull_policy`.
-The feature is not ready for production use.
+On self-managed GitLab, by default this feature is available. To hide the feature,
+ask an administrator to [disable the feature flag](../../administration/feature_flags.md) named `ci_docker_image_pull_policy`.
The pull policy that the runner uses to fetch the Docker image.
diff --git a/doc/development/integrations/secure.md b/doc/development/integrations/secure.md
index a68c0630b18..af2566934d1 100644
--- a/doc/development/integrations/secure.md
+++ b/doc/development/integrations/secure.md
@@ -360,6 +360,41 @@ Ongoing improvements to report validation are tracked [in this epic](https://git
In the meantime, you can see which versions are supported in the
[source code](https://gitlab.com/gitlab-org/gitlab/-/blob/08dd756429731a0cca1e27ca9d59eea226398a7d/lib/gitlab/ci/parsers/security/validators/schema_validator.rb#L9-27).
+#### Validate locally
+
+Before running your analyzer in GitLab, you should validate the report produced by your analyzer to
+ensure it complies with the declared schema version.
+
+Use the script below to validate JSON files against a given schema.
+
+```ruby
+require 'bundler/inline'
+
+gemfile do
+ source 'https://rubygems.org'
+ gem 'json_schemer'
+end
+
+require 'json'
+require 'pathname'
+
+raise 'Usage: ruby script.rb <security schema file name> <report file name>' unless ARGV.size == 2
+
+schema = JSONSchemer.schema(Pathname.new(ARGV[0]))
+report = JSON.parse(File.open(ARGV[1]).read)
+schema_validation_errors = schema.validate(report).map { |error| JSONSchemer::Errors.pretty(error) }
+puts(schema_validation_errors)
+```
+
+1. Download the appropriate schema that matches your report type and declared version. For
+ example, you can find version `14.0.6` of the `container_scanning` report schema at
+ `https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/raw/v14.0.6/dist/container-scanning-report-format.json?inline=false`.
+1. Save the Ruby script above in a file, for example, `validate.rb`.
+1. Run the script, passing the schema and report file names as arguments in order. For example:
+ 1. Using your local Ruby interpreter: `ruby validate.rb container-scanning-format_14-0-6.json gl-container-scanning-report.json`.
+ 1. Using Docker: `docker run -it --rm -v $(pwd):/ci ruby:3-slim ruby /ci/validate.rb /ci/container-scanning-format_14-0-6.json /ci/gl-container-scanning-report.json`
+1. Validation errors are shown on the screen. You must resolve these errors before GitLab can ingest your report.
+
### Report Fields
#### Version
diff --git a/doc/user/analytics/ci_cd_analytics.md b/doc/user/analytics/ci_cd_analytics.md
index 920b651c094..f4075c3420b 100644
--- a/doc/user/analytics/ci_cd_analytics.md
+++ b/doc/user/analytics/ci_cd_analytics.md
@@ -78,7 +78,7 @@ To view the lead time for changes chart:
![Lead time](img/lead_time_chart_v13_11.png)
-## View time to restore service chart **(PREMIUM)**
+## View time to restore service chart **(ULTIMATE)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/356959) in GitLab 15.1
@@ -93,3 +93,17 @@ To view the time to restore service chart:
1. Select the **Time to restore service** tab.
![Lead time](img/time_to_restore_service_charts_v15_1.png)
+
+## View change failure rate chart **(ULTIMATE)**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/357072) in GitLab 15.2
+
+The change failure rate chart shows information about the percentage of deployments that cause an incident in a production environment. This chart is available for groups and projects.
+
+Change failure rate is one of the four [DORA metrics](index.md#devops-research-and-assessment-dora-key-metrics) that DevOps teams use for measuring excellence in software delivery.
+
+To view the change failure rate chart:
+
+1. On the top bar, select **Menu > Projects** and find your project.
+1. On the left sidebar, select **Analytics > CI/CD Analytics**.
+1. Select the **Change failure rate** tab.
diff --git a/doc/user/project/integrations/webhook_events.md b/doc/user/project/integrations/webhook_events.md
index e6bf766e177..32e5f949c15 100644
--- a/doc/user/project/integrations/webhook_events.md
+++ b/doc/user/project/integrations/webhook_events.md
@@ -873,6 +873,7 @@ Payload example:
},
"object_attributes": {
"id": 99,
+ "iid": 1,
"target_branch": "master",
"source_branch": "ms-viewport",
"source_project_id": 14,
@@ -884,10 +885,12 @@ Payload example:
"milestone_id": null,
"state": "opened",
"blocking_discussions_resolved": true,
+ "work_in_progress": false,
+ "first_contribution": true,
"merge_status": "unchecked",
"target_project_id": 14,
- "iid": 1,
"description": "",
+ "url": "http://example.com/diaspora/merge_requests/1",
"source": {
"name":"Awesome Project",
"description":"Aut reprehenderit ut est.",
@@ -930,8 +933,18 @@ Payload example:
"email": "gitlabdev@dv6700.(none)"
}
},
- "work_in_progress": false,
- "url": "http://example.com/diaspora/merge_requests/1",
+ "labels": [{
+ "id": 206,
+ "title": "API",
+ "color": "#ffffff",
+ "project_id": 14,
+ "created_at": "2013-12-03T17:15:43Z",
+ "updated_at": "2013-12-03T17:15:43Z",
+ "template": false,
+ "description": "API related issues",
+ "type": "ProjectLabel",
+ "group_id": 41
+ }],
"action": "open",
"assignee": {
"name": "User1",
@@ -990,6 +1003,9 @@ Payload example:
}
```
+NOTE:
+The fields `assignee_id`, and `state` are deprecated.
+
## Wiki page events
Wiki page events are triggered when a wiki page is created, updated, or deleted.
diff --git a/lib/api/entities/terraform/module_version.rb b/lib/api/entities/terraform/module_version.rb
new file mode 100644
index 00000000000..411fa09465e
--- /dev/null
+++ b/lib/api/entities/terraform/module_version.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module Terraform
+ class ModuleVersion < Grape::Entity
+ expose :name
+ expose :provider
+ expose :providers
+ expose :root
+ expose :source
+ expose :submodules
+ expose :version
+ expose :versions
+ end
+ end
+ end
+end
diff --git a/lib/api/terraform/modules/v1/packages.rb b/lib/api/terraform/modules/v1/packages.rb
index 816a02966a7..4d3719a04fe 100644
--- a/lib/api/terraform/modules/v1/packages.rb
+++ b/lib/api/terraform/modules/v1/packages.rb
@@ -151,6 +151,16 @@ module API
present_carrierwave_file!(package_file.file)
end
end
+
+ # This endpoint has to be the last within namespace '*module_version' block
+ # due to how the route matching works in grape
+ # format: false is required, otherwise grape splits the semver version into 2 params:
+ # params[:module_version] and params[:format],
+ # thus leading to an invalid/not found module version
+ get format: false do
+ presenter = ::Terraform::ModuleVersionPresenter.new(package, params[:module_system])
+ present presenter, with: ::API::Entities::Terraform::ModuleVersion
+ end
end
end
diff --git a/lib/api/usage_data.rb b/lib/api/usage_data.rb
index 6e81a578d4a..9e446aff605 100644
--- a/lib/api/usage_data.rb
+++ b/lib/api/usage_data.rb
@@ -29,7 +29,7 @@ module API
params do
requires :event, type: String, desc: 'The event name that should be tracked'
end
- post 'increment_unique_users' do
+ post 'increment_unique_users', urgency: :low do
event_name = params[:event]
increment_unique_values(event_name, current_user.id)
diff --git a/lib/gitlab/data_builder/issuable.rb b/lib/gitlab/data_builder/issuable.rb
index 9a0b964915c..d12537c4874 100644
--- a/lib/gitlab/data_builder/issuable.rb
+++ b/lib/gitlab/data_builder/issuable.rb
@@ -18,7 +18,7 @@ module Gitlab
user: user.hook_attrs,
project: issuable.project.hook_attrs,
object_attributes: issuable_builder.new(issuable).build,
- labels: issuable.labels.map(&:hook_attrs),
+ labels: issuable.labels_hook_attrs,
changes: final_changes(changes.slice(*safe_keys)),
# DEPRECATED
repository: issuable.project.hook_attrs.slice(:name, :url, :description, :homepage)
diff --git a/lib/gitlab/database/gitlab_schemas.yml b/lib/gitlab/database/gitlab_schemas.yml
index 6ddbb64b956..e850e876ec1 100644
--- a/lib/gitlab/database/gitlab_schemas.yml
+++ b/lib/gitlab/database/gitlab_schemas.yml
@@ -327,6 +327,7 @@ milestone_releases: :gitlab_main
milestones: :gitlab_main
namespace_admin_notes: :gitlab_main
namespace_aggregation_schedules: :gitlab_main
+namespace_bans: :gitlab_main
namespace_limits: :gitlab_main
namespace_package_settings: :gitlab_main
namespace_root_storage_statistics: :gitlab_main
diff --git a/lib/gitlab/hook_data/merge_request_builder.rb b/lib/gitlab/hook_data/merge_request_builder.rb
index 2b4bdbd48bd..b4f90715293 100644
--- a/lib/gitlab/hook_data/merge_request_builder.rb
+++ b/lib/gitlab/hook_data/merge_request_builder.rb
@@ -62,7 +62,8 @@ module Gitlab
assignee_id: merge_request.assignee_ids.first, # This key is deprecated
labels: merge_request.labels_hook_attrs,
state: merge_request.state, # This key is deprecated
- blocking_discussions_resolved: merge_request.mergeable_discussions_state?
+ blocking_discussions_resolved: merge_request.mergeable_discussions_state?,
+ first_contribution: merge_request.first_contribution?
}
merge_request.attributes.with_indifferent_access.slice(*self.class.safe_hook_attributes)
diff --git a/lib/gitlab/import_export/project/import_export.yml b/lib/gitlab/import_export/project/import_export.yml
index 22c98981f64..50ff6146174 100644
--- a/lib/gitlab/import_export/project/import_export.yml
+++ b/lib/gitlab/import_export/project/import_export.yml
@@ -977,7 +977,7 @@ methods:
statuses:
- :type
merge_request_diff_files:
- - :diff_export
+ - :utf8_diff
merge_requests:
- :diff_head_sha
- :source_branch_sha
diff --git a/lib/gitlab/metrics/memory.rb b/lib/gitlab/metrics/memory.rb
new file mode 100644
index 00000000000..c165cdec7a3
--- /dev/null
+++ b/lib/gitlab/metrics/memory.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Metrics
+ module Memory
+ extend self
+
+ HEAP_SLOTS_PER_PAGE = GC::INTERNAL_CONSTANTS[:HEAP_PAGE_OBJ_LIMIT]
+
+ def gc_heap_fragmentation(gc_stat = GC.stat)
+ 1 - (gc_stat[:heap_live_slots] / (HEAP_SLOTS_PER_PAGE * gc_stat[:heap_eden_pages].to_f))
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/metrics/samplers/ruby_sampler.rb b/lib/gitlab/metrics/samplers/ruby_sampler.rb
index 4a3ef3711a5..8e002293347 100644
--- a/lib/gitlab/metrics/samplers/ruby_sampler.rb
+++ b/lib/gitlab/metrics/samplers/ruby_sampler.rb
@@ -39,7 +39,8 @@ module Gitlab
process_proportional_memory_bytes: ::Gitlab::Metrics.gauge(metric_name(:process, :proportional_memory_bytes), 'Memory used (PSS)', labels),
process_start_time_seconds: ::Gitlab::Metrics.gauge(metric_name(:process, :start_time_seconds), 'Process start time seconds'),
sampler_duration: ::Gitlab::Metrics.counter(metric_name(:sampler, :duration_seconds_total), 'Sampler time', labels),
- gc_duration_seconds: ::Gitlab::Metrics.histogram(metric_name(:gc, :duration_seconds), 'GC time', labels, GC_REPORT_BUCKETS)
+ gc_duration_seconds: ::Gitlab::Metrics.histogram(metric_name(:gc, :duration_seconds), 'GC time', labels, GC_REPORT_BUCKETS),
+ heap_fragmentation: ::Gitlab::Metrics.gauge(metric_name(:gc_stat_ext, :heap_fragmentation), 'Ruby heap fragmentation', labels)
}
GC.stat.keys.each do |key|
@@ -76,8 +77,13 @@ module Gitlab
end
# Collect generic GC stats
- GC.stat.each do |key, value|
- metrics[key].set(labels, value)
+ GC.stat.then do |gc_stat|
+ gc_stat.each do |key, value|
+ metrics[key].set(labels, value)
+ end
+
+ # Collect custom GC stats
+ metrics[:heap_fragmentation].set(labels, Memory.gc_heap_fragmentation(gc_stat))
end
end
diff --git a/lib/unnested_in_filters/rewriter.rb b/lib/unnested_in_filters/rewriter.rb
index c953b673c0e..cba002a5632 100644
--- a/lib/unnested_in_filters/rewriter.rb
+++ b/lib/unnested_in_filters/rewriter.rb
@@ -105,6 +105,8 @@ module UnnestedInFilters
# LIMIT 20
#
def rewrite
+ log_rewrite
+
model.from(from)
.limit(limit_value)
.order(order_values)
@@ -125,6 +127,10 @@ module UnnestedInFilters
delegate :model, :order_values, :limit_value, :where_values_hash, to: :relation, private: true
+ def log_rewrite
+ ::Gitlab::AppLogger.info(message: 'Query is being rewritten by `UnnestedInFilters`', model: model.name)
+ end
+
def from
[value_tables.map(&:to_sql) + [lateral]].join(', ')
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index eb973319562..3eef677b848 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -6969,6 +6969,9 @@ msgstr ""
msgid "CICDAnalytics|All time"
msgstr ""
+msgid "CICDAnalytics|Change failure rate"
+msgstr ""
+
msgid "CICDAnalytics|Deployment frequency"
msgstr ""
@@ -23625,9 +23628,6 @@ msgstr ""
msgid "Manage access"
msgstr ""
-msgid "Manage all notifications"
-msgstr ""
-
msgid "Manage applications that can use GitLab as an OAuth provider, and applications that you've authorized to use your account."
msgstr ""
@@ -32982,12 +32982,6 @@ msgstr ""
msgid "Retry migration"
msgstr ""
-msgid "Retry the downstream pipeline"
-msgstr ""
-
-msgid "Retry the trigger job"
-msgstr ""
-
msgid "Retry this job"
msgstr ""
@@ -39678,9 +39672,6 @@ msgstr ""
msgid "This job requires manual intervention to start. Before starting this job, you can add variables below for last-minute configuration changes."
msgstr ""
-msgid "This job triggers a downstream pipeline"
-msgstr ""
-
msgid "This job will automatically run after its timer finishes. Often they are used for incremental roll-out deploys to production environments. When unscheduled it converts into a manual action."
msgstr ""
@@ -42467,9 +42458,6 @@ msgstr ""
msgid "View documentation"
msgstr ""
-msgid "View downstream pipeline"
-msgstr ""
-
msgid "View eligible approvers"
msgstr ""
@@ -44489,18 +44477,36 @@ msgstr ""
msgid "You're receiving this email because of your account on %{host}."
msgstr ""
-msgid "You're receiving this email because of your account on %{host}. %{manage_notifications_link} &middot; %{help_link}"
+msgid "You're receiving this email because of your account on %{host}. %{manage_label_subscriptions_link_start}Manage label subscriptions%{manage_label_subscriptions_link_end} &middot; %{help_link_start}Help%{help_link_end}"
+msgstr ""
+
+msgid "You're receiving this email because of your account on %{host}. %{manage_notifications_link_start}Manage all notifications%{manage_notifications_link_end} &middot; %{help_link_start}Help%{help_link_end}"
+msgstr ""
+
+msgid "You're receiving this email because of your account on %{host}. %{unsubscribe_link_start}Unsubscribe%{unsubscribe_link_end} from this thread &middot; %{manage_notifications_link_start}Manage all notifications%{manage_notifications_link_end} &middot; %{help_link_start}Help%{help_link_end}"
msgstr ""
msgid "You're receiving this email because of your activity on %{host}."
msgstr ""
+msgid "You're receiving this email because of your activity on %{host}. %{unsubscribe_link_start}Unsubscribe%{unsubscribe_link_end} from this thread &middot; %{manage_notifications_link_start}Manage all notifications%{manage_notifications_link_end} &middot; %{help_link_start}Help%{help_link_end}"
+msgstr ""
+
msgid "You're receiving this email because you have been assigned an item on %{host}."
msgstr ""
+msgid "You're receiving this email because you have been assigned an item on %{host}. %{unsubscribe_link_start}Unsubscribe%{unsubscribe_link_end} from this thread &middot; %{manage_notifications_link_start}Manage all notifications%{manage_notifications_link_end} &middot; %{help_link_start}Help%{help_link_end}"
+msgstr ""
+
msgid "You're receiving this email because you have been mentioned on %{host}."
msgstr ""
+msgid "You're receiving this email because you have been mentioned on %{host}. %{manage_notifications_link_start}Manage all notifications%{manage_notifications_link_end} &middot; %{help_link_start}Help%{help_link_end}"
+msgstr ""
+
+msgid "You're receiving this email because you have been mentioned on %{host}. %{unsubscribe_link_start}Unsubscribe%{unsubscribe_link_end} from this thread &middot; %{manage_notifications_link_start}Manage all notifications%{manage_notifications_link_end} &middot; %{help_link_start}Help%{help_link_end}"
+msgstr ""
+
msgid "You've already enabled two-factor authentication using one time password authenticators. In order to register a different device, you must first disable two-factor authentication."
msgstr ""
@@ -44930,6 +44936,9 @@ msgstr ""
msgid "allowed to fail"
msgstr ""
+msgid "already banned from namespace"
+msgstr ""
+
msgid "already being used for another group or project %{timebox_name}."
msgstr ""
diff --git a/metrics_server/dependencies.rb b/metrics_server/dependencies.rb
index 3f188658ba2..233511eb505 100644
--- a/metrics_server/dependencies.rb
+++ b/metrics_server/dependencies.rb
@@ -20,6 +20,7 @@ require_relative '../lib/prometheus/cleanup_multiproc_dir_service'
require_relative '../lib/gitlab/metrics/prometheus'
require_relative '../lib/gitlab/metrics'
require_relative '../lib/gitlab/metrics/system'
+require_relative '../lib/gitlab/metrics/memory'
require_relative '../lib/gitlab/metrics/samplers/base_sampler'
require_relative '../lib/gitlab/metrics/samplers/ruby_sampler'
require_relative '../lib/gitlab/metrics/exporter/base_exporter'
diff --git a/qa/qa/page/group/settings/package_registries.rb b/qa/qa/page/group/settings/package_registries.rb
index 433872a378a..fa24f7c2ce8 100644
--- a/qa/qa/page/group/settings/package_registries.rb
+++ b/qa/qa/page/group/settings/package_registries.rb
@@ -18,13 +18,13 @@ module QA
end
def set_allow_duplicates_disabled
- expand_content :package_registry_settings_content do
+ within_element :package_registry_settings_content do
click_on_allow_duplicates_button if duplicates_enabled?
end
end
def set_allow_duplicates_enabled
- expand_content :package_registry_settings_content do
+ within_element :package_registry_settings_content do
click_on_allow_duplicates_button unless duplicates_enabled?
end
end
@@ -49,7 +49,7 @@ module QA
end
def has_dependency_proxy_enabled?
- expand_content :dependency_proxy_settings_content do
+ within_element :dependency_proxy_settings_content do
within_element :dependency_proxy_setting_toggle do
toggle = find('button.gl-toggle')
toggle[:class].include?('is-checked')
diff --git a/spec/features/unsubscribe_links_spec.rb b/spec/features/unsubscribe_links_spec.rb
index 3fe276ce162..5317f586390 100644
--- a/spec/features/unsubscribe_links_spec.rb
+++ b/spec/features/unsubscribe_links_spec.rb
@@ -14,7 +14,7 @@ RSpec.describe 'Unsubscribe links', :sidekiq_might_not_need_inline do
let(:mail) { ActionMailer::Base.deliveries.last }
let(:body) { Capybara::Node::Simple.new(mail.default_part_body.to_s) }
let(:header_link) { mail.header['List-Unsubscribe'].to_s[1..-2] } # Strip angle brackets
- let(:body_link) { body.find_link('unsubscribe')['href'] }
+ let(:body_link) { body.find_link('Unsubscribe')['href'] }
before do
perform_enqueued_jobs { issue }
diff --git a/spec/fixtures/api/schemas/public_api/v4/packages/terraform/modules/v1/single_version.json b/spec/fixtures/api/schemas/public_api/v4/packages/terraform/modules/v1/single_version.json
new file mode 100644
index 00000000000..689b9427f38
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/packages/terraform/modules/v1/single_version.json
@@ -0,0 +1,55 @@
+{
+ "type": "object",
+ "required": [
+ "name",
+ "provider",
+ "providers",
+ "root",
+ "source",
+ "submodules",
+ "version",
+ "versions"
+ ],
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "provider": {
+ "type": "string"
+ },
+ "providers": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "root": {
+ "type": "object",
+ "required": [
+ "dependencies"
+ ],
+ "properties": {
+ "dependencies": {
+ "type": "array",
+ "maxItems": 0
+ }
+ }
+ },
+ "source": {
+ "type": "string"
+ },
+ "submodules": {
+ "type": "array",
+ "maxItems": 0
+ },
+ "version": {
+ "type": "string"
+ },
+ "versions": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/spec/frontend/jobs/bridge/app_spec.js b/spec/frontend/jobs/bridge/app_spec.js
deleted file mode 100644
index 210dcfa364b..00000000000
--- a/spec/frontend/jobs/bridge/app_spec.js
+++ /dev/null
@@ -1,146 +0,0 @@
-import Vue, { nextTick } from 'vue';
-import { shallowMount } from '@vue/test-utils';
-
-import { GlBreakpointInstance } from '@gitlab/ui/dist/utils';
-import { GlLoadingIcon } from '@gitlab/ui';
-import VueApollo from 'vue-apollo';
-import createMockApollo from 'helpers/mock_apollo_helper';
-import getPipelineQuery from '~/jobs/bridge/graphql/queries/pipeline.query.graphql';
-import waitForPromises from 'helpers/wait_for_promises';
-import BridgeApp from '~/jobs/bridge/app.vue';
-import BridgeEmptyState from '~/jobs/bridge/components/empty_state.vue';
-import BridgeSidebar from '~/jobs/bridge/components/sidebar.vue';
-import CiHeader from '~/vue_shared/components/header_ci_component.vue';
-import {
- MOCK_BUILD_ID,
- MOCK_PIPELINE_IID,
- MOCK_PROJECT_FULL_PATH,
- mockPipelineQueryResponse,
-} from './mock_data';
-
-describe('Bridge Show Page', () => {
- let wrapper;
- let mockApollo;
- let mockPipelineQuery;
-
- const createComponent = (options) => {
- wrapper = shallowMount(BridgeApp, {
- provide: {
- buildId: MOCK_BUILD_ID,
- projectFullPath: MOCK_PROJECT_FULL_PATH,
- pipelineIid: MOCK_PIPELINE_IID,
- },
- mocks: {
- $apollo: {
- queries: {
- pipeline: {
- loading: true,
- },
- },
- },
- },
- ...options,
- });
- };
-
- const createComponentWithApollo = () => {
- const handlers = [[getPipelineQuery, mockPipelineQuery]];
- Vue.use(VueApollo);
- mockApollo = createMockApollo(handlers);
-
- createComponent({
- apolloProvider: mockApollo,
- mocks: {},
- });
- };
-
- const findCiHeader = () => wrapper.findComponent(CiHeader);
- const findEmptyState = () => wrapper.findComponent(BridgeEmptyState);
- const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
- const findSidebar = () => wrapper.findComponent(BridgeSidebar);
-
- beforeEach(() => {
- mockPipelineQuery = jest.fn();
- });
-
- afterEach(() => {
- mockPipelineQuery.mockReset();
- wrapper.destroy();
- });
-
- describe('while pipeline query is loading', () => {
- beforeEach(() => {
- createComponent();
- });
-
- it('renders loading icon', () => {
- expect(findLoadingIcon().exists()).toBe(true);
- });
- });
-
- describe('after pipeline query is loaded', () => {
- beforeEach(async () => {
- mockPipelineQuery.mockResolvedValue(mockPipelineQueryResponse);
- createComponentWithApollo();
- await waitForPromises();
- });
-
- it('query is called with correct variables', async () => {
- expect(mockPipelineQuery).toHaveBeenCalledTimes(1);
- expect(mockPipelineQuery).toHaveBeenCalledWith({
- fullPath: MOCK_PROJECT_FULL_PATH,
- iid: MOCK_PIPELINE_IID,
- });
- });
-
- it('renders CI header state', () => {
- expect(findCiHeader().exists()).toBe(true);
- });
-
- it('renders empty state', () => {
- expect(findEmptyState().exists()).toBe(true);
- });
-
- it('renders sidebar', () => {
- expect(findSidebar().exists()).toBe(true);
- });
- });
-
- describe('sidebar expansion', () => {
- beforeEach(async () => {
- mockPipelineQuery.mockResolvedValue(mockPipelineQueryResponse);
- createComponentWithApollo();
- await waitForPromises();
- });
-
- describe('on resize', () => {
- it.each`
- breakpoint | isSidebarExpanded
- ${'xs'} | ${false}
- ${'sm'} | ${false}
- ${'md'} | ${true}
- ${'lg'} | ${true}
- ${'xl'} | ${true}
- `(
- 'sets isSidebarExpanded to `$isSidebarExpanded` when the breakpoint is "$breakpoint"',
- async ({ breakpoint, isSidebarExpanded }) => {
- jest.spyOn(GlBreakpointInstance, 'getBreakpointSize').mockReturnValue(breakpoint);
-
- window.dispatchEvent(new Event('resize'));
- await nextTick();
-
- expect(findSidebar().exists()).toBe(isSidebarExpanded);
- },
- );
- });
-
- it('toggles expansion on button click', async () => {
- expect(findSidebar().exists()).toBe(true);
-
- wrapper.vm.toggleSidebar();
- await nextTick();
-
- expect(findSidebar().exists()).toBe(false);
- });
- });
-});
diff --git a/spec/frontend/jobs/bridge/components/empty_state_spec.js b/spec/frontend/jobs/bridge/components/empty_state_spec.js
deleted file mode 100644
index 38c55b296f0..00000000000
--- a/spec/frontend/jobs/bridge/components/empty_state_spec.js
+++ /dev/null
@@ -1,58 +0,0 @@
-import { GlButton } from '@gitlab/ui';
-import { shallowMount } from '@vue/test-utils';
-import BridgeEmptyState from '~/jobs/bridge/components/empty_state.vue';
-import { MOCK_EMPTY_ILLUSTRATION_PATH, MOCK_PATH_TO_DOWNSTREAM } from '../mock_data';
-
-describe('Bridge Empty State', () => {
- let wrapper;
-
- const createComponent = ({ downstreamPipelinePath }) => {
- wrapper = shallowMount(BridgeEmptyState, {
- provide: {
- emptyStateIllustrationPath: MOCK_EMPTY_ILLUSTRATION_PATH,
- },
- propsData: {
- downstreamPipelinePath,
- },
- });
- };
-
- const findSvg = () => wrapper.find('img');
- const findTitle = () => wrapper.find('h1');
- const findLinkBtn = () => wrapper.findComponent(GlButton);
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- describe('template', () => {
- beforeEach(() => {
- createComponent({ downstreamPipelinePath: MOCK_PATH_TO_DOWNSTREAM });
- });
-
- it('renders illustration', () => {
- expect(findSvg().exists()).toBe(true);
- });
-
- it('renders title', () => {
- expect(findTitle().exists()).toBe(true);
- expect(findTitle().text()).toBe(wrapper.vm.$options.i18n.title);
- });
-
- it('renders CTA button', () => {
- expect(findLinkBtn().exists()).toBe(true);
- expect(findLinkBtn().text()).toBe(wrapper.vm.$options.i18n.linkBtnText);
- expect(findLinkBtn().attributes('href')).toBe(MOCK_PATH_TO_DOWNSTREAM);
- });
- });
-
- describe('without downstream pipeline', () => {
- beforeEach(() => {
- createComponent({ downstreamPipelinePath: undefined });
- });
-
- it('does not render CTA button', () => {
- expect(findLinkBtn().exists()).toBe(false);
- });
- });
-});
diff --git a/spec/frontend/jobs/bridge/components/sidebar_spec.js b/spec/frontend/jobs/bridge/components/sidebar_spec.js
deleted file mode 100644
index 5006d4f08a6..00000000000
--- a/spec/frontend/jobs/bridge/components/sidebar_spec.js
+++ /dev/null
@@ -1,99 +0,0 @@
-import { GlButton, GlDropdown } from '@gitlab/ui';
-import { shallowMount } from '@vue/test-utils';
-import BridgeSidebar from '~/jobs/bridge/components/sidebar.vue';
-import CommitBlock from '~/jobs/components/commit_block.vue';
-import { mockCommit, mockJob } from '../mock_data';
-
-describe('Bridge Sidebar', () => {
- let wrapper;
-
- const MockHeaderEl = {
- getBoundingClientRect() {
- return {
- bottom: '40',
- };
- },
- };
-
- const createComponent = ({ featureFlag } = {}) => {
- wrapper = shallowMount(BridgeSidebar, {
- provide: {
- glFeatures: {
- triggerJobRetryAction: featureFlag,
- },
- },
- propsData: {
- bridgeJob: mockJob,
- commit: mockCommit,
- },
- });
- };
-
- const findJobTitle = () => wrapper.find('h4');
- const findCommitBlock = () => wrapper.findComponent(CommitBlock);
- const findRetryDropdown = () => wrapper.find(GlDropdown);
- const findToggleBtn = () => wrapper.findComponent(GlButton);
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- describe('template', () => {
- beforeEach(() => {
- createComponent();
- });
-
- it('renders job name', () => {
- expect(findJobTitle().text()).toBe(mockJob.name);
- });
-
- it('renders commit information', () => {
- expect(findCommitBlock().exists()).toBe(true);
- });
- });
-
- describe('styles', () => {
- beforeEach(async () => {
- jest.spyOn(document, 'querySelector').mockReturnValue(MockHeaderEl);
- createComponent();
- });
-
- it('calculates root styles correctly', () => {
- expect(wrapper.attributes('style')).toBe('width: 290px; top: 40px;');
- });
- });
-
- describe('sidebar expansion', () => {
- beforeEach(() => {
- createComponent();
- });
-
- it('emits toggle sidebar event on button click', async () => {
- expect(wrapper.emitted('toggleSidebar')).toBe(undefined);
-
- findToggleBtn().vm.$emit('click');
-
- expect(wrapper.emitted('toggleSidebar')).toHaveLength(1);
- });
- });
-
- describe('retry action', () => {
- describe('when feature flag is ON', () => {
- beforeEach(() => {
- createComponent({ featureFlag: true });
- });
-
- it('renders retry dropdown', () => {
- expect(findRetryDropdown().exists()).toBe(true);
- });
- });
-
- describe('when feature flag is OFF', () => {
- it('does not render retry dropdown', () => {
- createComponent({ featureFlag: false });
-
- expect(findRetryDropdown().exists()).toBe(false);
- });
- });
- });
-});
diff --git a/spec/frontend/jobs/bridge/mock_data.js b/spec/frontend/jobs/bridge/mock_data.js
deleted file mode 100644
index 4084bb54163..00000000000
--- a/spec/frontend/jobs/bridge/mock_data.js
+++ /dev/null
@@ -1,102 +0,0 @@
-export const MOCK_EMPTY_ILLUSTRATION_PATH = '/path/to/svg';
-export const MOCK_PATH_TO_DOWNSTREAM = '/path/to/downstream/pipeline';
-export const MOCK_BUILD_ID = '1331';
-export const MOCK_PIPELINE_IID = '174';
-export const MOCK_PROJECT_FULL_PATH = '/root/project/';
-export const MOCK_SHA = '38f3d89147765427a7ce58be28cd76d14efa682a';
-
-export const mockCommit = {
- id: `gid://gitlab/CommitPresenter/${MOCK_SHA}`,
- shortId: '38f3d891',
- title: 'Update .gitlab-ci.yml file',
- webPath: `/root/project/-/commit/${MOCK_SHA}`,
- __typename: 'Commit',
-};
-
-export const mockJob = {
- createdAt: '2021-12-10T09:05:45Z',
- id: 'gid://gitlab/Ci::Build/1331',
- name: 'triggerJobName',
- scheduledAt: null,
- startedAt: '2021-12-10T09:13:43Z',
- status: 'SUCCESS',
- triggered: null,
- detailedStatus: {
- id: '1',
- detailsPath: '/root/project/-/jobs/1331',
- icon: 'status_success',
- group: 'success',
- text: 'passed',
- tooltip: 'passed',
- __typename: 'DetailedStatus',
- },
- downstreamPipeline: {
- id: '1',
- path: '/root/project/-/pipelines/175',
- },
- stage: {
- id: '1',
- name: 'build',
- __typename: 'CiStage',
- },
- __typename: 'CiJob',
-};
-
-export const mockUser = {
- id: 'gid://gitlab/User/1',
- avatarUrl: 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
- name: 'Administrator',
- username: 'root',
- webPath: '/root',
- webUrl: 'http://gdk.test:3000/root',
- status: {
- message: 'making great things',
- __typename: 'UserStatus',
- },
- __typename: 'UserCore',
-};
-
-export const mockStage = {
- id: '1',
- name: 'build',
- jobs: {
- nodes: [mockJob],
- __typename: 'CiJobConnection',
- },
- __typename: 'CiStage',
-};
-
-export const mockPipelineQueryResponse = {
- data: {
- project: {
- id: '1',
- pipeline: {
- commit: mockCommit,
- id: 'gid://gitlab/Ci::Pipeline/174',
- iid: '88',
- path: '/root/project/-/pipelines/174',
- sha: MOCK_SHA,
- ref: 'main',
- refPath: 'path/to/ref',
- user: mockUser,
- detailedStatus: {
- id: '1',
- icon: 'status_failed',
- group: 'failed',
- __typename: 'DetailedStatus',
- },
- stages: {
- edges: [
- {
- node: mockStage,
- __typename: 'CiStageEdge',
- },
- ],
- __typename: 'CiStageConnection',
- },
- __typename: 'Pipeline',
- },
- __typename: 'Project',
- },
- },
-};
diff --git a/spec/frontend/projects/pipelines/charts/components/app_spec.js b/spec/frontend/projects/pipelines/charts/components/app_spec.js
index 98c7856a61a..df8455531b3 100644
--- a/spec/frontend/projects/pipelines/charts/components/app_spec.js
+++ b/spec/frontend/projects/pipelines/charts/components/app_spec.js
@@ -14,6 +14,7 @@ jest.mock('~/lib/utils/url_utility');
const DeploymentFrequencyChartsStub = { name: 'DeploymentFrequencyCharts', render: () => {} };
const LeadTimeChartsStub = { name: 'LeadTimeCharts', render: () => {} };
const TimeToRestoreServiceChartsStub = { name: 'TimeToRestoreServiceCharts', render: () => {} };
+const ChangeFailureRateChartsStub = { name: 'ChangeFailureRateCharts', render: () => {} };
const ProjectQualitySummaryStub = { name: 'ProjectQualitySummary', render: () => {} };
describe('ProjectsPipelinesChartsApp', () => {
@@ -33,6 +34,7 @@ describe('ProjectsPipelinesChartsApp', () => {
DeploymentFrequencyCharts: DeploymentFrequencyChartsStub,
LeadTimeCharts: LeadTimeChartsStub,
TimeToRestoreServiceCharts: TimeToRestoreServiceChartsStub,
+ ChangeFailureRateCharts: ChangeFailureRateChartsStub,
ProjectQualitySummary: ProjectQualitySummaryStub,
},
},
@@ -50,6 +52,7 @@ describe('ProjectsPipelinesChartsApp', () => {
const findGlTabAtIndex = (index) => findAllGlTabs().at(index);
const findLeadTimeCharts = () => wrapper.find(LeadTimeChartsStub);
const findTimeToRestoreServiceCharts = () => wrapper.find(TimeToRestoreServiceChartsStub);
+ const findChangeFailureRateCharts = () => wrapper.find(ChangeFailureRateChartsStub);
const findDeploymentFrequencyCharts = () => wrapper.find(DeploymentFrequencyChartsStub);
const findPipelineCharts = () => wrapper.find(PipelineCharts);
const findProjectQualitySummary = () => wrapper.find(ProjectQualitySummaryStub);
@@ -59,58 +62,49 @@ describe('ProjectsPipelinesChartsApp', () => {
createComponent();
});
- it('renders tabs', () => {
- expect(findGlTabs().exists()).toBe(true);
+ describe.each`
+ title | finderFn | index
+ ${'Pipelines'} | ${findPipelineCharts} | ${0}
+ ${'Deployment frequency'} | ${findDeploymentFrequencyCharts} | ${1}
+ ${'Lead time'} | ${findLeadTimeCharts} | ${2}
+ ${'Time to restore service'} | ${findTimeToRestoreServiceCharts} | ${3}
+ ${'Change failure rate'} | ${findChangeFailureRateCharts} | ${4}
+ ${'Project quality'} | ${findProjectQualitySummary} | ${5}
+ `('Tabs', ({ title, finderFn, index }) => {
+ it(`renders tab with a title ${title} at index ${index}`, () => {
+ expect(findGlTabAtIndex(index).attributes('title')).toBe(title);
+ });
- expect(findGlTabAtIndex(0).attributes('title')).toBe('Pipelines');
- expect(findGlTabAtIndex(1).attributes('title')).toBe('Deployment frequency');
- expect(findGlTabAtIndex(2).attributes('title')).toBe('Lead time');
- expect(findGlTabAtIndex(3).attributes('title')).toBe('Time to restore service');
- });
+ it(`renders the ${title} chart`, () => {
+ expect(finderFn().exists()).toBe(true);
+ });
- it('renders the pipeline charts', () => {
- expect(findPipelineCharts().exists()).toBe(true);
- });
+ it(`updates the current tab and url when the ${title} tab is clicked`, async () => {
+ let chartsPath;
+ const tabName = title.toLowerCase().replace(/\s/g, '-');
- it('renders the deployment frequency charts', () => {
- expect(findDeploymentFrequencyCharts().exists()).toBe(true);
- });
+ setWindowLocation(`${TEST_HOST}/gitlab-org/gitlab-test/-/pipelines/charts`);
- it('renders the lead time charts', () => {
- expect(findLeadTimeCharts().exists()).toBe(true);
- });
+ mergeUrlParams.mockImplementation(({ chart }, path) => {
+ expect(chart).toBe(tabName);
+ expect(path).toBe(window.location.pathname);
+ chartsPath = `${path}?chart=${chart}`;
+ return chartsPath;
+ });
- it('renders the time to restore service charts', () => {
- expect(findTimeToRestoreServiceCharts().exists()).toBe(true);
- });
+ updateHistory.mockImplementation(({ url }) => {
+ expect(url).toBe(chartsPath);
+ });
+ const tabs = findGlTabs();
- it('renders the project quality summary', () => {
- expect(findProjectQualitySummary().exists()).toBe(true);
- });
+ expect(tabs.attributes('value')).toBe('0');
- it('sets the tab and url when a tab is clicked', async () => {
- let chartsPath;
- setWindowLocation(`${TEST_HOST}/gitlab-org/gitlab-test/-/pipelines/charts`);
+ tabs.vm.$emit('input', index);
- mergeUrlParams.mockImplementation(({ chart }, path) => {
- expect(chart).toBe('deployment-frequency');
- expect(path).toBe(window.location.pathname);
- chartsPath = `${path}?chart=${chart}`;
- return chartsPath;
- });
+ await nextTick();
- updateHistory.mockImplementation(({ url }) => {
- expect(url).toBe(chartsPath);
+ expect(tabs.attributes('value')).toBe(index.toString());
});
- const tabs = findGlTabs();
-
- expect(tabs.attributes('value')).toBe('0');
-
- tabs.vm.$emit('input', 1);
-
- await nextTick();
-
- expect(tabs.attributes('value')).toBe('1');
});
it('should not try to push history if the tab does not change', async () => {
@@ -151,6 +145,7 @@ describe('ProjectsPipelinesChartsApp', () => {
describe('when provided with a query param', () => {
it.each`
chart | tab
+ ${'change-failure-rate'} | ${'4'}
${'time-to-restore-service'} | ${'3'}
${'lead-time'} | ${'2'}
${'deployment-frequency'} | ${'1'}
diff --git a/spec/helpers/emails_helper_spec.rb b/spec/helpers/emails_helper_spec.rb
index 220e154aad8..04653d9ff03 100644
--- a/spec/helpers/emails_helper_spec.rb
+++ b/spec/helpers/emails_helper_spec.rb
@@ -77,7 +77,7 @@ RSpec.describe EmailsHelper do
end
describe 'notification_reason_text' do
- subject { helper.notification_reason_text(reason_code) }
+ subject { helper.notification_reason_text(reason: reason_code) }
using RSpec::Parameterized::TableSyntax
diff --git a/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb b/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb
index 771fc0218e2..25b84a67ab2 100644
--- a/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb
+++ b/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb
@@ -7,13 +7,12 @@ RSpec.describe Gitlab::HookData::MergeRequestBuilder do
let(:builder) { described_class.new(merge_request) }
- describe '#build' do
- let(:data) { builder.build }
+ describe '.safe_hook_attributes' do
+ let(:safe_attribute_keys) { described_class.safe_hook_attributes }
it 'includes safe attribute' do
- %w[
+ expected_safe_attribute_keys = %i[
assignee_id
- assignee_ids
author_id
blocking_discussions_resolved
created_at
@@ -32,17 +31,21 @@ RSpec.describe Gitlab::HookData::MergeRequestBuilder do
milestone_id
source_branch
source_project_id
- state
+ state_id
target_branch
target_project_id
time_estimate
title
updated_at
updated_by_id
- ].each do |key|
- expect(data).to include(key)
- end
+ ].freeze
+
+ expect(safe_attribute_keys).to match_array(expected_safe_attribute_keys)
end
+ end
+
+ describe '#build' do
+ let(:data) { builder.build }
%i[source target].each do |key|
describe "#{key} key" do
@@ -52,17 +55,30 @@ RSpec.describe Gitlab::HookData::MergeRequestBuilder do
end
end
+ it 'includes safe attributes' do
+ expect(data).to include(*described_class.safe_hook_attributes)
+ end
+
it 'includes additional attrs' do
- expect(data).to include(:source)
- expect(data).to include(:target)
- expect(data).to include(:last_commit)
- expect(data).to include(:work_in_progress)
- expect(data).to include(:total_time_spent)
- expect(data).to include(:time_change)
- expect(data).to include(:human_time_estimate)
- expect(data).to include(:human_total_time_spent)
- expect(data).to include(:human_time_change)
- expect(data).to include(:labels)
+ expected_additional_attributes = %w[
+ description
+ url
+ last_commit
+ work_in_progress
+ total_time_spent
+ time_change
+ human_total_time_spent
+ human_time_change
+ human_time_estimate
+ assignee_ids
+ assignee_id
+ labels
+ state
+ blocking_discussions_resolved
+ first_contribution
+ ].freeze
+
+ expect(data).to include(*expected_additional_attributes)
end
context 'when the MR has an image in the description' do
diff --git a/spec/lib/gitlab/metrics/memory_spec.rb b/spec/lib/gitlab/metrics/memory_spec.rb
new file mode 100644
index 00000000000..fd8ca3b37c6
--- /dev/null
+++ b/spec/lib/gitlab/metrics/memory_spec.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+RSpec.describe Gitlab::Metrics::Memory do
+ describe '.gc_heap_fragmentation' do
+ subject(:call) do
+ described_class.gc_heap_fragmentation(
+ heap_live_slots: gc_stat_heap_live_slots,
+ heap_eden_pages: gc_stat_heap_eden_pages
+ )
+ end
+
+ context 'when the Ruby heap is perfectly utilized' do
+ # All objects are located in a single heap page.
+ let(:gc_stat_heap_live_slots) { described_class::HEAP_SLOTS_PER_PAGE }
+ let(:gc_stat_heap_eden_pages) { 1 }
+
+ it { is_expected.to eq(0) }
+ end
+
+ context 'when the Ruby heap is greatly fragmented' do
+ # There is one object per heap page.
+ let(:gc_stat_heap_live_slots) { described_class::HEAP_SLOTS_PER_PAGE }
+ let(:gc_stat_heap_eden_pages) { described_class::HEAP_SLOTS_PER_PAGE }
+
+ # The heap can never be "perfectly fragmented" because that would require
+ # zero objects per page.
+ it { is_expected.to be > 0.99 }
+ end
+
+ context 'when the Ruby heap is semi-fragmented' do
+ # All objects are spread over two pages i.e. each page is 50% utilized.
+ let(:gc_stat_heap_live_slots) { described_class::HEAP_SLOTS_PER_PAGE }
+ let(:gc_stat_heap_eden_pages) { 2 }
+
+ it { is_expected.to eq(0.5) }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb b/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb
index dfae5aa6784..b1566ffa7b4 100644
--- a/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb
+++ b/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb
@@ -125,5 +125,11 @@ RSpec.describe Gitlab::Metrics::Samplers::RubySampler do
sampler.sample
end
+
+ it 'adds a heap fragmentation metric' do
+ expect(sampler.metrics[:heap_fragmentation]).to receive(:set).with({}, anything)
+
+ sampler.sample
+ end
end
end
diff --git a/spec/lib/unnested_in_filters/rewriter_spec.rb b/spec/lib/unnested_in_filters/rewriter_spec.rb
index f4fff393f28..e2ccbd92504 100644
--- a/spec/lib/unnested_in_filters/rewriter_spec.rb
+++ b/spec/lib/unnested_in_filters/rewriter_spec.rb
@@ -138,5 +138,20 @@ RSpec.describe UnnestedInFilters::Rewriter do
end
end
end
+
+ describe 'logging' do
+ subject(:load_reload) { rewriter.rewrite }
+
+ before do
+ allow(::Gitlab::AppLogger).to receive(:info)
+ end
+
+ it 'logs the call' do
+ load_reload
+
+ expect(::Gitlab::AppLogger)
+ .to have_received(:info).with(message: 'Query is being rewritten by `UnnestedInFilters`', model: 'User')
+ end
+ end
end
end
diff --git a/spec/mailers/emails/admin_notification_spec.rb b/spec/mailers/emails/admin_notification_spec.rb
index bfd07f1d438..1b770d6d4a2 100644
--- a/spec/mailers/emails/admin_notification_spec.rb
+++ b/spec/mailers/emails/admin_notification_spec.rb
@@ -64,7 +64,7 @@ RSpec.describe Emails::AdminNotification do
end
it 'includes the email reason' do
- is_expected.to have_body_text "You're receiving this email because of your account on localhost"
+ is_expected.to have_body_text %r{You're receiving this email because of your account on <a .*>localhost<\/a>}
end
context 'when scoped to a group' do
diff --git a/spec/mailers/emails/profile_spec.rb b/spec/mailers/emails/profile_spec.rb
index f4483f7e8f5..09ed27eb90f 100644
--- a/spec/mailers/emails/profile_spec.rb
+++ b/spec/mailers/emails/profile_spec.rb
@@ -151,7 +151,7 @@ RSpec.describe Emails::Profile do
end
it 'includes the email reason' do
- is_expected.to have_body_text /You're receiving this email because of your account on localhost/
+ is_expected.to have_body_text %r{You're receiving this email because of your account on <a .*>localhost<\/a>}
end
end
end
@@ -187,7 +187,7 @@ RSpec.describe Emails::Profile do
end
it 'includes the email reason' do
- is_expected.to have_body_text /You're receiving this email because of your account on localhost/
+ is_expected.to have_body_text %r{You're receiving this email because of your account on <a .*>localhost<\/a>}
end
context 'with User does not exist' do
@@ -222,7 +222,7 @@ RSpec.describe Emails::Profile do
end
it 'includes the email reason' do
- is_expected.to have_body_text /You're receiving this email because of your account on localhost/
+ is_expected.to have_body_text %r{You're receiving this email because of your account on <a .*>localhost<\/a>}
end
end
@@ -266,7 +266,7 @@ RSpec.describe Emails::Profile do
end
shared_examples 'includes the email reason' do
- it { is_expected.to have_body_text /You're receiving this email because of your account on localhost/ }
+ it { is_expected.to have_body_text %r{You're receiving this email because of your account on <a .*>localhost<\/a>} }
end
shared_examples 'valid use case' do
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index f501488bb8c..8beb54bca4d 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -7,6 +7,7 @@ RSpec.describe Notify do
include EmailSpec::Helpers
include EmailSpec::Matchers
include EmailHelpers
+ include EmailsHelper
include RepoHelpers
include MembersHelper
@@ -396,7 +397,7 @@ RSpec.describe Notify do
end
end
- context 'when sent with a reason' do
+ context 'when sent with a reason', type: :helper do
subject { described_class.reassigned_merge_request_email(recipient.id, merge_request.id, [previous_assignee.id], current_user.id, NotificationReason::ASSIGNED) }
it_behaves_like 'appearance header and footer enabled'
@@ -407,15 +408,15 @@ RSpec.describe Notify do
end
it 'includes the reason in the footer' do
- text = EmailsHelper.instance_method(:notification_reason_text).bind(self).call(NotificationReason::ASSIGNED)
+ text = EmailsHelper.instance_method(:notification_reason_text).bind(self).call(reason: NotificationReason::ASSIGNED, format: :html)
is_expected.to have_body_text(text)
new_subject = described_class.reassigned_merge_request_email(recipient.id, merge_request.id, [previous_assignee.id], current_user.id, NotificationReason::MENTIONED)
- text = EmailsHelper.instance_method(:notification_reason_text).bind(self).call(NotificationReason::MENTIONED)
+ text = EmailsHelper.instance_method(:notification_reason_text).bind(self).call(reason: NotificationReason::MENTIONED, format: :html)
expect(new_subject).to have_body_text(text)
new_subject = described_class.reassigned_merge_request_email(recipient.id, merge_request.id, [previous_assignee.id], current_user.id, nil)
- text = EmailsHelper.instance_method(:notification_reason_text).bind(self).call(nil)
+ text = EmailsHelper.instance_method(:notification_reason_text).bind(self).call(format: :html)
expect(new_subject).to have_body_text(text)
end
end
diff --git a/spec/models/merge_request_diff_file_spec.rb b/spec/models/merge_request_diff_file_spec.rb
index 64203c50759..7dc550a6c93 100644
--- a/spec/models/merge_request_diff_file_spec.rb
+++ b/spec/models/merge_request_diff_file_spec.rb
@@ -97,10 +97,8 @@ RSpec.describe MergeRequestDiffFile do
file.utf8_diff
end
- end
- describe '#diff_export' do
- context 'when diff is externally stored' do
+ context 'externally stored diff caching' do
let(:test_dir) { 'tmp/tests/external-diffs' }
around do |example|
@@ -121,7 +119,7 @@ RSpec.describe MergeRequestDiffFile do
it 'caches external diffs' do
expect(file.merge_request_diff).to receive(:cache_external_diff).and_call_original
- expect(file.diff_export).to eq(file.utf8_diff)
+ expect(file.utf8_diff).to eq(file.diff)
end
end
@@ -133,7 +131,7 @@ RSpec.describe MergeRequestDiffFile do
expect(file_stub).to receive(:seek).with(file.external_diff_offset)
expect(file_stub).to receive(:read).with(file.external_diff_size)
- file.diff_export
+ file.utf8_diff
end
end
@@ -151,28 +149,28 @@ RSpec.describe MergeRequestDiffFile do
end
it 'unpacks from base 64' do
- expect(file.diff_export).to eq(unpacked)
+ expect(file.utf8_diff).to eq(unpacked)
end
context 'invalid base64' do
let(:packed) { '---/dev/null' }
it 'returns the raw diff' do
- expect(file.diff_export).to eq(packed)
+ expect(file.utf8_diff).to eq(packed)
end
end
end
context 'when the diff is not marked as binary' do
it 'returns the raw diff' do
- expect(file.diff_export).to eq(packed)
+ expect(file.utf8_diff).to eq(packed)
end
end
end
context 'when content responds to #encoding' do
it 'encodes content to utf8 encoding' do
- expect(file.diff_export.encoding).to eq(Encoding::UTF_8)
+ expect(file.utf8_diff.encoding).to eq(Encoding::UTF_8)
end
end
@@ -180,35 +178,46 @@ RSpec.describe MergeRequestDiffFile do
it 'returns an empty string' do
allow(file.merge_request_diff).to receive(:cached_external_diff).and_return(nil)
- expect(file.diff_export).to eq('')
+ expect(file.utf8_diff).to eq('')
end
end
context 'when exception is raised' do
- it 'falls back to #utf8_diff' do
- allow(file).to receive(:binary?).and_raise(StandardError)
- expect(file).to receive(:utf8_diff)
-
- file.diff_export
+ it 'falls back to #diff' do
+ allow(file).to receive(:binary?).and_raise(StandardError, 'Error!')
+ expect(file).to receive(:diff)
+ expect(Gitlab::AppLogger)
+ .to receive(:warn)
+ .with(
+ a_hash_including(
+ :message => 'Cached external diff export failed',
+ :merge_request_diff_file_id => file.id,
+ :merge_request_diff_id => file.merge_request_diff.id,
+ 'exception.class' => 'StandardError',
+ 'exception.message' => 'Error!'
+ )
+ )
+
+ file.utf8_diff
end
end
end
context 'when externally_stored_diffs_caching_export feature flag is disabled' do
- it 'calls #utf8_diff' do
+ it 'calls #diff' do
stub_feature_flags(externally_stored_diffs_caching_export: false)
- expect(file).to receive(:utf8_diff)
+ expect(file).to receive(:diff)
- file.diff_export
+ file.utf8_diff
end
end
context 'when diff is not stored externally' do
- it 'calls #utf8_diff' do
- expect(file).to receive(:utf8_diff)
+ it 'calls #diff' do
+ expect(file).to receive(:diff)
- file.diff_export
+ file.utf8_diff
end
end
end
diff --git a/spec/requests/api/terraform/modules/v1/packages_spec.rb b/spec/requests/api/terraform/modules/v1/packages_spec.rb
index cb305227b01..d802282f8dc 100644
--- a/spec/requests/api/terraform/modules/v1/packages_spec.rb
+++ b/spec/requests/api/terraform/modules/v1/packages_spec.rb
@@ -171,6 +171,75 @@ RSpec.describe API::Terraform::Modules::V1::Packages do
end
end
+ describe 'GET /api/v4/packages/terraform/modules/v1/:module_namespace/:module_name/:module_system/:module_version' do
+ let(:url) { api("/packages/terraform/modules/v1/#{group.path}/#{package.name}/#{package.version}") }
+ let(:headers) { {} }
+
+ subject { get(url, headers: headers) }
+
+ context 'not found' do
+ let(:url) { api("/packages/terraform/modules/v1/#{group.path}/#{package.name}/2.0.0") }
+ let(:headers) { { 'Authorization' => "Bearer #{tokens[:job_token]}" } }
+
+ subject { get(url, headers: headers) }
+
+ it 'returns not found when the specified version is not present in the registry' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'with valid namespace' do
+ where(:visibility, :user_role, :member, :token_type, :shared_examples_name, :expected_status) do
+ :public | :developer | true | :personal_access_token | 'returns terraform module version' | :success
+ :public | :guest | true | :personal_access_token | 'returns terraform module version' | :success
+ :public | :developer | true | :invalid | 'rejects terraform module packages access' | :unauthorized
+ :public | :guest | true | :invalid | 'rejects terraform module packages access' | :unauthorized
+ :public | :developer | false | :personal_access_token | 'returns terraform module version' | :success
+ :public | :guest | false | :personal_access_token | 'returns terraform module version' | :success
+ :public | :developer | false | :invalid | 'rejects terraform module packages access' | :unauthorized
+ :public | :guest | false | :invalid | 'rejects terraform module packages access' | :unauthorized
+ :public | :anonymous | false | nil | 'returns terraform module version' | :success
+ :private | :developer | true | :personal_access_token | 'returns terraform module version' | :success
+ :private | :guest | true | :personal_access_token | 'rejects terraform module packages access' | :forbidden
+ :private | :developer | true | :invalid | 'rejects terraform module packages access' | :unauthorized
+ :private | :guest | true | :invalid | 'rejects terraform module packages access' | :unauthorized
+ :private | :developer | false | :personal_access_token | 'rejects terraform module packages access' | :forbidden
+ :private | :guest | false | :personal_access_token | 'rejects terraform module packages access' | :forbidden
+ :private | :developer | false | :invalid | 'rejects terraform module packages access' | :unauthorized
+ :private | :guest | false | :invalid | 'rejects terraform module packages access' | :unauthorized
+ :private | :anonymous | false | nil | 'rejects terraform module packages access' | :unauthorized
+ :public | :developer | true | :job_token | 'returns terraform module version' | :success
+ :public | :guest | true | :job_token | 'returns terraform module version' | :success
+ :public | :guest | true | :invalid | 'rejects terraform module packages access' | :unauthorized
+ :public | :developer | false | :job_token | 'returns terraform module version' | :success
+ :public | :guest | false | :job_token | 'returns terraform module version' | :success
+ :public | :developer | false | :invalid | 'rejects terraform module packages access' | :unauthorized
+ :public | :guest | false | :invalid | 'rejects terraform module packages access' | :unauthorized
+ :private | :developer | true | :job_token | 'returns terraform module version' | :success
+ :private | :guest | true | :job_token | 'rejects terraform module packages access' | :forbidden
+ :private | :developer | true | :invalid | 'rejects terraform module packages access' | :unauthorized
+ :private | :guest | true | :invalid | 'rejects terraform module packages access' | :unauthorized
+ :private | :developer | false | :job_token | 'rejects terraform module packages access' | :forbidden
+ :private | :guest | false | :job_token | 'rejects terraform module packages access' | :forbidden
+ :private | :developer | false | :invalid | 'rejects terraform module packages access' | :unauthorized
+ :private | :guest | false | :invalid | 'rejects terraform module packages access' | :unauthorized
+ end
+
+ with_them do
+ let(:headers) { user_role == :anonymous ? {} : { 'Authorization' => "Bearer #{token}" } }
+
+ before do
+ group.update!(visibility: visibility.to_s)
+ project.update!(visibility: visibility.to_s)
+ end
+
+ it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
+ end
+ end
+ end
+
describe 'GET /api/v4/packages/terraform/modules/v1/:module_namespace/:module_name/:module_system/:module_version/download' do
let(:url) { api("/packages/terraform/modules/v1/#{group.path}/#{package.name}/#{package.version}/download") }
let(:headers) { {} }
diff --git a/spec/support/shared_examples/finders/issues_finder_shared_examples.rb b/spec/support/shared_examples/finders/issues_finder_shared_examples.rb
index f3c616c7d9b..9d8f37a3e64 100644
--- a/spec/support/shared_examples/finders/issues_finder_shared_examples.rb
+++ b/spec/support/shared_examples/finders/issues_finder_shared_examples.rb
@@ -269,6 +269,17 @@ RSpec.shared_examples 'issues or work items finder' do |factory, execute_context
it 'returns items not assigned to that milestone' do
expect(items).to contain_exactly(item2, item3, item4, item5)
end
+
+ context 'with multiple milestones' do
+ let(:milestone2) { create(:milestone, project: project2) }
+ let(:params) { { not: { milestone_title: [milestone.title, milestone2.title] } } }
+
+ it 'returns items not assigned to both milestones' do
+ item2.update!(milestone: milestone2)
+
+ expect(items).to contain_exactly(item3, item4, item5)
+ end
+ end
end
context 'filtering by group milestone' do
diff --git a/spec/support/shared_examples/requests/api/terraform/modules/v1/packages_shared_examples.rb b/spec/support/shared_examples/requests/api/terraform/modules/v1/packages_shared_examples.rb
index ab994e2c8c7..544a0ed8fdd 100644
--- a/spec/support/shared_examples/requests/api/terraform/modules/v1/packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/terraform/modules/v1/packages_shared_examples.rb
@@ -102,6 +102,22 @@ RSpec.shared_examples 'returns terraform module packages' do |user_type, status,
end
end
+RSpec.shared_examples 'returns terraform module version' do |user_type, status, add_member = true|
+ context "for user type #{user_type}" do
+ before do
+ group.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it_behaves_like 'returning response status', status
+
+ it 'returning a valid response' do
+ subject
+
+ expect(json_response).to match_schema('public_api/v4/packages/terraform/modules/v1/single_version')
+ end
+ end
+end
+
RSpec.shared_examples 'returns no terraform module packages' do |user_type, status, add_member = true|
context "for user type #{user_type}" do
before do
diff --git a/spec/views/projects/commits/_commit.html.haml_spec.rb b/spec/views/projects/commits/_commit.html.haml_spec.rb
index da93871e0e4..2ca23d4cb2d 100644
--- a/spec/views/projects/commits/_commit.html.haml_spec.rb
+++ b/spec/views/projects/commits/_commit.html.haml_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe 'projects/commits/_commit.html.haml' do
- let(:template) { 'projects/commits/commit.html.haml' }
+ let(:template) { 'projects/commits/commit' }
let(:project) { create(:project, :repository) }
let(:commit) { project.repository.commit(ref) }
diff --git a/spec/views/projects/jobs/show.html.haml_spec.rb b/spec/views/projects/jobs/show.html.haml_spec.rb
index 8242d20a9e7..2ea3dc9f76a 100644
--- a/spec/views/projects/jobs/show.html.haml_spec.rb
+++ b/spec/views/projects/jobs/show.html.haml_spec.rb
@@ -27,7 +27,6 @@ RSpec.describe 'projects/jobs/show' do
it 'shows job vue app' do
expect(rendered).to have_css('#js-job-page')
- expect(rendered).not_to have_css('#js-bridge-page')
end
context 'when job is running' do
@@ -42,18 +41,4 @@ RSpec.describe 'projects/jobs/show' do
end
end
end
-
- context 'when showing a bridge job' do
- let(:bridge) { create(:ci_bridge, status: :pending) }
-
- before do
- assign(:build, bridge)
- render
- end
-
- it 'shows bridge vue app' do
- expect(rendered).to have_css('#js-bridge-page')
- expect(rendered).not_to have_css('#js-job-page')
- end
- end
end
diff --git a/workhorse/go.mod b/workhorse/go.mod
index 6832647e57f..4afb47fb82a 100644
--- a/workhorse/go.mod
+++ b/workhorse/go.mod
@@ -25,7 +25,7 @@ require (
github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a
github.com/sirupsen/logrus v1.8.1
github.com/smartystreets/goconvey v1.7.2
- github.com/stretchr/testify v1.7.0
+ github.com/stretchr/testify v1.8.0
gitlab.com/gitlab-org/gitaly/v15 v15.1.0
gitlab.com/gitlab-org/golang-archive-zip v0.1.1
gitlab.com/gitlab-org/labkit v1.15.0
@@ -113,5 +113,5 @@ require (
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20220401170504-314d38edb7de // indirect
gopkg.in/DataDog/dd-trace-go.v1 v1.32.0 // indirect
- gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
+ gopkg.in/yaml.v3 v3.0.1 // indirect
)
diff --git a/workhorse/go.sum b/workhorse/go.sum
index 1a4f0fd500e..063d287b61c 100644
--- a/workhorse/go.sum
+++ b/workhorse/go.sum
@@ -1044,16 +1044,19 @@ github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3
github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
+github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
github.com/tinylib/msgp v1.1.2 h1:gWmO7n0Ys2RBEb7GPYB9Ujq8Mk5p2U08lRnmMcGy6BQ=
@@ -1815,8 +1818,9 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=