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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab/ci/rails/shared.gitlab-ci.yml4
-rw-r--r--.gitlab/ci/review.gitlab-ci.yml15
-rw-r--r--.gitlab/ci/rules.gitlab-ci.yml9
-rw-r--r--.rubocop_todo/style/empty_method.yml26
-rw-r--r--app/assets/javascripts/issues/show/components/incidents/constants.js1
-rw-r--r--app/assets/javascripts/issues/show/components/incidents/edit_timeline_event.vue2
-rw-r--r--app/assets/javascripts/issues/show/components/incidents/timeline_events_form.vue67
-rw-r--r--app/assets/javascripts/issues/show/components/incidents/timeline_events_list.vue1
-rw-r--r--app/assets/stylesheets/_page_specific_files.scss1
-rw-r--r--app/assets/stylesheets/framework/sidebar.scss641
-rw-r--r--app/assets/stylesheets/page_bundles/issuable.scss183
-rw-r--r--app/assets/stylesheets/page_bundles/issuable_list.scss96
-rw-r--r--app/assets/stylesheets/pages/issuable.scss912
-rw-r--r--app/models/ci/build.rb2
-rw-r--r--app/models/ci/job_variable.rb3
-rw-r--r--app/models/concerns/ci/partitionable.rb1
-rw-r--r--app/models/packages/rpm/repository_file.rb10
-rw-r--r--app/views/admin/application_settings/_error_tracking.html.haml2
-rw-r--r--app/views/admin/topics/_form.html.haml11
-rw-r--r--app/views/devise/unlocks/new.html.haml4
-rw-r--r--app/views/groups/_import_group_from_another_instance_panel.html.haml2
-rw-r--r--app/views/groups/issues.html.haml1
-rw-r--r--app/views/projects/issuable/_show.html.haml1
-rw-r--r--app/views/projects/issues/index.html.haml1
-rw-r--r--app/views/projects/merge_requests/index.html.haml1
-rw-r--r--app/views/projects/merge_requests/show.html.haml1
-rw-r--r--config/application.rb2
-rw-r--r--doc/architecture/blueprints/ci_pipeline_components/index.md174
-rw-r--r--doc/ci/variables/predefined_variables.md2
-rw-r--r--doc/development/documentation/styleguide/index.md15
-rw-r--r--doc/development/pipelines/index.md4
-rw-r--r--doc/operations/incident_management/incident_timeline_events.md8
-rw-r--r--lib/api/rpm_project_packages.rb4
-rw-r--r--locale/gitlab.pot9
-rwxr-xr-xscripts/review_apps/automated_cleanup.rb21
-rwxr-xr-xscripts/review_apps/gcp_cleanup.sh160
-rw-r--r--scripts/rspec_helpers.sh28
-rw-r--r--scripts/utils.sh5
-rw-r--r--spec/controllers/concerns/check_rate_limit_spec.rb6
-rw-r--r--spec/controllers/concerns/issuable_actions_spec.rb6
-rw-r--r--spec/factories/packages/rpm/rpm_repository_files.rb4
-rw-r--r--spec/frontend/issues/show/components/incidents/edit_timeline_event_spec.js8
-rw-r--r--spec/frontend/issues/show/components/incidents/timeline_events_form_spec.js41
-rw-r--r--spec/initializers/forbid_sidekiq_in_transactions_spec.rb6
-rw-r--r--spec/lib/api/helpers/rate_limiter_spec.rb3
-rw-r--r--spec/lib/gitlab/database/load_balancing/sidekiq_client_middleware_spec.rb3
-rw-r--r--spec/lib/gitlab/database/load_balancing/sidekiq_server_middleware_spec.rb3
-rw-r--r--spec/lib/gitlab/database/migration_helpers/restrict_gitlab_schema_spec.rb15
-rw-r--r--spec/lib/gitlab/database/partitioning/sliding_list_strategy_spec.rb6
-rw-r--r--spec/lib/gitlab/database/postgresql_adapter/dump_schema_versions_mixin_spec.rb3
-rw-r--r--spec/lib/gitlab/database/postgresql_database_tasks/load_schema_versions_mixin_spec.rb3
-rw-r--r--spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb3
-rw-r--r--spec/lib/gitlab/repository_archive_rate_limiter_spec.rb3
-rw-r--r--spec/lib/gitlab/repository_cache_adapter_spec.rb3
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/client_metrics_spec.rb3
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/client_spec.rb3
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/server_spec.rb3
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/instrumentation_logger_spec.rb3
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/query_analyzer_spec.rb3
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb6
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/worker_context/client_spec.rb6
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/worker_context/server_spec.rb3
-rw-r--r--spec/lib/gitlab/ssh_public_key_spec.rb3
-rw-r--r--spec/lib/gitlab/utils/delegator_override/validator_spec.rb12
-rw-r--r--spec/lib/gitlab/utils/delegator_override_spec.rb12
-rw-r--r--spec/lib/gitlab/utils/override_spec.rb3
-rw-r--r--spec/lib/gitlab/utils/strong_memoize_spec.rb9
-rw-r--r--spec/models/ci/build_spec.rb24
-rw-r--r--spec/models/ci/job_variable_spec.rb62
-rw-r--r--spec/models/packages/rpm/repository_file_spec.rb28
-rw-r--r--spec/requests/api/rpm_project_packages_spec.rb13
71 files changed, 1487 insertions, 1255 deletions
diff --git a/.gitlab/ci/rails/shared.gitlab-ci.yml b/.gitlab/ci/rails/shared.gitlab-ci.yml
index ba4bde9d07b..e282781b7b5 100644
--- a/.gitlab/ci/rails/shared.gitlab-ci.yml
+++ b/.gitlab/ci/rails/shared.gitlab-ci.yml
@@ -54,10 +54,14 @@ include:
RECORD_DEPRECATIONS: "true"
GEO_SECONDARY_PROXY: 0
RSPEC_TESTS_FILTER_FILE: "${RSPEC_MATCHING_TESTS_PATH}"
+ SUCCESSFULLY_RETRIED_TEST_EXIT_CODE: 137
needs: ["setup-test-env", "retrieve-tests-metadata", "compile-test-assets", "detect-tests"]
script:
- !reference [.base-script, script]
- rspec_paralellized_job "--tag ~quarantine --tag ~level:migration"
+ allow_failure:
+ # the exit code listed here must match the one defined for the variable SUCCESSFULLY_RETRIED_TEST_EXIT_CODE
+ exit_codes: 137
.base-artifacts:
artifacts:
diff --git a/.gitlab/ci/review.gitlab-ci.yml b/.gitlab/ci/review.gitlab-ci.yml
index 507bb73c723..b6c273aeb99 100644
--- a/.gitlab/ci/review.gitlab-ci.yml
+++ b/.gitlab/ci/review.gitlab-ci.yml
@@ -7,20 +7,13 @@ review-cleanup:
environment:
name: review/regular-cleanup
action: access
- variables:
- KUBE_NAMESPACE: "review-apps" # gcp_cleanup.sh requires passing KUBE_NAMESPACE variable
before_script:
- source scripts/utils.sh
- - source scripts/review_apps/gcp_cleanup.sh
- !reference [".use-kube-context", before_script]
- install_gitlab_gem
- - setup_gcp_dependencies
+ - setup_gcloud
script:
- - exit_code_cmd_1=0;
- - exit_code_cmd_2=0;
- - scripts/review_apps/automated_cleanup.rb || exit_code_cmd_1=$?
- - gcp_cleanup || exit_code_cmd_2=$?
- - if [ $exit_code_cmd_1 -ne 0 ] || [ $exit_code_cmd_2 -ne 0 ]; then (scripts/slack review-apps-monitoring "☠️ \`${CI_JOB_NAME}\` failed! ☠️ See ${CI_JOB_URL} - <https://gitlab.com/gitlab-org/quality/engineering-productivity/team/-/blob/main/runbooks/review-apps.md#review-cleanup-job-failed|📗 RUNBOOK 📕>" warning "GitLab Bot" && exit 1); fi;
+ - scripts/review_apps/automated_cleanup.rb || (scripts/slack review-apps-monitoring "☠️ \`${CI_JOB_NAME}\` failed! ☠️ See ${CI_JOB_URL} - <https://gitlab.com/gitlab-org/quality/engineering-productivity/team/-/blob/main/runbooks/review-apps.md#review-cleanup-job-failed|📗 RUNBOOK 📕>" warning "GitLab Bot" && exit 1);
.base-review-checks:
extends:
@@ -28,8 +21,8 @@ review-cleanup:
image: ${REVIEW_APPS_IMAGE}
stage: prepare
before_script:
- - source scripts/review_apps/gcp_cleanup.sh
- - setup_gcp_dependencies
+ - source scripts/utils.sh
+ - setup_gcloud
- !reference [".use-kube-context", before_script]
review-k8s-resources-count-checks:
diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml
index 2f86c449b79..f458f47f6b7 100644
--- a/.gitlab/ci/rules.gitlab-ci.yml
+++ b/.gitlab/ci/rules.gitlab-ci.yml
@@ -38,11 +38,16 @@
# Once https://gitlab.com/gitlab-org/gitlab/-/issues/373904 is implemented, we should be able to change this back to
# if: '$CI_MERGE_REQUEST_IID && $CI_MERGE_REQUEST_APPROVALS_COUNT > 0'
# or any similar condition to check that the MR has *any* approval (not just required approval).
+#
+# Temprorarily adding || $CI_MERGE_REQUEST_LABELS =~ /pipeline:run-full-rspec/ for backward compatibility,
+# remove once https://gitlab.com/gitlab-org/quality/quality-engineering/team-tasks/-/issues/1557 is fully rolled out
.if-merge-request-approved: &if-merge-request-approved
- if: '$CI_MERGE_REQUEST_IID && $CI_MERGE_REQUEST_LABELS =~ /pipeline:run-full-rspec/'
+ if: '$CI_MERGE_REQUEST_IID && $CI_MERGE_REQUEST_LABELS =~ /pipeline:mr-approved/ || $CI_MERGE_REQUEST_LABELS =~ /pipeline:run-full-rspec/'
+# Temprorarily adding && $CI_MERGE_REQUEST_LABELS !~ /pipeline:run-full-rspec/ for backward compatibility,
+# remove once https://gitlab.com/gitlab-org/quality/quality-engineering/team-tasks/-/issues/1557 is fully rolled out
.if-merge-request-not-approved: &if-merge-request-not-approved
- if: '$CI_MERGE_REQUEST_IID && $CI_MERGE_REQUEST_LABELS !~ /pipeline:run-full-rspec/'
+ if: '$CI_MERGE_REQUEST_IID && $CI_MERGE_REQUEST_LABELS !~ /pipeline:mr-approved/ && $CI_MERGE_REQUEST_LABELS !~ /pipeline:run-full-rspec/'
.if-automated-merge-request: &if-automated-merge-request
if: '$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME == "release-tools/update-gitaly" || $CI_MERGE_REQUEST_TARGET_BRANCH_NAME =~ /stable-ee$/'
diff --git a/.rubocop_todo/style/empty_method.yml b/.rubocop_todo/style/empty_method.yml
index 3c00f6278c5..0c7eb5c0d92 100644
--- a/.rubocop_todo/style/empty_method.yml
+++ b/.rubocop_todo/style/empty_method.yml
@@ -163,29 +163,3 @@ Style/EmptyMethod:
- 'qa/qa/service/cluster_provider/k3d.rb'
- 'qa/qa/service/cluster_provider/k3s.rb'
- 'qa/qa/service/cluster_provider/minikube.rb'
- - 'spec/controllers/concerns/check_rate_limit_spec.rb'
- - 'spec/controllers/concerns/issuable_actions_spec.rb'
- - 'spec/initializers/forbid_sidekiq_in_transactions_spec.rb'
- - 'spec/lib/api/helpers/rate_limiter_spec.rb'
- - 'spec/lib/gitlab/database/load_balancing/sidekiq_client_middleware_spec.rb'
- - 'spec/lib/gitlab/database/load_balancing/sidekiq_server_middleware_spec.rb'
- - 'spec/lib/gitlab/database/migration_helpers/restrict_gitlab_schema_spec.rb'
- - 'spec/lib/gitlab/database/partitioning/sliding_list_strategy_spec.rb'
- - 'spec/lib/gitlab/database/postgresql_adapter/dump_schema_versions_mixin_spec.rb'
- - 'spec/lib/gitlab/database/postgresql_database_tasks/load_schema_versions_mixin_spec.rb'
- - 'spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb'
- - 'spec/lib/gitlab/repository_archive_rate_limiter_spec.rb'
- - 'spec/lib/gitlab/repository_cache_adapter_spec.rb'
- - 'spec/lib/gitlab/sidekiq_middleware/client_metrics_spec.rb'
- - 'spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/client_spec.rb'
- - 'spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/server_spec.rb'
- - 'spec/lib/gitlab/sidekiq_middleware/instrumentation_logger_spec.rb'
- - 'spec/lib/gitlab/sidekiq_middleware/query_analyzer_spec.rb'
- - 'spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb'
- - 'spec/lib/gitlab/sidekiq_middleware/worker_context/client_spec.rb'
- - 'spec/lib/gitlab/sidekiq_middleware/worker_context/server_spec.rb'
- - 'spec/lib/gitlab/ssh_public_key_spec.rb'
- - 'spec/lib/gitlab/utils/delegator_override/validator_spec.rb'
- - 'spec/lib/gitlab/utils/delegator_override_spec.rb'
- - 'spec/lib/gitlab/utils/override_spec.rb'
- - 'spec/lib/gitlab/utils/strong_memoize_spec.rb'
diff --git a/app/assets/javascripts/issues/show/components/incidents/constants.js b/app/assets/javascripts/issues/show/components/incidents/constants.js
index db846009409..22db19610c1 100644
--- a/app/assets/javascripts/issues/show/components/incidents/constants.js
+++ b/app/assets/javascripts/issues/show/components/incidents/constants.js
@@ -14,6 +14,7 @@ export const timelineFormI18n = Object.freeze({
areaPlaceholder: s__('Incident|Timeline text...'),
save: __('Save'),
cancel: __('Cancel'),
+ delete: __('Delete'),
description: __('Description'),
hint: __('You can enter up to 280 characters'),
textRemaining: (count) => n__('%d character remaining', '%d characters remaining', count),
diff --git a/app/assets/javascripts/issues/show/components/incidents/edit_timeline_event.vue b/app/assets/javascripts/issues/show/components/incidents/edit_timeline_event.vue
index 60fa8cb949b..8cdd62ca9ef 100644
--- a/app/assets/javascripts/issues/show/components/incidents/edit_timeline_event.vue
+++ b/app/assets/javascripts/issues/show/components/incidents/edit_timeline_event.vue
@@ -40,8 +40,10 @@ export default {
:is-event-processed="editTimelineEventActive"
:previous-occurred-at="event.occurredAt"
:previous-note="event.note"
+ show-delete
@save-event="saveEvent"
@cancel="$emit('hide-edit')"
+ @delete="$emit('delete')"
/>
</div>
</template>
diff --git a/app/assets/javascripts/issues/show/components/incidents/timeline_events_form.vue b/app/assets/javascripts/issues/show/components/incidents/timeline_events_form.vue
index 0f000815978..f1a3aebc990 100644
--- a/app/assets/javascripts/issues/show/components/incidents/timeline_events_form.vue
+++ b/app/assets/javascripts/issues/show/components/incidents/timeline_events_form.vue
@@ -32,6 +32,11 @@ export default {
required: false,
default: false,
},
+ showDelete: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
isEventProcessed: {
type: Boolean,
required: true,
@@ -181,32 +186,42 @@ export default {
</gl-form-group>
</div>
<gl-form-group class="gl-mb-0">
- <gl-button
- variant="confirm"
- category="primary"
- class="gl-mr-3"
- data-testid="save-button"
- :disabled="!isTimelineTextValid"
- :loading="isEventProcessed"
- @click="handleSave(false)"
- >
- {{ $options.i18n.save }}
- </gl-button>
- <gl-button
- v-if="showSaveAndAdd"
- variant="confirm"
- category="secondary"
- class="gl-mr-3 gl-ml-n2"
- data-testid="save-and-add-button"
- :disabled="!isTimelineTextValid"
- :loading="isEventProcessed"
- @click="handleSave(true)"
- >
- {{ $options.i18n.saveAndAdd }}
- </gl-button>
- <gl-button class="gl-ml-n2" :disabled="isEventProcessed" @click="$emit('cancel')">
- {{ $options.i18n.cancel }}
- </gl-button>
+ <div class="gl-display-flex">
+ <gl-button
+ variant="confirm"
+ category="primary"
+ class="gl-mr-3"
+ data-testid="save-button"
+ :disabled="!isTimelineTextValid"
+ :loading="isEventProcessed"
+ @click="handleSave(false)"
+ >
+ {{ $options.i18n.save }}
+ </gl-button>
+ <gl-button
+ v-if="showSaveAndAdd"
+ variant="confirm"
+ category="secondary"
+ class="gl-mr-3 gl-ml-n2"
+ data-testid="save-and-add-button"
+ :disabled="!isTimelineTextValid"
+ :loading="isEventProcessed"
+ @click="handleSave(true)"
+ >
+ {{ $options.i18n.saveAndAdd }}
+ </gl-button>
+ <gl-button class="gl-ml-n2" :disabled="isEventProcessed" @click="$emit('cancel')">
+ {{ $options.i18n.cancel }}
+ </gl-button>
+ <gl-button
+ v-if="showDelete"
+ class="gl-ml-auto btn-danger"
+ :disabled="isEventProcessed"
+ @click="$emit('delete')"
+ >
+ {{ $options.i18n.delete }}
+ </gl-button>
+ </div>
<div class="timeline-event-bottom-border"></div>
</gl-form-group>
</form>
diff --git a/app/assets/javascripts/issues/show/components/incidents/timeline_events_list.vue b/app/assets/javascripts/issues/show/components/incidents/timeline_events_list.vue
index 321b7ccc14a..fad4f07cfc3 100644
--- a/app/assets/javascripts/issues/show/components/incidents/timeline_events_list.vue
+++ b/app/assets/javascripts/issues/show/components/incidents/timeline_events_list.vue
@@ -153,6 +153,7 @@ export default {
:edit-timeline-event-active="editTimelineEventActive"
@handle-save-edit="handleSaveEdit"
@hide-edit="hideEdit()"
+ @delete="handleDelete(event)"
/>
<incident-timeline-event-item
v-else
diff --git a/app/assets/stylesheets/_page_specific_files.scss b/app/assets/stylesheets/_page_specific_files.scss
index addb5f8b9c2..afb0106452d 100644
--- a/app/assets/stylesheets/_page_specific_files.scss
+++ b/app/assets/stylesheets/_page_specific_files.scss
@@ -4,7 +4,6 @@
@import './pages/events';
@import './pages/groups';
@import './pages/hierarchy';
-@import './pages/issuable';
@import './pages/issues';
@import './pages/labels';
@import './pages/login';
diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss
index 9b4323979b7..a4693c20cef 100644
--- a/app/assets/stylesheets/framework/sidebar.scss
+++ b/app/assets/stylesheets/framework/sidebar.scss
@@ -280,3 +280,644 @@
grid-area: user;
}
}
+
+@mixin right-sidebar {
+ position: fixed;
+ top: $header-height;
+ // Default value for CSS var must contain a unit
+ // stylelint-disable-next-line length-zero-no-unit
+ bottom: var(--review-bar-height, 0px);
+ right: 0;
+ transition: width $gl-transition-duration-medium;
+ background-color: $white;
+ z-index: 200;
+ overflow: hidden;
+
+}
+
+.right-sidebar {
+ &:not(.right-sidebar-merge-requests) {
+ @include right-sidebar;
+ }
+
+ &.right-sidebar-merge-requests {
+ @include media-breakpoint-down(md) {
+ @include right-sidebar;
+ z-index: 251;
+ }
+ }
+
+ @include media-breakpoint-down(sm) {
+ z-index: 251;
+ }
+
+ a:not(.btn) {
+ color: inherit;
+
+ &:hover {
+ color: $blue-800;
+ }
+ }
+
+ .gl-label .gl-label-link:hover {
+ color: inherit;
+ }
+
+ .btn-link {
+ color: inherit;
+ }
+
+ .issuable-header-text {
+ margin-top: 7px;
+ }
+
+ .gutter-toggle {
+ display: flex;
+ align-items: center;
+ margin-left: 20px;
+ padding: 4px;
+ border-radius: 4px;
+ height: 24px;
+
+ &:hover {
+ color: $gl-text-color;
+ background: $gray-50;
+ }
+
+ &:hover,
+ &:focus {
+ text-decoration: none;
+ }
+ }
+
+ &.right-sidebar-merge-requests {
+ .block,
+ .sidebar-contained-width,
+ .issuable-sidebar-header {
+ width: 100%;
+ }
+
+ .block {
+ @include media-breakpoint-up(lg) {
+ padding: $gl-spacing-scale-4 0 $gl-spacing-scale-5;
+ }
+
+ &.participants {
+ border-bottom: 0;
+ }
+ }
+ }
+
+ .block,
+ .sidebar-contained-width,
+ .issuable-sidebar-header {
+ @include clearfix;
+ padding: $gl-spacing-scale-4 0 $gl-spacing-scale-5;
+ border-bottom: 1px solid $border-gray-normal;
+ // This prevents the mess when resizing the sidebar
+ // of elements repositioning themselves..
+ width: $gutter-inner-width;
+ // --
+
+ &:last-child {
+ border: 0;
+ }
+
+ &.assignee {
+ .author-link {
+ display: block;
+ position: relative;
+
+ &:hover {
+ .author {
+ text-decoration: underline;
+ }
+ }
+ }
+ }
+
+ &.time-tracking,
+ &.participants,
+ &.subscriptions,
+ &.with-sub-blocks {
+ padding-top: $gl-spacing-scale-5;
+ }
+ }
+
+ .block-first {
+ padding-top: 0;
+ }
+
+ .title {
+ color: $gl-text-color;
+ line-height: $gl-line-height-20;
+
+ .avatar {
+ margin-left: 0;
+ }
+ }
+
+ .selectbox {
+ display: none;
+
+ &.show {
+ display: block;
+ }
+ }
+
+ .btn-clipboard:hover {
+ color: $gl-text-color;
+ }
+
+ .issuable-sidebar {
+ height: 100%;
+
+ &:not(.is-merge-request) {
+ overflow-y: scroll;
+ overflow-x: hidden;
+ -webkit-overflow-scrolling: touch;
+ }
+
+ &.is-merge-request {
+ @include media-breakpoint-down(sm) {
+ overflow-y: scroll;
+ overflow-x: hidden;
+ -webkit-overflow-scrolling: touch;
+ }
+ }
+ }
+
+ &.right-sidebar-expanded {
+ &:not(.right-sidebar-merge-requests) {
+ width: $gutter-width;
+ }
+
+ .value {
+ line-height: 1;
+ }
+
+ .issuable-sidebar {
+ padding: 0 20px;
+
+ &.is-merge-request {
+ @include media-breakpoint-up(lg) {
+ padding: 0;
+
+ .issuable-context-form {
+ --initial-top: calc(#{$header-height} + 76px);
+ --top: var(--initial-top);
+
+ @include gl-sticky;
+ @include gl-overflow-auto;
+
+ top: var(--top);
+ height: calc(100vh - var(--top));
+ padding: 0 15px;
+ margin-bottom: calc(var(--top) * -1);
+
+ .with-performance-bar & {
+ --top: calc(var(--initial-top) + #{$performance-bar-height});
+ }
+
+ .with-system-header & {
+ --top: calc(var(--initial-top) + #{$system-header-height});
+ }
+
+ .with-performance-bar.with-system-header & {
+ --top: calc(var(--initial-top) + #{$system-header-height} + #{$performance-bar-height});
+ }
+ }
+ }
+ }
+ }
+
+ &:not(.boards-sidebar):not([data-signed-in]):not([data-always-show-toggle]) {
+ .issuable-sidebar-header {
+ display: none;
+ }
+ }
+
+ .light {
+ font-weight: $gl-font-weight-normal;
+ }
+
+ .no-value {
+ color: $gl-text-color-secondary;
+ }
+
+ .sidebar-collapsed-icon {
+ display: none;
+ }
+
+ .gutter-toggle {
+ text-align: center;
+ }
+
+ .title .gutter-toggle {
+ margin-top: 0;
+ }
+
+ .assignee .user-list .avatar {
+ margin: 0;
+ }
+
+ .hide-expanded {
+ display: none;
+ }
+ }
+
+ &.right-sidebar-collapsed {
+ /* Extra small devices (phones, less than 768px) */
+ display: none;
+ /* Small devices (tablets, 768px and up) */
+
+ &:not(.right-sidebar-merge-requests) {
+ @include media-breakpoint-up(sm) {
+ display: block;
+ }
+ }
+
+ &.right-sidebar-merge-requests {
+ @include media-breakpoint-up(lg) {
+ display: block;
+ }
+ }
+
+ width: $gutter-collapsed-width;
+ padding: 0;
+
+ .block,
+ .sidebar-contained-width,
+ .issuable-sidebar-header {
+ width: $gutter-collapsed-width - 2px;
+ padding: 0;
+ border-bottom: 0;
+ overflow: hidden;
+ }
+
+ .block,
+ .gutter-toggle,
+ .sidebar-collapsed-container {
+ &.with-sub-blocks .sub-block:hover,
+ &:not(.with-sub-blocks):hover {
+ background-color: $gray-100;
+ }
+ }
+
+ .participants {
+ border-bottom: 1px solid $border-gray-normal;
+ }
+
+ .hide-collapsed {
+ display: none;
+ }
+
+ .gutter-toggle {
+ width: 100%;
+ height: $sidebar-toggle-height;
+ margin-left: 0;
+ border-bottom: 1px solid $border-white-normal;
+ border-radius: 0;
+ }
+
+ a.gutter-toggle {
+ display: flex;
+ justify-content: center;
+ flex-direction: column;
+ text-align: center;
+ }
+
+ .merge-icon {
+ height: 12px;
+ width: 12px;
+ bottom: -5px;
+ right: 4px;
+ }
+
+ .sidebar-collapsed-icon {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ width: 100%;
+ height: $sidebar-toggle-height;
+ text-align: center;
+ color: $gl-text-color-secondary;
+
+ > svg {
+ fill: $gl-text-color-secondary;
+ }
+
+ &:hover:not(.disabled),
+ &:hover .todo-undone {
+ color: $gl-text-color;
+
+ > svg {
+ fill: $gl-text-color;
+ }
+ }
+
+ .todo-undone {
+ color: $blue-600;
+ fill: $blue-600;
+ }
+
+ .author {
+ display: none;
+ }
+
+ .avatar-counter:hover {
+ color: $gl-text-color-secondary;
+ border-color: $gl-text-color-secondary;
+ }
+
+ .btn-clipboard {
+ /*
+ This change should be temporary, because the DOM currently gets
+ generated from a ruby definition in `app/helpers/button_helper.rb`.
+ As soon as the `copy to clipboard` button will be transferred to
+ Vue this should be adjusted as well.
+ */
+ flex: 1;
+ align-self: stretch;
+ padding: 0;
+
+ border: 0;
+ background: transparent;
+ color: $gl-text-color-secondary;
+
+ &:hover {
+ color: $gl-text-color;
+ }
+ }
+
+ &.multiple-users {
+ display: flex;
+ justify-content: center;
+ }
+ }
+
+ .sidebar-avatar-counter {
+ width: 24px;
+ height: 24px;
+ border-radius: 12px;
+
+ ~.merge-icon {
+ bottom: 0;
+ }
+ }
+
+ .sidebar-collapsed-user {
+ padding-bottom: 0;
+
+ .author-link {
+ padding-left: 0;
+
+ .avatar {
+ position: static;
+ margin: 0;
+ }
+ }
+ }
+
+ .issuable-header-btn {
+ display: none;
+ }
+
+ .multiple-users {
+ .btn-link {
+ padding: 0;
+ border: 0;
+
+ .avatar {
+ margin: 0;
+ }
+ }
+
+ .btn-link:first-child {
+ position: absolute;
+ left: 10px;
+ z-index: 1;
+ }
+
+ .btn-link:last-child {
+ position: absolute;
+ right: 10px;
+
+ &:hover {
+ text-decoration: none;
+ }
+ }
+ }
+
+ .milestone-title span,
+ .collapse-truncated-title {
+ @include str-truncated(100%);
+ display: block;
+ margin: 0 4px;
+ }
+ }
+
+ .dropdown-menu-toggle {
+ width: 100%;
+ padding-top: 6px;
+ }
+
+ .dropdown-menu {
+ width: 100%;
+
+ /*
+ * Overwrite hover style for dropdown items, so that they are not blue
+ * This should be removed during dev of https://gitlab.com/gitlab-org/gitlab-foss/issues/44040
+ */
+ li a {
+ &:hover,
+ &:active,
+ &:focus,
+ &.is-focused {
+ @include dropdown-item-hover;
+ }
+ }
+
+ }
+}
+
+.with-performance-bar .right-sidebar {
+ top: calc(#{$header-height} + #{$performance-bar-height});
+}
+
+.sidebar-move-issue-confirmation-button {
+ width: 100%;
+
+ &.is-loading {
+ .sidebar-move-issue-confirmation-loading-icon {
+ display: inline-block;
+ }
+ }
+}
+
+.sidebar-move-issue-confirmation-loading-icon {
+ display: none;
+}
+
+.issuable-show-labels {
+ .gl-label {
+ margin-bottom: 5px;
+ margin-right: 5px;
+ }
+
+ a {
+ display: inline-block;
+
+ .color-label {
+ padding: 4px $grid-size;
+ border-radius: $label-border-radius;
+ margin-right: 4px;
+ margin-bottom: 4px;
+ }
+
+ &:hover .color-label {
+ text-decoration: underline;
+ }
+ }
+
+ &.has-labels {
+ // this font size is a fix to
+ // prevent unintended spacing between labels
+ // which shows up when rendering markup has white-space
+ // characters present.
+ // see: https://css-tricks.com/fighting-the-space-between-inline-block-elements/#article-header-id-3
+ font-size: 0;
+ margin-bottom: -5px;
+ }
+}
+
+.assignee,
+.reviewer {
+ .merge-icon {
+ color: $orange-400;
+ position: absolute;
+ bottom: -3px;
+ right: -3px;
+ filter: drop-shadow(0 0 0.5px $white) drop-shadow(0 0 1px $white) drop-shadow(0 0 2px $white);
+ }
+}
+
+.participants-author {
+ &:nth-of-type(8n) {
+ padding-right: 0;
+ }
+
+ .avatar.avatar-inline {
+ margin: 0;
+ }
+}
+
+.participants-more,
+.user-list-more {
+ margin-left: 5px;
+
+ a,
+ .btn-link {
+ color: $gl-text-color-secondary;
+ }
+
+ .btn-link {
+ padding: 0;
+ }
+
+ .btn-link:hover {
+ color: $blue-800;
+ text-decoration: none;
+ }
+
+ .btn-link:focus {
+ text-decoration: none;
+ }
+}
+
+.sidebar-help-wrap {
+ .sidebar-help-state {
+ margin: 16px -20px -20px;
+ padding: 16px 20px;
+ }
+
+ .help-state-toggle-enter-active {
+ transition: all 0.8s ease;
+ }
+
+ .help-state-toggle-leave-active {
+ transition: all 0.5s ease;
+ }
+
+ .help-state-toggle-enter,
+ .help-state-toggle-leave-active {
+ opacity: 0;
+ }
+}
+
+.time-tracker {
+ .sidebar-collapsed-icon {
+ > .stopwatch-svg {
+ display: inline-block;
+ }
+
+ svg {
+ width: 16px;
+ height: 16px;
+ fill: $gl-text-color-secondary;
+ }
+
+ &:hover svg {
+ fill: $gl-text-color;
+ }
+ }
+
+ .compare-meter {
+ &.over_estimate {
+ .time-remaining,
+ .compare-value.spent {
+ color: $red-500;
+ }
+ }
+ }
+
+ .compare-display-container {
+ font-size: 13px;
+ }
+}
+
+/*
+ * Following overrides are done to prevent
+ * legacy dropdown styles from influencing
+ * GitLab UI components used within GlDropdown
+ */
+.right-sidebar-collapsed {
+ .sidebar-grouped-item {
+ .sidebar-collapsed-icon {
+ margin-bottom: 0;
+ }
+
+ .sidebar-collapsed-divider {
+ line-height: 5px;
+ font-size: 12px;
+ color: $gray-500;
+
+ + .sidebar-collapsed-icon {
+ padding-top: 0;
+ }
+ }
+ }
+}
+
+@include media-breakpoint-down(sm) {
+ // Overriding the following rule with the negative margin
+ // https://gitlab.com/gitlab-org/gitlab/-/blob/146c43c931c3743a140529307aea616e4aa9ff21/app/assets/stylesheets/framework/sidebar.scss#L1-5
+ .container-fluid {
+ .issuable-list,
+ .issues-filters,
+ .epics-filters {
+ margin: 0 (-$gl-padding);
+ }
+ }
+}
diff --git a/app/assets/stylesheets/page_bundles/issuable.scss b/app/assets/stylesheets/page_bundles/issuable.scss
new file mode 100644
index 00000000000..f364170c99f
--- /dev/null
+++ b/app/assets/stylesheets/page_bundles/issuable.scss
@@ -0,0 +1,183 @@
+@import 'mixins_and_variables_and_functions';
+
+.status-box {
+ padding: 0 $gl-btn-padding;
+ border-radius: $border-radius-default;
+ display: block;
+ float: left;
+ margin-right: $gl-padding-8;
+ color: var(--white, $white);
+ font-size: $gl-font-size;
+ line-height: $gl-line-height-24;
+}
+
+.issuable-warning-icon {
+ background-color: var(--orange-50, $orange-50);
+ border-radius: $border-radius-default;
+ color: var(--orange-600, $orange-600);
+ width: $issuable-warning-size;
+ height: $issuable-warning-size;
+ text-align: center;
+ margin-right: $issuable-warning-icon-margin;
+ line-height: $gl-line-height-24;
+ flex: 0 0 auto;
+}
+
+.limit-container-width {
+ .flash-container,
+ .detail-page-header,
+ .page-content-header,
+ .commit-box,
+ .info-well,
+ .commit-ci-menu,
+ .files-changed-inner,
+ .limited-header-width,
+ .limited-width-notes {
+ @include fixed-width-container;
+ }
+
+ .issuable-details {
+ .detail-page-description,
+ .mr-source-target,
+ .mr-state-widget,
+ .merge-manually {
+ @include fixed-width-container;
+ }
+ }
+
+ .merge-request-details {
+ .emoji-list-container {
+ @include fixed-width-container;
+ }
+ }
+}
+
+.issuable-details {
+ section {
+ .issuable-discussion {
+ margin-right: 1px;
+ }
+ }
+
+ .title-container {
+ display: flex;
+ align-items: flex-start;
+ }
+
+ .title {
+ padding: 0;
+ margin-bottom: $gl-padding;
+ border-bottom: 0;
+ word-wrap: break-word;
+ overflow-wrap: break-word;
+ min-width: 0;
+ width: 100%;
+ text-align: initial;
+ }
+
+ .btn-edit {
+ margin-left: auto;
+ }
+}
+
+.detail-page-description {
+ padding: 16px 0;
+
+ small {
+ color: var(--gray-500, $gray-500);
+ }
+}
+
+.edited-text {
+ color: var(--gray-500, $gray-500);
+ display: block;
+ margin: 16px 0 0;
+ font-size: 85%;
+
+ .author-link {
+ color: var(--gray-500, $gray-500);
+ }
+}
+
+.user-item {
+ padding: 5px;
+ flex-basis: 20%;
+
+ .user-link {
+ display: inline-block;
+ }
+}
+
+.issuable-gutter-toggle {
+ @include media-breakpoint-down(sm) {
+ margin-left: $btn-side-margin;
+ }
+}
+
+.issuable-meta {
+ flex: 1;
+ display: inline-block;
+ font-size: 14px;
+ align-self: center;
+ overflow: hidden;
+ text-overflow: ellipsis;
+
+ .user-status-emoji {
+ margin-left: $gl-padding-4;
+ margin-right: 0;
+ }
+}
+
+.js-issuable-selector-wrap {
+ .js-issuable-selector {
+ width: 100%;
+ }
+
+ @include media-breakpoint-down(sm) {
+ margin-bottom: $gl-padding;
+ }
+}
+
+.add-issuable-form-input-wrapper {
+ &.focus {
+ border-color: var(--gray-700, $gray-700);
+ @include gl-focus;
+
+ input {
+ @include gl-shadow-none;
+ }
+ }
+
+ .gl-show-field-errors &.form-control:not(textarea) {
+ height: auto;
+ }
+}
+
+/*
+ * Following overrides are done to prevent
+ * legacy dropdown styles from influencing
+ * GitLab UI components used within GlDropdown
+ */
+.issuable-move-dropdown {
+ .b-dropdown-form {
+ @include gl-p-0;
+ }
+
+ .gl-search-box-by-type button.gl-clear-icon-button:hover {
+ @include gl-bg-transparent;
+ }
+
+ .issuable-move-button:not(.disabled):hover {
+ @include gl-text-white;
+ }
+}
+
+.suggestion-footer {
+ font-size: 12px;
+ line-height: 15px;
+
+ .avatar {
+ margin-top: -3px;
+ border: 0;
+ }
+}
diff --git a/app/assets/stylesheets/page_bundles/issuable_list.scss b/app/assets/stylesheets/page_bundles/issuable_list.scss
new file mode 100644
index 00000000000..b08e129a805
--- /dev/null
+++ b/app/assets/stylesheets/page_bundles/issuable_list.scss
@@ -0,0 +1,96 @@
+@import 'mixins_and_variables_and_functions';
+
+.issuable-list {
+ li {
+ .issuable-info-container {
+ flex: 1;
+ display: flex;
+ }
+
+ .issuable-main-info {
+ flex: 1 auto;
+ margin-right: 10px;
+ min-width: 0;
+
+ .issue-weight-icon,
+ .issue-estimate-icon {
+ vertical-align: sub;
+ }
+ }
+
+ .issuable-meta {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-end;
+ flex: 1 0 auto;
+
+ .controls {
+ margin-bottom: 2px;
+ line-height: 20px;
+ padding: 0;
+ }
+ }
+
+ @include media-breakpoint-down(xs) {
+ .issuable-meta {
+ .controls li {
+ margin-right: 0;
+ }
+ }
+ }
+
+ .issue-check {
+ min-width: 15px;
+ }
+
+ .issuable-milestone,
+ .issuable-info,
+ .task-status,
+ .issuable-timestamp {
+ font-weight: $gl-font-weight-normal;
+ color: var(--gray-500, $gl-text-color-secondary);
+
+ a {
+ color: var(--gl-text-color, $gl-text-color);
+ }
+
+ .gl-label-link {
+ color: inherit;
+
+ &:hover {
+ text-decoration: none;
+
+ .gl-label-text:last-of-type {
+ text-decoration: underline;
+ }
+ }
+ }
+
+ .milestone {
+ color: var(--gray-700, $gray-700);
+ }
+ }
+
+ @media(max-width: map-get($grid-breakpoints, lg)-1) {
+ .task-status,
+ .issuable-due-date,
+ .issuable-weight,
+ .project-ref-path {
+ display: none;
+ }
+ }
+ }
+}
+
+.issuable-list li,
+.issuable-info-container .controls {
+ .avatar-counter {
+ display: inline-block;
+ vertical-align: middle;
+ min-width: 16px;
+ line-height: 14px;
+ height: 16px;
+ padding-left: 2px;
+ padding-right: 2px;
+ }
+}
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
deleted file mode 100644
index 1b6e7954366..00000000000
--- a/app/assets/stylesheets/pages/issuable.scss
+++ /dev/null
@@ -1,912 +0,0 @@
-.status-box {
- padding: 0 $gl-btn-padding;
- border-radius: $border-radius-default;
- display: block;
- float: left;
- margin-right: $gl-padding-8;
- color: $white;
- font-size: $gl-font-size;
- line-height: $gl-line-height-24;
-}
-
-.issuable-warning-icon {
- background-color: $orange-50;
- border-radius: $border-radius-default;
- color: $orange-600;
- width: $issuable-warning-size;
- height: $issuable-warning-size;
- text-align: center;
- margin-right: $issuable-warning-icon-margin;
- line-height: $gl-line-height-24;
- flex: 0 0 auto;
-}
-
-.limit-container-width {
- .flash-container,
- .detail-page-header,
- .page-content-header,
- .commit-box,
- .info-well,
- .commit-ci-menu,
- .files-changed-inner,
- .limited-header-width,
- .limited-width-notes {
- @include fixed-width-container;
- }
-
- .issuable-details {
- .detail-page-description,
- .mr-source-target,
- .mr-state-widget,
- .merge-manually {
- @include fixed-width-container;
- }
- }
-
- .merge-request-details {
- .emoji-list-container {
- @include fixed-width-container;
- }
- }
-}
-
-.issuable-details {
- section {
- .issuable-discussion {
- margin-right: 1px;
- }
- }
-
- .title-container {
- display: flex;
- align-items: flex-start;
- }
-
- .title {
- padding: 0;
- margin-bottom: $gl-padding;
- border-bottom: 0;
- word-wrap: break-word;
- overflow-wrap: break-word;
- min-width: 0;
- width: 100%;
- text-align: initial;
- }
-
- .btn-edit {
- margin-left: auto;
- }
-}
-
-.issuable-show-labels {
- .gl-label {
- margin-bottom: 5px;
- margin-right: 5px;
- }
-
- a {
- display: inline-block;
-
- .color-label {
- padding: 4px $grid-size;
- border-radius: $label-border-radius;
- margin-right: 4px;
- margin-bottom: 4px;
- }
-
- &:hover .color-label {
- text-decoration: underline;
- }
- }
-
- &.has-labels {
- // this font size is a fix to
- // prevent unintended spacing between labels
- // which shows up when rendering markup has white-space
- // characters present.
- // see: https://css-tricks.com/fighting-the-space-between-inline-block-elements/#article-header-id-3
- font-size: 0;
- margin-bottom: -5px;
- }
-}
-
-.assignee,
-.reviewer {
- .merge-icon {
- color: $orange-400;
- position: absolute;
- bottom: -3px;
- right: -3px;
- filter: drop-shadow(0 0 0.5px $white) drop-shadow(0 0 1px $white) drop-shadow(0 0 2px $white);
- }
-}
-
-@mixin right-sidebar {
- position: fixed;
- top: $header-height;
- // Default value for CSS var must contain a unit
- // stylelint-disable-next-line length-zero-no-unit
- bottom: var(--review-bar-height, 0px);
- right: 0;
- transition: width $gl-transition-duration-medium;
- background-color: $white;
- z-index: 200;
- overflow: hidden;
-
-}
-
-.right-sidebar {
- &:not(.right-sidebar-merge-requests) {
- @include right-sidebar;
- }
-
- &.right-sidebar-merge-requests {
- @include media-breakpoint-down(md) {
- @include right-sidebar;
- z-index: 251;
- }
- }
-
- @include media-breakpoint-down(sm) {
- z-index: 251;
- }
-
- a:not(.btn) {
- color: inherit;
-
- &:hover {
- color: $blue-800;
- }
- }
-
- .gl-label .gl-label-link:hover {
- color: inherit;
- }
-
- .btn-link {
- color: inherit;
- }
-
- .issuable-header-text {
- margin-top: 7px;
- }
-
- .gutter-toggle {
- display: flex;
- align-items: center;
- margin-left: 20px;
- padding: 4px;
- border-radius: 4px;
- height: 24px;
-
- &:hover {
- color: $gl-text-color;
- background: $gray-50;
- }
-
- &:hover,
- &:focus {
- text-decoration: none;
- }
- }
-
- &.right-sidebar-merge-requests {
- .block,
- .sidebar-contained-width,
- .issuable-sidebar-header {
- width: 100%;
- }
-
- .block {
- @include media-breakpoint-up(lg) {
- padding: $gl-spacing-scale-4 0 $gl-spacing-scale-5;
- }
-
- &.participants {
- border-bottom: 0;
- }
- }
- }
-
- .block,
- .sidebar-contained-width,
- .issuable-sidebar-header {
- @include clearfix;
- padding: $gl-spacing-scale-4 0 $gl-spacing-scale-5;
- border-bottom: 1px solid $border-gray-normal;
- // This prevents the mess when resizing the sidebar
- // of elements repositioning themselves..
- width: $gutter-inner-width;
- // --
-
- &:last-child {
- border: 0;
- }
-
- &.assignee {
- .author-link {
- display: block;
- position: relative;
-
- &:hover {
- .author {
- text-decoration: underline;
- }
- }
- }
- }
-
- &.time-tracking,
- &.participants,
- &.subscriptions,
- &.with-sub-blocks {
- padding-top: $gl-spacing-scale-5;
- }
- }
-
- .block-first {
- padding-top: 0;
- }
-
- .title {
- color: $gl-text-color;
- line-height: $gl-line-height-20;
-
- .avatar {
- margin-left: 0;
- }
- }
-
- .selectbox {
- display: none;
-
- &.show {
- display: block;
- }
- }
-
- .btn-clipboard:hover {
- color: $gl-text-color;
- }
-
- .issuable-sidebar {
- height: 100%;
-
- &:not(.is-merge-request) {
- overflow-y: scroll;
- overflow-x: hidden;
- -webkit-overflow-scrolling: touch;
- }
-
- &.is-merge-request {
- @include media-breakpoint-down(sm) {
- overflow-y: scroll;
- overflow-x: hidden;
- -webkit-overflow-scrolling: touch;
- }
- }
- }
-
- &.right-sidebar-expanded {
- &:not(.right-sidebar-merge-requests) {
- width: $gutter-width;
- }
-
- .value {
- line-height: 1;
- }
-
- .issuable-sidebar {
- padding: 0 20px;
-
- &.is-merge-request {
- @include media-breakpoint-up(lg) {
- padding: 0;
-
- .issuable-context-form {
- --initial-top: calc(#{$header-height} + 76px);
- --top: var(--initial-top);
-
- @include gl-sticky;
- @include gl-overflow-auto;
-
- top: var(--top);
- height: calc(100vh - var(--top));
- padding: 0 15px;
- margin-bottom: calc(var(--top) * -1);
-
- .with-performance-bar & {
- --top: calc(var(--initial-top) + #{$performance-bar-height});
- }
-
- .with-system-header & {
- --top: calc(var(--initial-top) + #{$system-header-height});
- }
-
- .with-performance-bar.with-system-header & {
- --top: calc(var(--initial-top) + #{$system-header-height} + #{$performance-bar-height});
- }
- }
- }
- }
- }
-
- &:not(.boards-sidebar):not([data-signed-in]):not([data-always-show-toggle]) {
- .issuable-sidebar-header {
- display: none;
- }
- }
-
- .light {
- font-weight: $gl-font-weight-normal;
- }
-
- .no-value {
- color: $gl-text-color-secondary;
- }
-
- .sidebar-collapsed-icon {
- display: none;
- }
-
- .gutter-toggle {
- text-align: center;
- }
-
- .title .gutter-toggle {
- margin-top: 0;
- }
-
- .assignee .user-list .avatar {
- margin: 0;
- }
-
- .hide-expanded {
- display: none;
- }
- }
-
- &.right-sidebar-collapsed {
- /* Extra small devices (phones, less than 768px) */
- display: none;
- /* Small devices (tablets, 768px and up) */
-
- &:not(.right-sidebar-merge-requests) {
- @include media-breakpoint-up(sm) {
- display: block;
- }
- }
-
- &.right-sidebar-merge-requests {
- @include media-breakpoint-up(lg) {
- display: block;
- }
- }
-
- width: $gutter-collapsed-width;
- padding: 0;
-
- .block,
- .sidebar-contained-width,
- .issuable-sidebar-header {
- width: $gutter-collapsed-width - 2px;
- padding: 0;
- border-bottom: 0;
- overflow: hidden;
- }
-
- .block,
- .gutter-toggle,
- .sidebar-collapsed-container {
- &.with-sub-blocks .sub-block:hover,
- &:not(.with-sub-blocks):hover {
- background-color: $gray-100;
- }
- }
-
- .participants {
- border-bottom: 1px solid $border-gray-normal;
- }
-
- .hide-collapsed {
- display: none;
- }
-
- .gutter-toggle {
- width: 100%;
- height: $sidebar-toggle-height;
- margin-left: 0;
- border-bottom: 1px solid $border-white-normal;
- border-radius: 0;
- }
-
- a.gutter-toggle {
- display: flex;
- justify-content: center;
- flex-direction: column;
- text-align: center;
- }
-
- .merge-icon {
- height: 12px;
- width: 12px;
- bottom: -5px;
- right: 4px;
- }
-
- .sidebar-collapsed-icon {
- display: flex;
- flex-direction: column;
- justify-content: center;
- align-items: center;
- width: 100%;
- height: $sidebar-toggle-height;
- text-align: center;
- color: $gl-text-color-secondary;
-
- > svg {
- fill: $gl-text-color-secondary;
- }
-
- &:hover:not(.disabled),
- &:hover .todo-undone {
- color: $gl-text-color;
-
- > svg {
- fill: $gl-text-color;
- }
- }
-
- .todo-undone {
- color: $blue-600;
- fill: $blue-600;
- }
-
- .author {
- display: none;
- }
-
- .avatar-counter:hover {
- color: $gl-text-color-secondary;
- border-color: $gl-text-color-secondary;
- }
-
- .btn-clipboard {
- /*
- This change should be temporary, because the DOM currently gets
- generated from a ruby definition in `app/helpers/button_helper.rb`.
- As soon as the `copy to clipboard` button will be transferred to
- Vue this should be adjusted as well.
- */
- flex: 1;
- align-self: stretch;
- padding: 0;
-
- border: 0;
- background: transparent;
- color: $gl-text-color-secondary;
-
- &:hover {
- color: $gl-text-color;
- }
- }
-
- &.multiple-users {
- display: flex;
- justify-content: center;
- }
- }
-
- .sidebar-avatar-counter {
- width: 24px;
- height: 24px;
- border-radius: 12px;
-
- ~.merge-icon {
- bottom: 0;
- }
- }
-
- .sidebar-collapsed-user {
- padding-bottom: 0;
-
- .author-link {
- padding-left: 0;
-
- .avatar {
- position: static;
- margin: 0;
- }
- }
- }
-
- .issuable-header-btn {
- display: none;
- }
-
- .multiple-users {
- .btn-link {
- padding: 0;
- border: 0;
-
- .avatar {
- margin: 0;
- }
- }
-
- .btn-link:first-child {
- position: absolute;
- left: 10px;
- z-index: 1;
- }
-
- .btn-link:last-child {
- position: absolute;
- right: 10px;
-
- &:hover {
- text-decoration: none;
- }
- }
- }
-
- .milestone-title span,
- .collapse-truncated-title {
- @include str-truncated(100%);
- display: block;
- margin: 0 4px;
- }
- }
-
- .dropdown-menu-toggle {
- width: 100%;
- padding-top: 6px;
- }
-
- .dropdown-menu {
- width: 100%;
-
- /*
- * Overwrite hover style for dropdown items, so that they are not blue
- * This should be removed during dev of https://gitlab.com/gitlab-org/gitlab-foss/issues/44040
- */
- li a {
- &:hover,
- &:active,
- &:focus,
- &.is-focused {
- @include dropdown-item-hover;
- }
- }
-
- }
-}
-
-.with-performance-bar .right-sidebar {
- top: calc(#{$header-height} + #{$performance-bar-height});
-}
-
-.sidebar-move-issue-confirmation-button {
- width: 100%;
-
- &.is-loading {
- .sidebar-move-issue-confirmation-loading-icon {
- display: inline-block;
- }
- }
-}
-
-.sidebar-move-issue-confirmation-loading-icon {
- display: none;
-}
-
-.detail-page-description {
- padding: 16px 0;
-
- small {
- color: $gray-500;
- }
-}
-
-.edited-text {
- color: $gray-500;
- display: block;
- margin: 16px 0 0;
- font-size: 85%;
-
- .author-link {
- color: $gray-500;
- }
-}
-
-.participants-author {
- &:nth-of-type(8n) {
- padding-right: 0;
- }
-
- .avatar.avatar-inline {
- margin: 0;
- }
-}
-
-.user-item {
- padding: 5px;
- flex-basis: 20%;
-
- .user-link {
- display: inline-block;
- }
-}
-
-.participants-more,
-.user-list-more {
- margin-left: 5px;
-
- a,
- .btn-link {
- color: $gl-text-color-secondary;
- }
-
- .btn-link {
- padding: 0;
- }
-
- .btn-link:hover {
- color: $blue-800;
- text-decoration: none;
- }
-
- .btn-link:focus {
- text-decoration: none;
- }
-}
-
-.issuable-gutter-toggle {
- @include media-breakpoint-down(sm) {
- margin-left: $btn-side-margin;
- }
-}
-
-.issuable-meta {
- flex: 1;
- display: inline-block;
- font-size: 14px;
- align-self: center;
- overflow: hidden;
- text-overflow: ellipsis;
-
- .user-status-emoji {
- margin-left: $gl-padding-4;
- margin-right: 0;
- }
-}
-
-.js-issuable-selector-wrap {
- .js-issuable-selector {
- width: 100%;
- }
-
- @include media-breakpoint-down(sm) {
- margin-bottom: $gl-padding;
- }
-}
-
-.issuable-list {
- li {
- .issuable-info-container {
- flex: 1;
- display: flex;
- }
-
- .issuable-main-info {
- flex: 1 auto;
- margin-right: 10px;
- min-width: 0;
-
- .issue-weight-icon,
- .issue-estimate-icon {
- vertical-align: sub;
- }
- }
-
- .issuable-meta {
- display: flex;
- flex-direction: column;
- align-items: flex-end;
- flex: 1 0 auto;
-
- .controls {
- margin-bottom: 2px;
- line-height: 20px;
- padding: 0;
- }
- }
-
- @include media-breakpoint-down(xs) {
- .issuable-meta {
- .controls li {
- margin-right: 0;
- }
- }
- }
-
- .issue-check {
- min-width: 15px;
- }
-
- .issuable-milestone,
- .issuable-info,
- .task-status,
- .issuable-timestamp {
- font-weight: $gl-font-weight-normal;
- color: $gl-text-color-secondary;
-
- a {
- color: $gl-text-color;
- }
-
- .gl-label-link {
- color: inherit;
-
- &:hover {
- text-decoration: none;
-
- .gl-label-text:last-of-type {
- text-decoration: underline;
- }
- }
- }
-
- .milestone {
- color: $gray-700;
- }
- }
-
- @media(max-width: map-get($grid-breakpoints, lg)-1) {
- .task-status,
- .issuable-due-date,
- .issuable-weight,
- .project-ref-path {
- display: none;
- }
- }
- }
-}
-
-.issuable-list li,
-.issuable-info-container .controls {
- .avatar-counter {
- display: inline-block;
- vertical-align: middle;
- min-width: 16px;
- line-height: 14px;
- height: 16px;
- padding-left: 2px;
- padding-right: 2px;
- }
-}
-
-.add-issuable-form-input-wrapper {
- &.focus {
- border-color: $gray-700;
- @include gl-focus;
-
- input {
- @include gl-shadow-none;
- }
- }
-
- .gl-show-field-errors &.form-control:not(textarea) {
- height: auto;
- }
-}
-
-.sidebar-help-wrap {
- .sidebar-help-state {
- margin: 16px -20px -20px;
- padding: 16px 20px;
- }
-
- .help-state-toggle-enter-active {
- transition: all 0.8s ease;
- }
-
- .help-state-toggle-leave-active {
- transition: all 0.5s ease;
- }
-
- .help-state-toggle-enter,
- .help-state-toggle-leave-active {
- opacity: 0;
- }
-}
-
-.time-tracker {
- .sidebar-collapsed-icon {
- > .stopwatch-svg {
- display: inline-block;
- }
-
- svg {
- width: 16px;
- height: 16px;
- fill: $gl-text-color-secondary;
- }
-
- &:hover svg {
- fill: $gl-text-color;
- }
- }
-
- .compare-meter {
- &.over_estimate {
- .time-remaining,
- .compare-value.spent {
- color: $red-500;
- }
- }
- }
-
- .compare-display-container {
- font-size: 13px;
- }
-}
-
-/*
- * Following overrides are done to prevent
- * legacy dropdown styles from influencing
- * GitLab UI components used within GlDropdown
- */
-.issuable-move-dropdown {
- .b-dropdown-form {
- @include gl-p-0;
- }
-
- .gl-search-box-by-type button.gl-clear-icon-button:hover {
- @include gl-bg-transparent;
- }
-
- .issuable-move-button:not(.disabled):hover {
- @include gl-text-white;
- }
-}
-
-.right-sidebar-collapsed {
- .sidebar-grouped-item {
- .sidebar-collapsed-icon {
- margin-bottom: 0;
- }
-
- .sidebar-collapsed-divider {
- line-height: 5px;
- font-size: 12px;
- color: $gray-500;
-
- + .sidebar-collapsed-icon {
- padding-top: 0;
- }
- }
- }
-}
-
-.suggestion-footer {
- font-size: 12px;
- line-height: 15px;
-
- .avatar {
- margin-top: -3px;
- border: 0;
- }
-}
-
-@include media-breakpoint-down(sm) {
- // Overriding the following rule with the negative margin
- // https://gitlab.com/gitlab-org/gitlab/-/blob/146c43c931c3743a140529307aea616e4aa9ff21/app/assets/stylesheets/framework/sidebar.scss#L1-5
- .container-fluid {
- .issuable-list,
- .issues-filters,
- .epics-filters {
- margin: 0 (-$gl-padding);
- }
- }
-}
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 7e85337c21f..71ca09717d9 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -46,7 +46,7 @@ module Ci
# DELETE queries when the Ci::Build is destroyed. The next step is to remove `dependent: :destroy`.
# Details: https://gitlab.com/gitlab-org/gitlab/-/issues/24644#note_689472685
has_many :job_artifacts, class_name: 'Ci::JobArtifact', foreign_key: :job_id, dependent: :destroy, inverse_of: :job # rubocop:disable Cop/ActiveRecordDependent
- has_many :job_variables, class_name: 'Ci::JobVariable', foreign_key: :job_id
+ has_many :job_variables, class_name: 'Ci::JobVariable', foreign_key: :job_id, inverse_of: :job
has_many :sourced_pipelines, class_name: 'Ci::Sources::Pipeline', foreign_key: :source_job_id
has_many :pages_deployments, inverse_of: :ci_build
diff --git a/app/models/ci/job_variable.rb b/app/models/ci/job_variable.rb
index 332a78b66ae..998f0647ad5 100644
--- a/app/models/ci/job_variable.rb
+++ b/app/models/ci/job_variable.rb
@@ -2,12 +2,15 @@
module Ci
class JobVariable < Ci::ApplicationRecord
+ include Ci::Partitionable
include Ci::NewHasVariable
include Ci::RawVariable
include BulkInsertSafe
belongs_to :job, class_name: "Ci::Build", foreign_key: :job_id
+ partitionable scope: :job
+
alias_attribute :secret_value, :value
validates :key, uniqueness: { scope: :job_id }, unless: :dotenv_source?
diff --git a/app/models/concerns/ci/partitionable.rb b/app/models/concerns/ci/partitionable.rb
index 6e01da2e9df..88692a735ba 100644
--- a/app/models/concerns/ci/partitionable.rb
+++ b/app/models/concerns/ci/partitionable.rb
@@ -32,6 +32,7 @@ module Ci
Ci::BuildTraceMetadata
Ci::BuildPendingState
Ci::JobArtifact
+ Ci::JobVariable
Ci::Pipeline
Ci::PendingBuild
Ci::RunningBuild
diff --git a/app/models/packages/rpm/repository_file.rb b/app/models/packages/rpm/repository_file.rb
index 4b5fa59c6ee..614ec9b3e56 100644
--- a/app/models/packages/rpm/repository_file.rb
+++ b/app/models/packages/rpm/repository_file.rb
@@ -8,6 +8,8 @@ module Packages
include Packages::Installable
INSTALLABLE_STATUSES = [:default].freeze
+ FILELISTS_FILENAME = 'filelists.xml'
+ FILELISTS_SIZE_LIMITATION = 20.megabytes
enum status: { default: 0, pending_destruction: 1, processing: 2, error: 3 }
@@ -20,6 +22,14 @@ module Packages
mount_file_store_uploader Packages::Rpm::RepositoryFileUploader
update_project_statistics project_statistics_name: :packages_size
+
+ def self.has_oversized_filelists?(project_id:)
+ where(
+ project_id: project_id,
+ file_name: FILELISTS_FILENAME,
+ size: [FILELISTS_SIZE_LIMITATION..]
+ ).exists?
+ end
end
end
end
diff --git a/app/views/admin/application_settings/_error_tracking.html.haml b/app/views/admin/application_settings/_error_tracking.html.haml
index 5a8aba5784e..aa42cd99e89 100644
--- a/app/views/admin/application_settings/_error_tracking.html.haml
+++ b/app/views/admin/application_settings/_error_tracking.html.haml
@@ -37,4 +37,4 @@
= f.label :error_tracking_api_url, _('Opstrace endpoint for Error Tracking integration'), class: 'label-light'
= f.text_field :error_tracking_api_url, class: 'form-control gl-form-input'
- = f.submit _('Save changes'), class: 'gl-button btn btn-confirm'
+ = f.submit _('Save changes'), pajamas_button: true
diff --git a/app/views/admin/topics/_form.html.haml b/app/views/admin/topics/_form.html.haml
index 9b9d97950cc..544310e312c 100644
--- a/app/views/admin/topics/_form.html.haml
+++ b/app/views/admin/topics/_form.html.haml
@@ -38,10 +38,13 @@
- if @topic.new_record?
.form-actions
- = f.submit _('Create topic'), class: "gl-button btn btn-confirm"
- = link_to _('Cancel'), admin_topics_path, class: "gl-button btn btn-default btn-cancel"
+ = f.submit _('Create topic'), pajamas_button: true
+ = render Pajamas::ButtonComponent.new(href: admin_topics_path) do
+ = _('Cancel')
- else
.form-actions
- = f.submit _('Save changes'), class: "gl-button btn btn-confirm", data: { qa_selector: 'save_changes_button' }
- = link_to _('Cancel'), admin_topics_path, class: "gl-button btn btn-cancel"
+ = f.submit _('Save changes'), pajamas_button: true, data: { qa_selector: 'save_changes_button' }
+ = render Pajamas::ButtonComponent.new(href: admin_topics_path) do
+ = _('Cancel')
+
diff --git a/app/views/devise/unlocks/new.html.haml b/app/views/devise/unlocks/new.html.haml
index abaf169afd5..b9d50e48d05 100644
--- a/app/views/devise/unlocks/new.html.haml
+++ b/app/views/devise/unlocks/new.html.haml
@@ -1,14 +1,14 @@
= render 'devise/shared/tab_single', tab_title: _('Resend unlock instructions')
.login-box
.login-body
- = form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post, class: 'gl-show-field-errors' }) do |f|
+ = gitlab_ui_form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post, class: 'gl-show-field-errors' }) do |f|
.devise-errors
= render "devise/shared/error_messages", resource: resource
.form-group.gl-mb-6
= f.label :email
= f.email_field :email, class: 'form-control', autofocus: 'autofocus', autocapitalize: 'off', autocorrect: 'off', title: _('Please provide a valid email address.')
.clearfix
- = f.submit _('Resend unlock instructions'), class: 'gl-button btn btn-confirm'
+ = f.submit _('Resend unlock instructions'), pajamas_button: true, class: 'gl-w-full'
.clearfix.prepend-top-20
= render 'devise/shared/sign_in_link'
diff --git a/app/views/groups/_import_group_from_another_instance_panel.html.haml b/app/views/groups/_import_group_from_another_instance_panel.html.haml
index d48bf0173a4..19c11d110b7 100644
--- a/app/views/groups/_import_group_from_another_instance_panel.html.haml
+++ b/app/views/groups/_import_group_from_another_instance_panel.html.haml
@@ -24,7 +24,7 @@
.gl-font-weight-normal
- pat_link_start = '<a href="%{url}" target="_blank">'.html_safe % { url: help_page_path('user/profile/personal_access_tokens') }
- short_living_link_start = '<a href="%{url}" target="_blank">'.html_safe % { url: help_page_path('security/token_overview', anchor: 'security-considerations') }
- = s_('GroupsNew|Create this in the %{pat_link_start}user settings%{pat_link_end} of the source GitLab instance. For %{short_living_link_start}security reasons%{short_living_link_end}, use a short expiration date when creating the token.').html_safe % { pat_link_start: pat_link_start, pat_link_end: '</a>'.html_safe, short_living_link_start: short_living_link_start, short_living_link_end: '</a>'.html_safe }
+ = s_('GroupsNew|Create a token with %{code_start}api%{code_end} and %{code_start}read_repository%{code_end} scopes in the %{pat_link_start}user settings%{pat_link_end} of the source GitLab instance. For %{short_living_link_start}security reasons%{short_living_link_end}, set a short expiration date for the token. Keep in mind that large migrations take more time.').html_safe % { code_start: '<code>'.html_safe, code_end: '</code>'.html_safe, pat_link_start: pat_link_start, pat_link_end: '</a>'.html_safe, short_living_link_start: short_living_link_start, short_living_link_end: '</a>'.html_safe }
= f.text_field :bulk_import_gitlab_access_token, placeholder: s_('GroupsNew|e.g. h8d3f016698e...'), class: 'gl-form-input gl-mt-3 col-xs-12 col-sm-8',
required: true,
autocomplete: 'off',
diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml
index e032d64e219..6faa4758d66 100644
--- a/app/views/groups/issues.html.haml
+++ b/app/views/groups/issues.html.haml
@@ -1,4 +1,5 @@
- page_title _('Issues')
+- add_page_specific_style 'page_bundles/issuable_list'
- add_page_specific_style 'page_bundles/issues_list'
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, safe_params.merge(rss_url_options).to_h, title: "#{@group.name} issues")
diff --git a/app/views/projects/issuable/_show.html.haml b/app/views/projects/issuable/_show.html.haml
index 0898e0ae52d..ec233bc9aff 100644
--- a/app/views/projects/issuable/_show.html.haml
+++ b/app/views/projects/issuable/_show.html.haml
@@ -3,6 +3,7 @@
- page_card_attributes issuable.card_attributes
- if issuable.relocation_target
- page_canonical_link issuable.relocation_target.present(current_user: current_user).web_url
+- add_page_specific_style 'page_bundles/issuable'
= render "projects/issues/service_desk/alert_moved_from_service_desk", issue: issuable
diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml
index ae6b157f452..f8f57934303 100644
--- a/app/views/projects/issues/index.html.haml
+++ b/app/views/projects/issues/index.html.haml
@@ -1,4 +1,5 @@
- page_title _('Issues')
+- add_page_specific_style 'page_bundles/issuable_list'
- add_page_specific_style 'page_bundles/issues_list'
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, safe_params.merge(rss_url_options).to_h, title: "#{@project.name} issues")
diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml
index a3f40207d20..79da09c5205 100644
--- a/app/views/projects/merge_requests/index.html.haml
+++ b/app/views/projects/merge_requests/index.html.haml
@@ -5,6 +5,7 @@
- page_title _("Merge requests")
- new_merge_request_email = @project.new_issuable_address(current_user, 'merge_request')
+- add_page_specific_style 'page_bundles/issuable_list'
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, safe_params.merge(rss_url_options).to_h, title: "#{@project.name} merge requests")
diff --git a/app/views/projects/merge_requests/show.html.haml b/app/views/projects/merge_requests/show.html.haml
index 41c2187f0b1..e42501ea278 100644
--- a/app/views/projects/merge_requests/show.html.haml
+++ b/app/views/projects/merge_requests/show.html.haml
@@ -8,6 +8,7 @@
- page_card_attributes @merge_request.card_attributes
- suggest_changes_help_path = help_page_path('user/project/merge_requests/reviews/suggestions.md')
- mr_action = j(params[:tab].presence || 'show')
+- add_page_specific_style 'page_bundles/issuable'
- add_page_specific_style 'page_bundles/design_management'
- add_page_specific_style 'page_bundles/merge_requests'
- add_page_specific_style 'page_bundles/pipelines'
diff --git a/config/application.rb b/config/application.rb
index 51818579ccf..da6fb5e4c31 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -284,6 +284,8 @@ module Gitlab
config.assets.precompile << "page_bundles/incident_management_list.css"
config.assets.precompile << "page_bundles/incidents.css"
config.assets.precompile << "page_bundles/issues_analytics.css"
+ config.assets.precompile << "page_bundles/issuable.css"
+ config.assets.precompile << "page_bundles/issuable_list.css"
config.assets.precompile << "page_bundles/issues_list.css"
config.assets.precompile << "page_bundles/issues_show.css"
config.assets.precompile << "page_bundles/jira_connect.css"
diff --git a/doc/architecture/blueprints/ci_pipeline_components/index.md b/doc/architecture/blueprints/ci_pipeline_components/index.md
index 27b82a7e6b0..87a0e7c0b66 100644
--- a/doc/architecture/blueprints/ci_pipeline_components/index.md
+++ b/doc/architecture/blueprints/ci_pipeline_components/index.md
@@ -173,6 +173,20 @@ A `gitlab-<component-type>.yml` file:
- Can optionally define **output data** that it returns.
- Should be **validated statically** (for example: using JSON schema validators).
+```yaml
+spec:
+ inputs:
+ website:
+ environment:
+ default: test
+ test_run:
+ options:
+ - unit
+ - integration
+ - system
+content: { ... }
+```
+
Components that are released in the catalog must have a `README.md` file in the same directory as the
metadata YAML file. The `README.md` represents the documentation for the specific component, hence it's recommended
even when not releasing versions in the catalog.
@@ -274,6 +288,166 @@ NOTE:
Any nesting more than 1 level is initially not permitted.
This limitation encourages cohesion at project level and keeps complexity low.
+## Input parameters `spec:inputs:` parameters
+
+If the component takes any input parameters they must be specified according to the following schema:
+
+```yaml
+spec:
+ inputs:
+ website: # by default all declared inputs are mandatory.
+ environment:
+ default: test # apply default if not provided. This makes the input optional.
+ test_run:
+ options: # a choice must be made from the list since there is no default value.
+ - unit
+ - integration
+ - system
+```
+
+When using the component we pass the input parameters as follows:
+
+```yaml
+include:
+ - component: org/my-component@1.0
+ with:
+ website: ${MY_WEBSITE} # variables expansion
+ test_run: system
+ environment: $[[ inputs.environment ]] # interpolation of upstream inputs
+```
+
+Variables expansion must be supported for `with:` syntax as well as interpolation of
+possible [inputs provided upstream](#input-parameters-for-pipelines).
+
+Input parameters are validated as soon as possible:
+
+1. Read the file `gitlab-template.yml` inside `org/my-component`.
+1. Parse `spec:inputs` and validate the parameters against this schema.
+1. If successfully validated, proceed with parsing `content:`. Return an error otherwise.
+1. Interpolate input parameters inside the component's `content:`.
+
+```yaml
+spec:
+ inputs:
+ environment:
+ options: [test, staging, production]
+content:
+ "run-tests-$[[ inputs.environment ]]":
+ script: ./run-test
+
+ scan-website:
+ script: ./scan-website $[[ inputs.environment ]]
+ rules:
+ - if: $[[ inputs.environment ]] == 'staging'
+ - if: $[[ inputs.environment ]] == 'production'
+```
+
+With `$[[ inputs.XXX ]]` inputs are interpolated immediately after parsing the `content:`.
+
+### Why input parameters and not environment variables?
+
+Until today we have been leveraging environment variables to pass information around.
+For example, we use environment variables to pass information from an upstream pipeline to a
+downstream pipeline.
+
+Using environment variables for passing information to a component is like declaring global
+variables in programming languages. The more variables we declare the more we risk variable
+conflicts and increase variables scope.
+
+Input parameters are like variables passed to the component which exist inside a specific
+scope and they don't leak to the outside.
+Inputs are not inherited from upstream `include`s. They must be passed explicitly.
+
+This paradigm allows to build more robust and isolated components as well as declare and
+enforce contracts.
+
+### Input parameters for existing `include:` syntax
+
+Because we are adding input parameters to components used via `include:component` we have an opportunity to
+extend it to other `include:` types support inputs via `with:` syntax:
+
+```yaml
+include:
+ - component: org/my-component@1.0
+ with:
+ foo: bar
+ - local: path/to/file.yml
+ with:
+ foo: bar
+ - project: org/another
+ file: .gitlab-ci.yml
+ with:
+ foo: bar
+ - remote: http://example.com/ci/config
+ with:
+ foo: bar
+ - template: Auto-DevOps.gitlab-ci.yml
+ with:
+ foo: bar
+```
+
+Then the configuration being included must specify the inputs:
+
+```yaml
+spec:
+ inputs:
+ foo:
+
+# rest of the configuration
+```
+
+If a YAML includes content using `with:` but the including YAML doesn't specify `inputs:`, an error should be raised.
+
+|`with:`| `inputs:` | result |
+| --- | --- | --- |
+| specified | | raise error |
+| specified | specified | validate inputs |
+| | specified | use defaults |
+| | | legacy `include:` without input passing |
+
+### Input parameters for pipelines
+
+Inputs can also be used to pass parameters to a pipeline when triggered and benefit from immediate validation.
+
+Today we have different use cases where using explicit input parameters would be beneficial:
+
+1. `Run Pipeline` UI form.
+ - **Problem today**: We are using top-level variables with `variables:*:description` to surface environment variables to the UI.
+ The problem with this is the mix of responsibilities as well as the jump in [precedence](../../../ci/variables/index.md#cicd-variable-precedence)
+ that a variable gets (from a YAML variable to a pipeline variable).
+ Building validation and features on top of this solution is challenging and complex.
+1. Trigger a pipeline via API. For example `POST /projects/:id/pipelines/trigger` with `{ inputs: { provider: 'aws' } }`
+1. Trigger a pipeline via `trigger:` syntax.
+
+```yaml
+deploy-app:
+ trigger:
+ project: org/deployer
+ with:
+ provider: aws
+ deploy_environment: staging
+```
+
+To solve the problem of `Run Pipeline` UI form we could fully leverage the `spec:inputs` schema:
+
+```yaml
+spec:
+ inputs:
+ concurrency:
+ default: 10 # displayed as default value in the input box
+ provider: # can enforce `required` in the form validation
+ description: Deployment provider # optional: render as input label.
+ deploy_environment:
+ options: # render a selectbox with options in order of how they are defined below
+ - staging # 1st option
+ - canary # 2nd option
+ - production # 3rd option
+ default: staging # selected by default in the UI.
+ # if `default:` is not specified, the user must explicitly select
+ # an option.
+ description: Deployment environment # optional: render as input label.
+```
+
## Limits
Any MVC that exposes a feature should be added with limitations from the beginning.
diff --git a/doc/ci/variables/predefined_variables.md b/doc/ci/variables/predefined_variables.md
index 852110a1415..93dcb57fe96 100644
--- a/doc/ci/variables/predefined_variables.md
+++ b/doc/ci/variables/predefined_variables.md
@@ -40,7 +40,7 @@ as it can cause the pipeline to behave unexpectedly.
| `CI_COMMIT_SHORT_SHA` | 11.7 | all | The first eight characters of `CI_COMMIT_SHA`. |
| `CI_COMMIT_TAG` | 9.0 | 0.5 | The commit tag name. Available only in pipelines for tags. |
| `CI_COMMIT_TAG_MESSAGE` | 15.5 | all | The commit tag message. Available only in pipelines for tags. |
-| `CI_COMMIT_TIMESTAMP` | 13.4 | all | The timestamp of the commit in the ISO 8601 format. |
+| `CI_COMMIT_TIMESTAMP` | 13.4 | all | The timestamp of the commit in the [ISO 8601](https://www.rfc-editor.org/rfc/rfc3339#appendix-A) format. |
| `CI_COMMIT_TITLE` | 10.8 | all | The title of the commit. The full first line of the message. |
| `CI_CONCURRENT_ID` | all | 11.10 | The unique ID of build execution in a single executor. |
| `CI_CONCURRENT_PROJECT_ID` | all | 11.10 | The unique ID of build execution in a single executor and project. |
diff --git a/doc/development/documentation/styleguide/index.md b/doc/development/documentation/styleguide/index.md
index 342d6b7845f..90f93e1db3f 100644
--- a/doc/development/documentation/styleguide/index.md
+++ b/doc/development/documentation/styleguide/index.md
@@ -345,6 +345,14 @@ Some contractions, however, should be avoided:
<!-- vale gitlab.Possessive = YES -->
+### Possessives
+
+Try to avoid using possessives (`'s`) for proper nouns, like organization or product names.
+
+For example, instead of `Docker's CLI`, use `the Docker CLI`.
+
+For details, see [the Google documentation style guide](https://developers.google.com/style/possessives#product,-feature,-and-company-names).
+
### Prepositions
Use prepositions at the end of the sentence when needed.
@@ -361,7 +369,12 @@ These constructions are more casual than the alternatives:
### Acronyms
If you use an acronym, spell it out on first use on a page. You do not need to spell it out more than once on a page.
-When possible, try to avoid acronyms in topic titles.
+
+- **Titles:** Try to avoid acronyms in topic titles, especially if the acronym is not widely used.
+- **Plurals:** Try not to make acronyms plural. For example, use `YAML files`, not `YAMLs`. If you must make an acronym plural, do not use an apostrophe. For example, use `APIs`, not `API's`.
+- **Possessives:** Use caution when making an acronym possessive. If possible,
+ write the sentence to avoid making the acronym possessive. If you must make the
+ acronym possessive, consider spelling out the words.
### Numbers
diff --git a/doc/development/pipelines/index.md b/doc/development/pipelines/index.md
index 05e9332b40f..883bede6724 100644
--- a/doc/development/pipelines/index.md
+++ b/doc/development/pipelines/index.md
@@ -68,8 +68,8 @@ Later on in [the `rspec fail-fast` job](#fail-fast-job-in-merge-request-pipeline
In addition, there are a few circumstances where we would always run the full RSpec tests:
-- when the `pipeline:run-all-rspec` label is set on the merge request
-- when the `pipeline:run-full-rspec` label is set on the merge request, this label is assigned by triage automation when the merge request is approved by any reviewer
+- when the `pipeline:run-all-rspec` label is set on the merge request. This label will trigger all RSpec tests including those run in the `as-if-foss` jobs.
+- when the `pipeline:mr-approved` label is set on the merge request, and if the code changes satisfy the `backend-patterns` rule. Note that this label is assigned by triage automation when the merge request is approved by any reviewer. It is not recommended to apply this label manually.
- when the merge request is created by an automation (for example, Gitaly update or MR targeting a stable branch)
- when the merge request is created in a security mirror
- when any CI configuration file is changed (for example, `.gitlab-ci.yml` or `.gitlab/ci/**/*`)
diff --git a/doc/operations/incident_management/incident_timeline_events.md b/doc/operations/incident_management/incident_timeline_events.md
index 58448222356..897fcb9efc6 100644
--- a/doc/operations/incident_management/incident_timeline_events.md
+++ b/doc/operations/incident_management/incident_timeline_events.md
@@ -85,6 +85,8 @@ of an incident.
## Delete an event
+> Ability to delete an event when editing it [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/372265) in GitLab 15.7.
+
You can also delete timeline events.
Prerequisites:
@@ -99,3 +101,9 @@ To delete a timeline event:
1. Select the **Timeline** tab.
1. On the right of a timeline event, select **More actions** (**{ellipsis_v}**) and then select **Delete**.
1. To confirm, select **Delete Event**.
+
+Alternatively:
+
+1. On the right of a timeline event, select **More actions** (**{ellipsis_v}**) and then select **Edit**.
+1. Select **Delete**.
+1. To confirm, select **Delete event**.
diff --git a/lib/api/rpm_project_packages.rb b/lib/api/rpm_project_packages.rb
index 40b8d022c6c..db85113f7a7 100644
--- a/lib/api/rpm_project_packages.rb
+++ b/lib/api/rpm_project_packages.rb
@@ -64,6 +64,10 @@ module API
bad_request!('File is too large')
end
+ if Packages::Rpm::RepositoryFile.has_oversized_filelists?(project_id: authorized_user_project.id)
+ bad_request!('Repository packages limit exceeded')
+ end
+
track_package_event(
'push_package',
:rpm,
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 2cf744a96f0..8b88701ef0a 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -19748,6 +19748,9 @@ msgstr ""
msgid "GroupsNew|Contact an administrator to enable options for importing your group."
msgstr ""
+msgid "GroupsNew|Create a token with %{code_start}api%{code_end} and %{code_start}read_repository%{code_end} scopes in the %{pat_link_start}user settings%{pat_link_end} of the source GitLab instance. For %{short_living_link_start}security reasons%{short_living_link_end}, set a short expiration date for the token. Keep in mind that large migrations take more time."
+msgstr ""
+
msgid "GroupsNew|Create group"
msgstr ""
@@ -19757,9 +19760,6 @@ msgstr ""
msgid "GroupsNew|Create subgroup"
msgstr ""
-msgid "GroupsNew|Create this in the %{pat_link_start}user settings%{pat_link_end} of the source GitLab instance. For %{short_living_link_start}security reasons%{short_living_link_end}, use a short expiration date when creating the token."
-msgstr ""
-
msgid "GroupsNew|GitLab source URL"
msgstr ""
@@ -37259,6 +37259,9 @@ msgstr ""
msgid "SecurityReports|No vulnerabilities found for this pipeline"
msgstr ""
+msgid "SecurityReports|Not available"
+msgstr ""
+
msgid "SecurityReports|Oops, something doesn't seem right."
msgstr ""
diff --git a/scripts/review_apps/automated_cleanup.rb b/scripts/review_apps/automated_cleanup.rb
index 7e1773a9c60..df1019f758b 100755
--- a/scripts/review_apps/automated_cleanup.rb
+++ b/scripts/review_apps/automated_cleanup.rb
@@ -22,6 +22,7 @@ module ReviewApps
IGNORED_KUBERNETES_ERRORS = [
'NotFound'
].freeze
+ ENVIRONMENTS_NOT_FOUND_THRESHOLD = 3
# $GITLAB_PROJECT_REVIEW_APP_CLEANUP_API_TOKEN => `Automated Review App Cleanup` project token
def initialize(
@@ -30,10 +31,11 @@ module ReviewApps
api_endpoint: ENV['CI_API_V4_URL'],
options: {}
)
- @project_path = project_path
- @gitlab_token = gitlab_token
- @api_endpoint = api_endpoint
- @dry_run = options[:dry_run]
+ @project_path = project_path
+ @gitlab_token = gitlab_token
+ @api_endpoint = api_endpoint
+ @dry_run = options[:dry_run]
+ @environments_not_found_count = 0
puts "Dry-run mode." if dry_run
end
@@ -172,7 +174,7 @@ module ReviewApps
private
- attr_reader :project_path, :gitlab_token, :api_endpoint, :dry_run
+ attr_reader :api_endpoint, :dry_run, :environments_not_found_count, :gitlab_token, :project_path
def fetch_environment(environment)
gitlab.environment(project_path, environment.id)
@@ -186,10 +188,17 @@ module ReviewApps
print_release_state(subject: 'Review app', release_name: environment.slug, release_date: release_date, action: 'deleting')
gitlab.delete_environment(project_path, environment.id) unless dry_run
+ rescue Gitlab::Error::NotFound
+ puts "Review app '#{environment.name}' / '#{environment.slug}' (##{environment.id}) was not found: ignoring it"
+ environments_not_found_count += 1
+
+ if environments_not_found_count >= ENVIRONMENTS_NOT_FOUND_THRESHOLD
+ raise "At least #{ENVIRONMENTS_NOT_FOUND_THRESHOLD} environments were missing when we tried to delete them. Please investigate"
+ end
rescue Gitlab::Error::Forbidden
puts "Review app '#{environment.name}' / '#{environment.slug}' (##{environment.id}) is forbidden: skipping it"
rescue Gitlab::Error::InternalServerError
- puts "Review app '#{environment.name}' / '#{environment.slug}' (##{environment.id}) 500 error - ignoring it"
+ puts "Review app '#{environment.name}' / '#{environment.slug}' (##{environment.id}) 500 error: ignoring it"
end
def stop_environment(environment, deployment)
diff --git a/scripts/review_apps/gcp_cleanup.sh b/scripts/review_apps/gcp_cleanup.sh
deleted file mode 100755
index 114ac6f7ec0..00000000000
--- a/scripts/review_apps/gcp_cleanup.sh
+++ /dev/null
@@ -1,160 +0,0 @@
-#!/usr/bin/env bash
-
-source scripts/utils.sh
-
-function setup_gcp_dependencies() {
- apt-get update && apt-get install -y jq
-
- gcloud auth activate-service-account --key-file="${REVIEW_APPS_GCP_CREDENTIALS}"
- gcloud config set project "${REVIEW_APPS_GCP_PROJECT}"
-}
-
-# These scripts require the following environment variables:
-# - REVIEW_APPS_GCP_REGION - e.g `us-central1`
-# - KUBE_NAMESPACE - e.g `review-apps`
-
-function delete_firewall_rules() {
- if [[ ${#@} -eq 0 ]]; then
- echoinfo "No firewall rules to be deleted" true
- return
- fi
-
- echoinfo "Deleting firewall rules:" true
- echo "${@}"
-
- if [[ ${DRY_RUN} = 1 ]]; then
- echo "[DRY RUN] gcloud compute firewall-rules delete -q" "${@}"
- else
- gcloud compute firewall-rules delete -q "${@}"
- fi
-}
-
-function delete_forwarding_rules() {
- if [[ ${#@} -eq 0 ]]; then
- echoinfo "No forwarding rules to be deleted" true
- return
- fi
-
- echoinfo "Deleting forwarding rules:" true
- echo "${@}"
-
- if [[ ${DRY_RUN} = 1 ]]; then
- echo "[DRY RUN] gcloud compute forwarding-rules delete -q" "${@}" "--region ${REVIEW_APPS_GCP_REGION}"
- else
- gcloud compute forwarding-rules delete -q "${@}" --region "${REVIEW_APPS_GCP_REGION}"
- fi
-}
-
-function delete_target_pools() {
- if [[ ${#@} -eq 0 ]]; then
- echoinfo "No target pools to be deleted" true
- return
- fi
-
- echoinfo "Deleting target pools:" true
- echo "${@}"
-
- if [[ ${DRY_RUN} = 1 ]]; then
- echo "[DRY RUN] gcloud compute target-pools delete -q" "${@}" "--region ${REVIEW_APPS_GCP_REGION}"
- else
- gcloud compute target-pools delete -q "${@}" --region "${REVIEW_APPS_GCP_REGION}"
- fi
-}
-
-function delete_http_health_checks() {
- if [[ ${#@} -eq 0 ]]; then
- echoinfo "No http health checks to be deleted" true
- return
- fi
-
- echoinfo "Deleting http health checks:" true
- echo "${@}"
-
- if [[ ${DRY_RUN} = 1 ]]; then
- echo "[DRY RUN] gcloud compute http-health-checks delete -q" "${@}"
- else
- gcloud compute http-health-checks delete -q "${@}"
- fi
-}
-
-function get_related_firewall_rules() {
- local forwarding_rule=${1}
-
- gcloud compute firewall-rules list --filter "name~${forwarding_rule}" --format "value(name)"
-}
-
-function get_service_name_in_forwarding_rule() {
- local forwarding_rule=${1}
-
- gcloud compute forwarding-rules describe "${forwarding_rule}" --region "${REVIEW_APPS_GCP_REGION}" --format "value(description)" | jq -r '.["kubernetes.io/service-name"]'
-}
-
-function forwarding_rule_k8s_service_exists() {
- local namespace="${KUBE_NAMESPACE}"
- local namespaced_service_name=$(get_service_name_in_forwarding_rule "$forwarding_rule")
-
- if [[ ! $namespaced_service_name =~ ^"${namespace}" ]]; then
- return 0 # this prevents `review-apps-ee` pipeline from deleting `review-apps-ce` resources and vice versa
- fi
-
- local service_name=$(echo "${namespaced_service_name}" | sed -e "s/${namespace}\///g")
-
- kubectl get svc "${service_name}" -n "${namespace}" >/dev/null 2>&1
- local status=$?
-
- return $status
-}
-
-function gcp_cleanup() {
- if [[ ! $(command -v kubectl) ]]; then
- echoerr "kubectl executable not found"
- return 1
- fi
-
- if [[ -z "${REVIEW_APPS_GCP_REGION}" ]]; then
- echoerr "REVIEW_APPS_GCP_REGION is not set."
- return 1
- fi
-
- if [[ -z "${KUBE_NAMESPACE}" ]]; then
- echoerr "KUBE_NAMESPACE is not set."
- return 1
- fi
-
- if [[ -n "${DRY_RUN}" ]]; then
- echoinfo "Running in DRY_RUN"
- fi
-
- local target_pools_to_delete=()
- local firewall_rules_to_delete=()
- local forwarding_rules_to_delete=()
- local http_health_checks_to_delete=()
-
- for forwarding_rule in $(gcloud compute forwarding-rules list --filter="region:(${REVIEW_APPS_GCP_REGION})" --format "value(name)"); do
- echoinfo "Inspecting forwarding rule ${forwarding_rule}" true
-
- # We perform clean up when there is no more kubernetes service that require the resources.
- # To identify the kubernetes service using the resources,
- # we find the service name indicated in the forwarding rule description, e.g:
- #
- # $ gcloud compute forwarding-rules describe aff68b997da1211e984a042010af0019
- # # ...
- # description: '{"kubernetes.io/service-name":"review-apps-ee/review-winh-eslin-809vqz-nginx-ingress-controller"}'
- # # ...
- if forwarding_rule_k8s_service_exists "${forwarding_rule}"; then
- echoinfo "Skip clean up for ${forwarding_rule}"
- else
- echoinfo "Queuing forwarding rule, firewall rule, target pool and health check for ${forwarding_rule} to be cleaned up"
-
- firewall_rules_to_delete+=($(get_related_firewall_rules "${forwarding_rule}"))
- forwarding_rules_to_delete+=(${forwarding_rule})
- target_pools_to_delete+=(${forwarding_rule})
- http_health_checks_to_delete+=(${forwarding_rule})
- fi
- done
-
- delete_firewall_rules "${firewall_rules_to_delete[@]}"
- delete_forwarding_rules "${forwarding_rules_to_delete[@]}"
- delete_target_pools "${target_pools_to_delete[@]}"
- delete_http_health_checks "${http_health_checks_to_delete[@]}"
-}
diff --git a/scripts/rspec_helpers.sh b/scripts/rspec_helpers.sh
index cdf397ed72d..e0586b8efdf 100644
--- a/scripts/rspec_helpers.sh
+++ b/scripts/rspec_helpers.sh
@@ -248,9 +248,37 @@ function retry_failed_rspec_examples() {
# Merge the JUnit report from retry into the first-try report
junit_merge "${JUNIT_RETRY_FILE}" "${JUNIT_RESULT_FILE}" --update-only
+ if [[ $rspec_run_status -eq 0 ]]; then
+ # The test is flaky because it succeeded after being retried.
+ # Make the pipeline "pass with warnings" if the flaky test is part of this MR.
+ warn_on_successfully_retried_test
+ fi
+
exit $rspec_run_status
}
+# Exit with an allowed_failure exit code if the flaky test was part of the MR that triggered this pipeline
+function warn_on_successfully_retried_test {
+ local changed_files=$(git diff --name-only $CI_MERGE_REQUEST_TARGET_BRANCH_SHA | grep spec)
+ echoinfo "A test was flaky and succeeded after being retried. Checking to see if flaky test is part of this MR..."
+
+ if [[ "$changed_files" == "" ]]; then
+ echoinfo "Flaky test was not part of this MR."
+ return
+ fi
+
+ while read changed_file
+ do
+ echoinfo "Searching flaky tests for ${changed_file}"
+ if grep -q "$changed_file" "$RETRIED_TESTS_REPORT_PATH"; then
+ echoinfo "Exiting with code $SUCCESSFULLY_RETRIED_TEST_EXIT_CODE because the flaky test was part of this MR."
+ exit $SUCCESSFULLY_RETRIED_TEST_EXIT_CODE
+ fi
+ done <<< "$changed_files"
+
+ echoinfo "Flaky test was not part of this MR."
+}
+
function rspec_rerun_previous_failed_tests() {
local test_file_count_threshold=${RSPEC_PREVIOUS_FAILED_TEST_FILE_COUNT_THRESHOLD:-10}
local matching_tests_file=${1}
diff --git a/scripts/utils.sh b/scripts/utils.sh
index b9b40d3dbd5..c9e4a6a487d 100644
--- a/scripts/utils.sh
+++ b/scripts/utils.sh
@@ -218,3 +218,8 @@ function assets_image_tag() {
echo -n "${CI_COMMIT_SHA}"
fi
}
+
+function setup_gcloud() {
+ gcloud auth activate-service-account --key-file="${REVIEW_APPS_GCP_CREDENTIALS}"
+ gcloud config set project "${REVIEW_APPS_GCP_PROJECT}"
+}
diff --git a/spec/controllers/concerns/check_rate_limit_spec.rb b/spec/controllers/concerns/check_rate_limit_spec.rb
index 34ececfe639..75776acd520 100644
--- a/spec/controllers/concerns/check_rate_limit_spec.rb
+++ b/spec/controllers/concerns/check_rate_limit_spec.rb
@@ -19,11 +19,9 @@ RSpec.describe CheckRateLimit do
@current_user = current_user
end
- def redirect_back_or_default(**args)
- end
+ def redirect_back_or_default(**args); end
- def render(**args)
- end
+ def render(**args); end
end
end
diff --git a/spec/controllers/concerns/issuable_actions_spec.rb b/spec/controllers/concerns/issuable_actions_spec.rb
index 37d9dc080e1..34f47ed16f2 100644
--- a/spec/controllers/concerns/issuable_actions_spec.rb
+++ b/spec/controllers/concerns/issuable_actions_spec.rb
@@ -14,8 +14,7 @@ RSpec.describe IssuableActions do
klass = Class.new do
attr_reader :current_user, :project, :issuable
- def self.before_action(action = nil, params = nil)
- end
+ def self.before_action(action = nil, params = nil); end
include IssuableActions
@@ -40,8 +39,7 @@ RSpec.describe IssuableActions do
[]
end
- def render(options)
- end
+ def render(options); end
end
klass.new(issuable, project, user, finder_params_for_issuable)
diff --git a/spec/factories/packages/rpm/rpm_repository_files.rb b/spec/factories/packages/rpm/rpm_repository_files.rb
index 00755f49d98..7b86c593627 100644
--- a/spec/factories/packages/rpm/rpm_repository_files.rb
+++ b/spec/factories/packages/rpm/rpm_repository_files.rb
@@ -34,5 +34,9 @@ FactoryBot.define do
trait :pending_destruction do
status { :pending_destruction }
end
+
+ trait :filelists do
+ file_name { 'filelists.xml' }
+ end
end
end
diff --git a/spec/frontend/issues/show/components/incidents/edit_timeline_event_spec.js b/spec/frontend/issues/show/components/incidents/edit_timeline_event_spec.js
index 4c1638a9147..81c3c30bf8a 100644
--- a/spec/frontend/issues/show/components/incidents/edit_timeline_event_spec.js
+++ b/spec/frontend/issues/show/components/incidents/edit_timeline_event_spec.js
@@ -40,5 +40,13 @@ describe('Edit Timeline events', () => {
expect(wrapper.emitted()).toEqual(cancelEvent);
});
+
+ it('should emit the delete event', async () => {
+ const deleteEvent = { delete: [[]] };
+
+ await findTimelineEventsForm().vm.$emit('delete');
+
+ expect(wrapper.emitted()).toEqual(deleteEvent);
+ });
});
});
diff --git a/spec/frontend/issues/show/components/incidents/timeline_events_form_spec.js b/spec/frontend/issues/show/components/incidents/timeline_events_form_spec.js
index 0ce3f75f576..d5b199cc790 100644
--- a/spec/frontend/issues/show/components/incidents/timeline_events_form_spec.js
+++ b/spec/frontend/issues/show/components/incidents/timeline_events_form_spec.js
@@ -22,11 +22,12 @@ describe('Timeline events form', () => {
useFakeDate(fakeDate);
let wrapper;
- const mountComponent = ({ mountMethod = shallowMountExtended } = {}) => {
+ const mountComponent = ({ mountMethod = shallowMountExtended } = {}, props = {}) => {
wrapper = mountMethod(TimelineEventsForm, {
propsData: {
showSaveAndAdd: true,
isEventProcessed: false,
+ ...props,
},
stubs: {
GlButton: true,
@@ -43,6 +44,7 @@ describe('Timeline events form', () => {
const findSubmitButton = () => wrapper.findByText(timelineFormI18n.save);
const findSubmitAndAddButton = () => wrapper.findByText(timelineFormI18n.saveAndAdd);
const findCancelButton = () => wrapper.findByText(timelineFormI18n.cancel);
+ const findDeleteButton = () => wrapper.findByText(timelineFormI18n.delete);
const findDatePicker = () => wrapper.findComponent(GlDatepicker);
const findHourInput = () => wrapper.findByTestId('input-hours');
const findMinuteInput = () => wrapper.findByTestId('input-minutes');
@@ -68,6 +70,9 @@ describe('Timeline events form', () => {
findCancelButton().vm.$emit('click');
await waitForPromises();
};
+ const deleteForm = () => {
+ findDeleteButton().vm.$emit('click');
+ };
it('renders markdown-field component with correct list of toolbar items', () => {
mountComponent({ mountMethod: mountExtended });
@@ -165,4 +170,38 @@ describe('Timeline events form', () => {
expect(findSubmitAndAddButton().props('disabled')).toBe(true);
});
});
+
+ describe('Delete button', () => {
+ it('does not show the delete button if showDelete prop is false', () => {
+ mountComponent({ mountMethod: mountExtended }, { showDelete: false });
+
+ expect(findDeleteButton().exists()).toBe(false);
+ });
+
+ it('shows the delete button if showDelete prop is true', () => {
+ mountComponent({ mountMethod: mountExtended }, { showDelete: true });
+
+ expect(findDeleteButton().exists()).toBe(true);
+ });
+
+ it('disables the delete button if isEventProcessed prop is true', () => {
+ mountComponent({ mountMethod: mountExtended }, { showDelete: true, isEventProcessed: true });
+
+ expect(findDeleteButton().props('disabled')).toBe(true);
+ });
+
+ it('does not disable the delete button if isEventProcessed prop is false', () => {
+ mountComponent({ mountMethod: mountExtended }, { showDelete: true, isEventProcessed: false });
+
+ expect(findDeleteButton().props('disabled')).toBe(false);
+ });
+
+ it('emits delete event on click', () => {
+ mountComponent({ mountMethod: mountExtended }, { showDelete: true, isEventProcessed: true });
+
+ deleteForm();
+
+ expect(wrapper.emitted('delete')).toEqual([[]]);
+ });
+ });
});
diff --git a/spec/initializers/forbid_sidekiq_in_transactions_spec.rb b/spec/initializers/forbid_sidekiq_in_transactions_spec.rb
index a89ac73f6fa..7b1907a7451 100644
--- a/spec/initializers/forbid_sidekiq_in_transactions_spec.rb
+++ b/spec/initializers/forbid_sidekiq_in_transactions_spec.rb
@@ -34,8 +34,7 @@ RSpec.describe 'Sidekiq::Worker' do
Class.new do
include Sidekiq::Worker
- def perform
- end
+ def perform; end
end
end
@@ -47,8 +46,7 @@ RSpec.describe 'Sidekiq::Worker' do
context 'for mailers' do
let(:mailer_class) do
Class.new(ApplicationMailer) do
- def test_mail
- end
+ def test_mail; end
end
end
diff --git a/spec/lib/api/helpers/rate_limiter_spec.rb b/spec/lib/api/helpers/rate_limiter_spec.rb
index 2fed1cf3604..3640c7e30e7 100644
--- a/spec/lib/api/helpers/rate_limiter_spec.rb
+++ b/spec/lib/api/helpers/rate_limiter_spec.rb
@@ -19,8 +19,7 @@ RSpec.describe API::Helpers::RateLimiter do
@current_user = current_user
end
- def render_api_error!(**args)
- end
+ def render_api_error!(**args); end
end
end
diff --git a/spec/lib/gitlab/database/load_balancing/sidekiq_client_middleware_spec.rb b/spec/lib/gitlab/database/load_balancing/sidekiq_client_middleware_spec.rb
index b7915e6cf69..bd143c99d64 100644
--- a/spec/lib/gitlab/database/load_balancing/sidekiq_client_middleware_spec.rb
+++ b/spec/lib/gitlab/database/load_balancing/sidekiq_client_middleware_spec.rb
@@ -34,8 +34,7 @@ RSpec.describe Gitlab::Database::LoadBalancing::SidekiqClientMiddleware do
data_consistency data_consistency, feature_flag: feature_flag
- def perform(*args)
- end
+ def perform(*args); end
end
end
diff --git a/spec/lib/gitlab/database/load_balancing/sidekiq_server_middleware_spec.rb b/spec/lib/gitlab/database/load_balancing/sidekiq_server_middleware_spec.rb
index 8a9d4ba0b25..abf10456d0a 100644
--- a/spec/lib/gitlab/database/load_balancing/sidekiq_server_middleware_spec.rb
+++ b/spec/lib/gitlab/database/load_balancing/sidekiq_server_middleware_spec.rb
@@ -33,8 +33,7 @@ RSpec.describe Gitlab::Database::LoadBalancing::SidekiqServerMiddleware, :clean_
data_consistency data_consistency, feature_flag: feature_flag
- def perform(*args)
- end
+ def perform(*args); end
end
end
diff --git a/spec/lib/gitlab/database/migration_helpers/restrict_gitlab_schema_spec.rb b/spec/lib/gitlab/database/migration_helpers/restrict_gitlab_schema_spec.rb
index 4619ca21a13..e8045f5afec 100644
--- a/spec/lib/gitlab/database/migration_helpers/restrict_gitlab_schema_spec.rb
+++ b/spec/lib/gitlab/database/migration_helpers/restrict_gitlab_schema_spec.rb
@@ -185,8 +185,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers::RestrictGitlabSchema, query_a
execute("create schema __test_schema")
end
- def down
- end
+ def down; end
end,
query_matcher: /create schema __test_schema/,
expected: {
@@ -306,8 +305,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers::RestrictGitlabSchema, query_a
detached_partitions_class.create!(drop_after: Time.current, table_name: '_test_table')
end
- def down
- end
+ def down; end
def detached_partitions_class
Class.new(Gitlab::Database::Migration[2.0]::MigrationRecord) do
@@ -450,8 +448,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers::RestrictGitlabSchema, query_a
ApplicationSetting.last
end
- def down
- end
+ def down; end
end,
query_matcher: /FROM "application_settings"/,
expected: {
@@ -475,8 +472,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers::RestrictGitlabSchema, query_a
Feature.enabled?(:redis_hll_tracking, type: :ops)
end
- def down
- end
+ def down; end
end,
query_matcher: /FROM "features"/,
expected: {
@@ -505,8 +501,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers::RestrictGitlabSchema, query_a
end
end
- def down
- end
+ def down; end
end,
query_matcher: /FROM ci_builds/,
setup: -> (_) { skip_if_multiple_databases_not_setup },
diff --git a/spec/lib/gitlab/database/partitioning/sliding_list_strategy_spec.rb b/spec/lib/gitlab/database/partitioning/sliding_list_strategy_spec.rb
index 550f254c4da..e6014f81b74 100644
--- a/spec/lib/gitlab/database/partitioning/sliding_list_strategy_spec.rb
+++ b/spec/lib/gitlab/database/partitioning/sliding_list_strategy_spec.rb
@@ -229,11 +229,9 @@ RSpec.describe Gitlab::Database::Partitioning::SlidingListStrategy do
next_partition_if: method(:next_partition_if_wrapper),
detach_partition_if: method(:detach_partition_if_wrapper)
- def self.next_partition?(current_partition)
- end
+ def self.next_partition?(current_partition); end
- def self.detach_partition?(partition)
- end
+ def self.detach_partition?(partition); end
end
end
diff --git a/spec/lib/gitlab/database/postgresql_adapter/dump_schema_versions_mixin_spec.rb b/spec/lib/gitlab/database/postgresql_adapter/dump_schema_versions_mixin_spec.rb
index 8b06f068503..884c4f625dd 100644
--- a/spec/lib/gitlab/database/postgresql_adapter/dump_schema_versions_mixin_spec.rb
+++ b/spec/lib/gitlab/database/postgresql_adapter/dump_schema_versions_mixin_spec.rb
@@ -9,8 +9,7 @@ RSpec.describe Gitlab::Database::PostgresqlAdapter::DumpSchemaVersionsMixin do
original_dump_schema_information
end
- def original_dump_schema_information
- end
+ def original_dump_schema_information; end
end
klass.prepend(described_class)
diff --git a/spec/lib/gitlab/database/postgresql_database_tasks/load_schema_versions_mixin_spec.rb b/spec/lib/gitlab/database/postgresql_database_tasks/load_schema_versions_mixin_spec.rb
index 3e675a85999..3bb206c6627 100644
--- a/spec/lib/gitlab/database/postgresql_database_tasks/load_schema_versions_mixin_spec.rb
+++ b/spec/lib/gitlab/database/postgresql_database_tasks/load_schema_versions_mixin_spec.rb
@@ -9,8 +9,7 @@ RSpec.describe Gitlab::Database::PostgresqlDatabaseTasks::LoadSchemaVersionsMixi
original_structure_load
end
- def original_structure_load
- end
+ def original_structure_load; end
end
klass.prepend(described_class)
diff --git a/spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb b/spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb
index 524b373a5b7..960e2ed77fb 100644
--- a/spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb
+++ b/spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb
@@ -18,8 +18,7 @@ RSpec.describe Gitlab::Git::RuggedImpl::UseRugged do
klazz = Class.new do
include Gitlab::Git::RuggedImpl::UseRugged
- def rugged_test(ref, test_number)
- end
+ def rugged_test(ref, test_number); end
end
klazz.new
diff --git a/spec/lib/gitlab/repository_archive_rate_limiter_spec.rb b/spec/lib/gitlab/repository_archive_rate_limiter_spec.rb
index 49df70f3cb3..4599c647d5c 100644
--- a/spec/lib/gitlab/repository_archive_rate_limiter_spec.rb
+++ b/spec/lib/gitlab/repository_archive_rate_limiter_spec.rb
@@ -7,8 +7,7 @@ RSpec.describe ::Gitlab::RepositoryArchiveRateLimiter do
Class.new do
include ::Gitlab::RepositoryArchiveRateLimiter
- def check_rate_limit!(**args)
- end
+ def check_rate_limit!(**args); end
end
end
diff --git a/spec/lib/gitlab/repository_cache_adapter_spec.rb b/spec/lib/gitlab/repository_cache_adapter_spec.rb
index d14c3f44c6f..71a20cc58fd 100644
--- a/spec/lib/gitlab/repository_cache_adapter_spec.rb
+++ b/spec/lib/gitlab/repository_cache_adapter_spec.rb
@@ -27,8 +27,7 @@ RSpec.describe Gitlab::RepositoryCacheAdapter do
'foo/bar'
end
- def project
- end
+ def project; end
def cached_methods
[:letters]
diff --git a/spec/lib/gitlab/sidekiq_middleware/client_metrics_spec.rb b/spec/lib/gitlab/sidekiq_middleware/client_metrics_spec.rb
index dca00c85e30..472591bde5e 100644
--- a/spec/lib/gitlab/sidekiq_middleware/client_metrics_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/client_metrics_spec.rb
@@ -40,8 +40,7 @@ RSpec.describe Gitlab::SidekiqMiddleware::ClientMetrics do
TestWorker.class_eval do
include Sidekiq::Worker
- def perform(*args)
- end
+ def perform(*args); end
end
allow(Gitlab::Metrics).to receive(:counter).and_return(Gitlab::Metrics::NullMetric.instance)
diff --git a/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/client_spec.rb b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/client_spec.rb
index 44c8df73463..14eb568b974 100644
--- a/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/client_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/client_spec.rb
@@ -17,8 +17,7 @@ RSpec.describe Gitlab::SidekiqMiddleware::DuplicateJobs::Client, :clean_gitlab_r
include ApplicationWorker
- def perform(*args)
- end
+ def perform(*args); end
end
end
diff --git a/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/server_spec.rb b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/server_spec.rb
index 09548d21106..1b01793d80d 100644
--- a/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/server_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/server_spec.rb
@@ -18,8 +18,7 @@ RSpec.describe Gitlab::SidekiqMiddleware::DuplicateJobs::Server, :clean_gitlab_r
self.class.work
end
- def self.work
- end
+ def self.work; end
end
end
diff --git a/spec/lib/gitlab/sidekiq_middleware/instrumentation_logger_spec.rb b/spec/lib/gitlab/sidekiq_middleware/instrumentation_logger_spec.rb
index 8cf65e1be5b..cfb2c7ab5c3 100644
--- a/spec/lib/gitlab/sidekiq_middleware/instrumentation_logger_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/instrumentation_logger_spec.rb
@@ -13,8 +13,7 @@ RSpec.describe Gitlab::SidekiqMiddleware::InstrumentationLogger do
include ApplicationWorker
- def perform(*args)
- end
+ def perform(*args); end
end
end
diff --git a/spec/lib/gitlab/sidekiq_middleware/query_analyzer_spec.rb b/spec/lib/gitlab/sidekiq_middleware/query_analyzer_spec.rb
index e58af1d60fe..c31f05f00e4 100644
--- a/spec/lib/gitlab/sidekiq_middleware/query_analyzer_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/query_analyzer_spec.rb
@@ -10,8 +10,7 @@ RSpec.describe Gitlab::SidekiqMiddleware::QueryAnalyzer, query_analyzers: false
let(:queue) { 'some-queue' }
let(:middleware) { described_class.new }
- def do_queries
- end
+ def do_queries; end
subject { middleware.call(worker, job, queue) { do_queries } }
diff --git a/spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb b/spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb
index 1a53a9b8701..f7cee6beb58 100644
--- a/spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb
@@ -231,8 +231,7 @@ RSpec.describe Gitlab::SidekiqMiddleware::ServerMetrics do
include Sidekiq::Worker
include WorkerAttributes
- def perform(*args)
- end
+ def perform(*args); end
end
allow(::Gitlab::Database::LoadBalancing).to receive_message_chain(:proxy, :load_balancer).and_return(load_balancer)
@@ -306,8 +305,7 @@ RSpec.describe Gitlab::SidekiqMiddleware::ServerMetrics do
feature_category :not_owned
end
- def perform
- end
+ def perform; end
end
end
diff --git a/spec/lib/gitlab/sidekiq_middleware/worker_context/client_spec.rb b/spec/lib/gitlab/sidekiq_middleware/worker_context/client_spec.rb
index 821d8b8fe7b..1b6cd7ac5fb 100644
--- a/spec/lib/gitlab/sidekiq_middleware/worker_context/client_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/worker_context/client_spec.rb
@@ -17,8 +17,7 @@ RSpec.describe Gitlab::SidekiqMiddleware::WorkerContext::Client do
jobs.find { |job| job['args'] == args }
end
- def perform(*args)
- end
+ def perform(*args); end
end
end
@@ -38,8 +37,7 @@ RSpec.describe Gitlab::SidekiqMiddleware::WorkerContext::Client do
'TestMailer'
end
- def test_mail
- end
+ def test_mail; end
end
end
diff --git a/spec/lib/gitlab/sidekiq_middleware/worker_context/server_spec.rb b/spec/lib/gitlab/sidekiq_middleware/worker_context/server_spec.rb
index 05b328e55d3..2deab3064eb 100644
--- a/spec/lib/gitlab/sidekiq_middleware/worker_context/server_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/worker_context/server_spec.rb
@@ -41,8 +41,7 @@ RSpec.describe Gitlab::SidekiqMiddleware::WorkerContext::Server do
include Sidekiq::Worker
- def perform
- end
+ def perform; end
end
end
diff --git a/spec/lib/gitlab/ssh_public_key_spec.rb b/spec/lib/gitlab/ssh_public_key_spec.rb
index a2524314458..d4b0b1ea53b 100644
--- a/spec/lib/gitlab/ssh_public_key_spec.rb
+++ b/spec/lib/gitlab/ssh_public_key_spec.rb
@@ -260,8 +260,7 @@ RSpec.describe Gitlab::SSHPublicKey, lib: true, fips_mode: false do
context 'when the key is represented by a subclass of the class that is in the list of supported technologies' do
it 'raises error' do
rsa_subclass = Class.new(described_class.technology(:rsa).key_class) do
- def initialize
- end
+ def initialize; end
end
key = rsa_subclass.new
diff --git a/spec/lib/gitlab/utils/delegator_override/validator_spec.rb b/spec/lib/gitlab/utils/delegator_override/validator_spec.rb
index a58bc65c708..4fcf01ea256 100644
--- a/spec/lib/gitlab/utils/delegator_override/validator_spec.rb
+++ b/spec/lib/gitlab/utils/delegator_override/validator_spec.rb
@@ -7,8 +7,7 @@ RSpec.describe Gitlab::Utils::DelegatorOverride::Validator do
Class.new(::SimpleDelegator) do
extend(::Gitlab::Utils::DelegatorOverride)
- def foo
- end
+ def foo; end
end.prepend(ee_delegator_extension)
end
@@ -16,18 +15,15 @@ RSpec.describe Gitlab::Utils::DelegatorOverride::Validator do
Module.new do
extend(::Gitlab::Utils::DelegatorOverride)
- def bar
- end
+ def bar; end
end
end
let(:target_class) do
Class.new do
- def foo
- end
+ def foo; end
- def bar
- end
+ def bar; end
end
end
diff --git a/spec/lib/gitlab/utils/delegator_override_spec.rb b/spec/lib/gitlab/utils/delegator_override_spec.rb
index 2dafa75e344..b566b7a2cad 100644
--- a/spec/lib/gitlab/utils/delegator_override_spec.rb
+++ b/spec/lib/gitlab/utils/delegator_override_spec.rb
@@ -7,25 +7,21 @@ RSpec.describe Gitlab::Utils::DelegatorOverride do
Class.new(::SimpleDelegator) do
extend(::Gitlab::Utils::DelegatorOverride)
- def foo
- end
+ def foo; end
end
end
let(:target_class) do
Class.new do
- def foo
- end
+ def foo; end
- def bar
- end
+ def bar; end
end
end
let(:dummy_module) do
Module.new do
- def foobar
- end
+ def foobar; end
end
end
diff --git a/spec/lib/gitlab/utils/override_spec.rb b/spec/lib/gitlab/utils/override_spec.rb
index a5e53c1dfc1..63f7b1623d8 100644
--- a/spec/lib/gitlab/utils/override_spec.rb
+++ b/spec/lib/gitlab/utils/override_spec.rb
@@ -35,8 +35,7 @@ RSpec.describe Gitlab::Utils::Override do
override :good
if bad_arity
- def good(num)
- end
+ def good(num); end
elsif negative_arity
def good(*args)
super.succ
diff --git a/spec/lib/gitlab/utils/strong_memoize_spec.rb b/spec/lib/gitlab/utils/strong_memoize_spec.rb
index 6a09a8da58e..2b925d90369 100644
--- a/spec/lib/gitlab/utils/strong_memoize_spec.rb
+++ b/spec/lib/gitlab/utils/strong_memoize_spec.rb
@@ -59,22 +59,19 @@ RSpec.describe Gitlab::Utils::StrongMemoize do
protected
- def private_method
- end
+ def private_method; end
private :private_method
strong_memoize_attr :private_method
public
- def protected_method
- end
+ def protected_method; end
protected :protected_method
strong_memoize_attr :protected_method
private
- def public_method
- end
+ def public_method; end
public :public_method
strong_memoize_attr :public_method
end
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index 3589c1156a3..51ac7adc019 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -5632,4 +5632,28 @@ RSpec.describe Ci::Build do
expect(runtime_hooks[0].script).to eq(["echo 'hello pre_get_sources_script'"])
end
end
+
+ describe 'partitioning', :ci_partitionable do
+ include Ci::PartitioningHelpers
+
+ let(:new_pipeline) { create(:ci_pipeline) }
+ let(:ci_build) { FactoryBot.build(:ci_build, pipeline: new_pipeline) }
+
+ before do
+ stub_current_partition_id
+ end
+
+ it 'assigns partition_id to job variables successfully', :aggregate_failures do
+ ci_build.job_variables_attributes = [
+ { key: 'TEST_KEY', value: 'new value' },
+ { key: 'NEW_KEY', value: 'exciting new value' }
+ ]
+
+ ci_build.save!
+
+ expect(ci_build.job_variables.count).to eq(2)
+ expect(ci_build.job_variables.first.partition_id).to eq(ci_testing_partition_id)
+ expect(ci_build.job_variables.second.partition_id).to eq(ci_testing_partition_id)
+ end
+ end
end
diff --git a/spec/models/ci/job_variable_spec.rb b/spec/models/ci/job_variable_spec.rb
index 4aebd3283f0..0a65708160a 100644
--- a/spec/models/ci/job_variable_spec.rb
+++ b/spec/models/ci/job_variable_spec.rb
@@ -2,11 +2,63 @@
require 'spec_helper'
-RSpec.describe Ci::JobVariable do
- subject { build(:ci_job_variable) }
-
+RSpec.describe Ci::JobVariable, feature_category: :continuous_integration do
it_behaves_like "CI variable"
- it { is_expected.to belong_to(:job) }
- it { is_expected.to validate_uniqueness_of(:key).scoped_to(:job_id) }
+ describe 'associations' do
+ let!(:job_variable) { create(:ci_job_variable) }
+
+ it { is_expected.to belong_to(:job) }
+ it { is_expected.to validate_uniqueness_of(:key).scoped_to(:job_id) }
+ end
+
+ describe 'partitioning' do
+ let(:job_variable) { build(:ci_job_variable, job: ci_build) }
+
+ context 'with build' do
+ let(:ci_build) { build(:ci_build, partition_id: ci_testing_partition_id) }
+
+ it 'copies the partition_id from build' do
+ expect { job_variable.valid? }.to change { job_variable.partition_id }.to(ci_testing_partition_id)
+ end
+
+ context 'when it is already set' do
+ let(:job_variable) { build(:ci_job_variable, partition_id: 125) }
+
+ it 'does not change the partition_id value' do
+ expect { job_variable.valid? }.not_to change { job_variable.partition_id }
+ end
+ end
+ end
+
+ context 'without build' do
+ subject(:job_variable) { build(:ci_job_variable, job: nil, partition_id: 125) }
+
+ it { is_expected.to validate_presence_of(:partition_id) }
+
+ it 'does not change the partition_id value' do
+ expect { job_variable.valid? }.not_to change { job_variable.partition_id }
+ end
+ end
+
+ context 'when using bulk_insert', :ci_partitionable do
+ include Ci::PartitioningHelpers
+
+ let(:new_pipeline) { create(:ci_pipeline) }
+ let(:ci_build) { create(:ci_build, pipeline: new_pipeline) }
+ let(:job_variable_2) { build(:ci_job_variable, job: ci_build) }
+
+ before do
+ stub_current_partition_id
+ end
+
+ it 'creates job variables successfully', :aggregate_failures do
+ described_class.bulk_insert!([job_variable, job_variable_2])
+
+ expect(described_class.count).to eq(2)
+ expect(described_class.first.partition_id).to eq(ci_testing_partition_id)
+ expect(described_class.last.partition_id).to eq(ci_testing_partition_id)
+ end
+ end
+ end
end
diff --git a/spec/models/packages/rpm/repository_file_spec.rb b/spec/models/packages/rpm/repository_file_spec.rb
index 34347793dd8..1147fd66ac6 100644
--- a/spec/models/packages/rpm/repository_file_spec.rb
+++ b/spec/models/packages/rpm/repository_file_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-RSpec.describe Packages::Rpm::RepositoryFile, type: :model do
+RSpec.describe Packages::Rpm::RepositoryFile, type: :model, feature_category: :package_registry do
using RSpec::Parameterized::TableSyntax
let_it_be(:repository_file) { create(:rpm_repository_file) }
@@ -16,6 +16,32 @@ RSpec.describe Packages::Rpm::RepositoryFile, type: :model do
it { is_expected.to validate_presence_of(:project) }
end
+ describe '.has_oversized_filelists?' do
+ let_it_be(:filelists) { create(:rpm_repository_file, :filelists, size: 21.megabytes) }
+
+ subject { described_class.has_oversized_filelists?(project_id: filelists.project_id) }
+
+ context 'when has oversized filelists' do
+ it { expect(subject).to be true }
+ end
+
+ context 'when filelists.xml is not oversized' do
+ before do
+ filelists.update!(size: 19.megabytes)
+ end
+
+ it { expect(subject).to be_falsey }
+ end
+
+ context 'when there is no filelists.xml' do
+ before do
+ filelists.destroy!
+ end
+
+ it { expect(subject).to be_falsey }
+ end
+ end
+
context 'when updating project statistics' do
context 'when the package file has an explicit size' do
it_behaves_like 'UpdateProjectStatistics' do
diff --git a/spec/requests/api/rpm_project_packages_spec.rb b/spec/requests/api/rpm_project_packages_spec.rb
index f367ad8c59f..19247c30333 100644
--- a/spec/requests/api/rpm_project_packages_spec.rb
+++ b/spec/requests/api/rpm_project_packages_spec.rb
@@ -216,6 +216,19 @@ RSpec.describe API::RpmProjectPackages do
expect(response.body).to match(/File is too large/)
end
end
+
+ context 'when filelists.xml file size too large' do
+ before do
+ create(:rpm_repository_file, :filelists, size: 21.megabytes, project: project)
+ end
+
+ it 'returns an error' do
+ upload_file(params: { file: file_upload }, request_headers: headers)
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(response.body).to match(/Repository packages limit exceeded/)
+ end
+ end
end
def upload_file(params: {}, request_headers: headers)