diff options
| author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-01-21 18:12:45 +0000 |
|---|---|---|
| committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-01-21 18:12:45 +0000 |
| commit | 6be1f63eb6ca987f959c18576bb9042b9ee7726b (patch) | |
| tree | cb15e23f3087776b54c4190d9aec4aa0c507ffb5 | |
| parent | 48fc1ad8991a96ef2eaa927bb6df3bfab2c78e46 (diff) | |
| download | gitlab-ce-6be1f63eb6ca987f959c18576bb9042b9ee7726b.tar.gz | |
Add latest changes from gitlab-org/gitlab@master
104 files changed, 934 insertions, 553 deletions
diff --git a/app/assets/javascripts/cycle_analytics/components/value_stream_metrics.vue b/app/assets/javascripts/cycle_analytics/components/value_stream_metrics.vue index b090c245559..16446c61102 100644 --- a/app/assets/javascripts/cycle_analytics/components/value_stream_metrics.vue +++ b/app/assets/javascripts/cycle_analytics/components/value_stream_metrics.vue @@ -86,6 +86,10 @@ export default { redirectTo(links[0].url); } }, + getDecimalPlaces(value) { + const parsedFloat = parseFloat(value); + return Number.isNaN(parsedFloat) || Number.isInteger(parsedFloat) ? 0 : 1; + }, }, }; </script> @@ -104,7 +108,7 @@ export default { :title="metric.label" :unit="metric.unit || ''" :should-animate="true" - :animation-decimal-places="1" + :animation-decimal-places="getDecimalPlaces(metric.value)" :class="{ 'gl-hover-cursor-pointer': hasLinks(metric.links) }" tabindex="0" @click="clickHandler(metric)" diff --git a/app/assets/javascripts/environments/components/deployment.vue b/app/assets/javascripts/environments/components/deployment.vue index 62dc46251c4..77999bbe05b 100644 --- a/app/assets/javascripts/environments/components/deployment.vue +++ b/app/assets/javascripts/environments/components/deployment.vue @@ -1,10 +1,12 @@ <script> import { GlBadge, GlIcon, GlTooltipDirective as GlTooltip } from '@gitlab/ui'; -import { s__ } from '~/locale'; +import { __, s__ } from '~/locale'; +import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; import DeploymentStatusBadge from './deployment_status_badge.vue'; export default { components: { + ClipboardButton, DeploymentStatusBadge, GlBadge, GlIcon, @@ -30,15 +32,20 @@ export default { iid() { return this.deployment?.iid; }, + shortSha() { + return this.deployment?.commit?.shortId; + }, }, i18n: { latestBadge: s__('Deployment|Latest Deployed'), deploymentId: s__('Deployment|Deployment ID'), + copyButton: __('Copy commit SHA'), + commitSha: __('Commit SHA'), }, }; </script> <template> - <div class="gl-display-flex gl-align-items-center gl-gap-x-3"> + <div class="gl-display-flex gl-align-items-center gl-gap-x-3 gl-font-sm gl-text-gray-700"> <deployment-status-badge v-if="status" :status="status" /> <gl-badge v-if="latest" variant="info">{{ $options.i18n.latestBadge }}</gl-badge> <div @@ -47,7 +54,21 @@ export default { :title="$options.i18n.deploymentId" :aria-label="$options.i18n.deploymentId" > - <gl-icon ref="deployment-iid-icon" name="deployments" /> {{ iid }} + <gl-icon ref="deployment-iid-icon" name="deployments" /> #{{ iid }} + </div> + <div + v-if="shortSha" + data-testid="deployment-commit-sha" + class="gl-font-monospace gl-display-flex gl-align-items-center" + > + <gl-icon ref="deployment-commit-icon" name="commit" class="gl-mr-2" /> + <span v-gl-tooltip :title="$options.i18n.commitSha">{{ shortSha }}</span> + <clipboard-button + :text="shortSha" + category="tertiary" + :title="$options.i18n.copyButton" + size="small" + /> </div> </div> </template> diff --git a/app/assets/javascripts/pipelines/components/notification/deprecated_type_keyword_notification.vue b/app/assets/javascripts/pipelines/components/notification/deprecated_type_keyword_notification.vue new file mode 100644 index 00000000000..b8f9f84c217 --- /dev/null +++ b/app/assets/javascripts/pipelines/components/notification/deprecated_type_keyword_notification.vue @@ -0,0 +1,102 @@ +<script> +import { GlAlert, GlLink, GlSprintf } from '@gitlab/ui'; +import { __ } from '~/locale'; +import getPipelineWarnings from '../../graphql/queries/get_pipeline_warnings.query.graphql'; + +export default { + // eslint-disable-next-line @gitlab/require-i18n-strings + expectedMessage: 'will be removed in', + i18n: { + title: __('Found warning in your .gitlab-ci.yml'), + rootTypesWarning: __( + '%{codeStart}types%{codeEnd} is deprecated and will be removed in 15.0. Use %{codeStart}stages%{codeEnd} instead. %{linkStart}Learn More %{linkEnd}', + ), + typeWarning: __( + '%{codeStart}type%{codeEnd} is deprecated and will be removed in 15.0. Use %{codeStart}stage%{codeEnd} instead. %{linkStart}Learn More %{linkEnd}', + ), + }, + components: { + GlAlert, + GlLink, + GlSprintf, + }, + inject: ['deprecatedKeywordsDocPath', 'fullPath', 'pipelineIid'], + apollo: { + warnings: { + query: getPipelineWarnings, + variables() { + return { + fullPath: this.fullPath, + iid: this.pipelineIid, + }; + }, + update(data) { + return data?.project?.pipeline?.warningMessages || []; + }, + error() { + this.hasError = true; + }, + }, + }, + data() { + return { + warnings: [], + hasError: false, + }; + }, + computed: { + deprecationWarnings() { + return this.warnings.filter(({ content }) => { + return content.includes(this.$options.expectedMessage); + }); + }, + formattedWarnings() { + // The API doesn't have a mechanism currently to return a + // type instead of just the error message. To work around this, + // we check if the deprecation message is found within the warnings + // and show a FE version of that message with the link to the documentation + // and translated. We can have only 2 types of warnings: root types and individual + // type. If the word `root` is present, then we know it's the root type deprecation + // and if not, it's the normal type. This has to be deleted in 15.0. + // Follow-up issue: https://gitlab.com/gitlab-org/gitlab/-/issues/350810 + return this.deprecationWarnings.map(({ content }) => { + if (content.includes('root')) { + return this.$options.i18n.rootTypesWarning; + } + return this.$options.i18n.typeWarning; + }); + }, + hasDeprecationWarning() { + return this.formattedWarnings.length > 0; + }, + showWarning() { + return ( + !this.$apollo.queries.warnings?.loading && !this.hasError && this.hasDeprecationWarning + ); + }, + }, +}; +</script> +<template> + <div> + <gl-alert + v-if="showWarning" + :title="$options.i18n.title" + variant="warning" + :dismissible="false" + > + <ul class="gl-mb-0"> + <li v-for="warning in formattedWarnings" :key="warning"> + <gl-sprintf :message="warning"> + <template #code="{ content }"> + <code> {{ content }}</code> + </template> + <template #link="{ content }"> + <gl-link :href="deprecatedKeywordsDocPath" target="_blank"> {{ content }}</gl-link> + </template> + </gl-sprintf> + </li> + </ul> + </gl-alert> + </div> +</template> diff --git a/app/assets/javascripts/pipelines/graphql/queries/get_pipeline_warnings.query.graphql b/app/assets/javascripts/pipelines/graphql/queries/get_pipeline_warnings.query.graphql new file mode 100644 index 00000000000..cd1d2b62a3d --- /dev/null +++ b/app/assets/javascripts/pipelines/graphql/queries/get_pipeline_warnings.query.graphql @@ -0,0 +1,12 @@ +query getPipelineWarnings($fullPath: ID!, $iid: ID!) { + project(fullPath: $fullPath) { + id + pipeline(iid: $iid) { + id + warningMessages { + content + id + } + } + } +} diff --git a/app/assets/javascripts/pipelines/pipeline_details_bundle.js b/app/assets/javascripts/pipelines/pipeline_details_bundle.js index ae8b2503c79..bfb95e5ab0c 100644 --- a/app/assets/javascripts/pipelines/pipeline_details_bundle.js +++ b/app/assets/javascripts/pipelines/pipeline_details_bundle.js @@ -3,6 +3,7 @@ import { __ } from '~/locale'; import createDagApp from './pipeline_details_dag'; import { createPipelinesDetailApp } from './pipeline_details_graph'; import { createPipelineHeaderApp } from './pipeline_details_header'; +import { createPipelineNotificationApp } from './pipeline_details_notification'; import { createPipelineJobsApp } from './pipeline_details_jobs'; import { apolloProvider } from './pipeline_shared_client'; import { createTestDetails } from './pipeline_test_details'; @@ -11,6 +12,7 @@ const SELECTORS = { PIPELINE_DETAILS: '.js-pipeline-details-vue', PIPELINE_GRAPH: '#js-pipeline-graph-vue', PIPELINE_HEADER: '#js-pipeline-header-vue', + PIPELINE_NOTIFICATION: '#js-pipeline-notification', PIPELINE_TESTS: '#js-pipeline-tests-detail', PIPELINE_JOBS: '#js-pipeline-jobs-vue', }; @@ -43,6 +45,14 @@ export default async function initPipelineDetailsBundle() { } try { + createPipelineNotificationApp(SELECTORS.PIPELINE_NOTIFICATION, apolloProvider); + } catch { + createFlash({ + message: __('An error occurred while loading a section of this page.'), + }); + } + + try { createDagApp(apolloProvider); } catch { createFlash({ diff --git a/app/assets/javascripts/pipelines/pipeline_details_notification.js b/app/assets/javascripts/pipelines/pipeline_details_notification.js new file mode 100644 index 00000000000..0061be843c5 --- /dev/null +++ b/app/assets/javascripts/pipelines/pipeline_details_notification.js @@ -0,0 +1,31 @@ +import Vue from 'vue'; +import VueApollo from 'vue-apollo'; +import DeprecatedKeywordNotification from './components/notification/deprecated_type_keyword_notification.vue'; + +Vue.use(VueApollo); + +export const createPipelineNotificationApp = (elSelector, apolloProvider) => { + const el = document.querySelector(elSelector); + + if (!el) { + return; + } + + const { deprecatedKeywordsDocPath, fullPath, pipelineIid } = el?.dataset; + // eslint-disable-next-line no-new + new Vue({ + el, + components: { + DeprecatedKeywordNotification, + }, + provide: { + deprecatedKeywordsDocPath, + fullPath, + pipelineIid, + }, + apolloProvider, + render(createElement) { + return createElement('deprecated-keyword-notification'); + }, + }); +}; diff --git a/app/assets/stylesheets/utilities.scss b/app/assets/stylesheets/utilities.scss index 0e7e52129b4..bb1e3035fc3 100644 --- a/app/assets/stylesheets/utilities.scss +++ b/app/assets/stylesheets/utilities.scss @@ -298,13 +298,6 @@ $gl-line-height-42: px-to-rem(42px); @include gl-focus($gl-border-size-1, $gray-900, true); } -// Will be moved to @gitlab/ui by https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1637 -.gl-lg-w-25p { - @include gl-media-breakpoint-up(lg) { - width: 25%; - } -} - // Will be moved to @gitlab/ui by https://gitlab.com/gitlab-org/gitlab-ui/-/merge_requests/2600 .gl-pr-10 { padding-right: $gl-spacing-scale-10; diff --git a/app/models/integrations/chat_message/base_message.rb b/app/models/integrations/chat_message/base_message.rb index ab213f4b43f..554b422c0fa 100644 --- a/app/models/integrations/chat_message/base_message.rb +++ b/app/models/integrations/chat_message/base_message.rb @@ -47,16 +47,21 @@ module Integrations format(message) end + # NOTE: Make sure to call `#strip_markup` on any untrusted user input that's added to the + # `title`, `subtitle`, `text`, `fallback`, or `author_name` fields. def attachments raise NotImplementedError end + # NOTE: Make sure to call `#strip_markup` on any untrusted user input that's added to the + # `title`, `subtitle`, `text`, `fallback`, or `author_name` fields. def activity raise NotImplementedError end private + # NOTE: Make sure to call `#strip_markup` on any untrusted user input that's added to the string. def message raise NotImplementedError end diff --git a/app/services/alert_management/alerts/update_service.rb b/app/services/alert_management/alerts/update_service.rb index 7a9bcf2a52d..6bac1d027e6 100644 --- a/app/services/alert_management/alerts/update_service.rb +++ b/app/services/alert_management/alerts/update_service.rb @@ -12,6 +12,7 @@ module AlertManagement @alert = alert @param_errors = [] @status = params.delete(:status) + @status_change_reason = params.delete(:status_change_reason) super(project: alert.project, current_user: current_user, params: params) end @@ -36,7 +37,7 @@ module AlertManagement private - attr_reader :alert, :param_errors, :status + attr_reader :alert, :param_errors, :status, :status_change_reason def allowed? current_user&.can?(:update_alert_management_alert, alert) @@ -133,7 +134,7 @@ module AlertManagement end def add_status_change_system_note - SystemNoteService.change_alert_status(alert, current_user) + SystemNoteService.change_alert_status(alert, current_user, status_change_reason) end def resolve_todos @@ -144,7 +145,12 @@ module AlertManagement ::Issues::UpdateService.new( project: project, current_user: current_user, - params: { escalation_status: { status: status } } + params: { + escalation_status: { + status: status, + status_change_reason: " by changing the status of #{alert.to_reference(project)}" + } + } ).execute(alert.issue) end diff --git a/app/services/incident_management/issuable_escalation_statuses/after_update_service.rb b/app/services/incident_management/issuable_escalation_statuses/after_update_service.rb index a7a99f88b32..b7f8b268f18 100644 --- a/app/services/incident_management/issuable_escalation_statuses/after_update_service.rb +++ b/app/services/incident_management/issuable_escalation_statuses/after_update_service.rb @@ -3,12 +3,12 @@ module IncidentManagement module IssuableEscalationStatuses class AfterUpdateService < ::BaseProjectService - def initialize(issuable, current_user) + def initialize(issuable, current_user, **params) @issuable = issuable @escalation_status = issuable.escalation_status @alert = issuable.alert_management_alert - super(project: issuable.project, current_user: current_user) + super(project: issuable.project, current_user: current_user, params: params) end def execute @@ -22,19 +22,27 @@ module IncidentManagement attr_reader :issuable, :escalation_status, :alert def after_update - sync_to_alert + sync_status_to_alert + add_status_system_note end - def sync_to_alert + def sync_status_to_alert return unless alert return if alert.status == escalation_status.status ::AlertManagement::Alerts::UpdateService.new( alert, current_user, - status: escalation_status.status_name + status: escalation_status.status_name, + status_change_reason: " by changing the incident status of #{issuable.to_reference(project)}" ).execute end + + def add_status_system_note + return unless escalation_status.status_previously_changed? + + SystemNoteService.change_incident_status(issuable, current_user, params[:status_change_reason]) + end end end end diff --git a/app/services/incident_management/issuable_escalation_statuses/prepare_update_service.rb b/app/services/incident_management/issuable_escalation_statuses/prepare_update_service.rb index 1a660e1a163..319d1e8f899 100644 --- a/app/services/incident_management/issuable_escalation_statuses/prepare_update_service.rb +++ b/app/services/incident_management/issuable_escalation_statuses/prepare_update_service.rb @@ -5,7 +5,7 @@ module IncidentManagement class PrepareUpdateService include Gitlab::Utils::StrongMemoize - SUPPORTED_PARAMS = %i[status].freeze + SUPPORTED_PARAMS = %i[status status_change_reason].freeze InvalidParamError = Class.new(StandardError) diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb index ecf10cf97a8..79921186a87 100644 --- a/app/services/issuable_base_service.rb +++ b/app/services/issuable_base_service.rb @@ -162,6 +162,8 @@ class IssuableBaseService < ::BaseProjectService return unless result.success? && result.payload.present? + @escalation_status_change_reason = result[:escalation_status].delete(:status_change_reason) + params[:incident_management_issuable_escalation_status_attributes] = result[:escalation_status] end diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb index aecb22453b7..c024cef2f82 100644 --- a/app/services/issues/update_service.rb +++ b/app/services/issues/update_service.rb @@ -214,7 +214,11 @@ module Issues return unless old_escalation_status.present? return if issue.escalation_status&.slice(:status, :policy_id) == old_escalation_status - ::IncidentManagement::IssuableEscalationStatuses::AfterUpdateService.new(issue, current_user).execute + ::IncidentManagement::IssuableEscalationStatuses::AfterUpdateService.new( + issue, + current_user, + status_change_reason: @escalation_status_change_reason # Defined in IssuableBaseService before save + ).execute end # rubocop: disable CodeReuse/ActiveRecord diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb index 0d13c73d49d..1c85955ce90 100644 --- a/app/services/system_note_service.rb +++ b/app/services/system_note_service.rb @@ -319,8 +319,8 @@ module SystemNoteService merge_requests_service(noteable, noteable.project, user).unapprove_mr end - def change_alert_status(alert, author) - ::SystemNotes::AlertManagementService.new(noteable: alert, project: alert.project, author: author).change_alert_status(alert) + def change_alert_status(alert, author, reason = nil) + ::SystemNotes::AlertManagementService.new(noteable: alert, project: alert.project, author: author).change_alert_status(reason) end def new_alert_issue(alert, issue, author) @@ -339,6 +339,10 @@ module SystemNoteService ::SystemNotes::IncidentService.new(noteable: incident, project: incident.project, author: author).resolve_incident_status end + def change_incident_status(incident, author, reason = nil) + ::SystemNotes::IncidentService.new(noteable: incident, project: incident.project, author: author).change_incident_status(reason) + end + def log_resolving_alert(alert, monitoring_tool) ::SystemNotes::AlertManagementService.new(noteable: alert, project: alert.project).log_resolving_alert(monitoring_tool) end diff --git a/app/services/system_notes/alert_management_service.rb b/app/services/system_notes/alert_management_service.rb index 70cdd5c6434..5e20b4be9a5 100644 --- a/app/services/system_notes/alert_management_service.rb +++ b/app/services/system_notes/alert_management_service.rb @@ -24,11 +24,12 @@ module SystemNotes # Example Note text: # # "changed the status to Acknowledged" + # "changed the status to Acknowledged by changing the incident status of #540" # # Returns the created Note object - def change_alert_status(alert) - status = alert.state.to_s.titleize - body = "changed the status to **#{status}**" + def change_alert_status(reason) + status = noteable.state.to_s.titleize + body = "changed the status to **#{status}**#{reason}" create_note(NoteSummary.new(noteable, project, author, body, action: 'status')) end diff --git a/app/services/system_notes/incident_service.rb b/app/services/system_notes/incident_service.rb index 785291e0637..6d1c10964b3 100644 --- a/app/services/system_notes/incident_service.rb +++ b/app/services/system_notes/incident_service.rb @@ -31,5 +31,22 @@ module SystemNotes create_note(NoteSummary.new(noteable, project, author, body, action: 'status')) end + + # Called when the status of an IncidentManagement::IssuableEscalationStatus has changed + # + # reason - String. + # + # Example Note text: + # + # "changed the incident status to Acknowledged" + # "changed the incident status to Acknowledged by changing the status of ^alert#540" + # + # Returns the created Note object + def change_incident_status(reason) + status = noteable.escalation_status.status_name.to_s.titleize + body = "changed the incident status to **#{status}**#{reason}" + + create_note(NoteSummary.new(noteable, project, author, body, action: 'status')) + end end end diff --git a/app/views/projects/pipelines/show.html.haml b/app/views/projects/pipelines/show.html.haml index d654d0e04d7..70815dbe7a7 100644 --- a/app/views/projects/pipelines/show.html.haml +++ b/app/views/projects/pipelines/show.html.haml @@ -26,6 +26,7 @@ - lint_link_start = '<a href="%{url}" class="gl-text-blue-500!">'.html_safe % { url: lint_link_url } = s_('You can also test your %{gitlab_ci_yml} in %{lint_link_start}CI Lint%{lint_link_end}').html_safe % { gitlab_ci_yml: '.gitlab-ci.yml', lint_link_start: lint_link_start, lint_link_end: '</a>'.html_safe } + #js-pipeline-notification{ data: { deprecated_keywords_doc_path: help_page_path('ci/yaml/index.md', anchor: 'deprecated-keywords'), full_path: @project.full_path, pipeline_iid: @pipeline.iid } } = render "projects/pipelines/with_tabs", pipeline: @pipeline, stages: @stages, pipeline_has_errors: pipeline_has_errors .js-pipeline-details-vue{ data: { metrics_path: namespace_project_ci_prometheus_metrics_histograms_path(namespace_id: @project.namespace, project_id: @project, format: :json), pipeline_project_path: @project.full_path, pipeline_iid: @pipeline.iid, graphql_resource_etag: graphql_etag_pipeline_path(@pipeline) } } diff --git a/config/feature_flags/development/rate_limit_frontend_requests.yml b/config/feature_flags/development/rate_limit_frontend_requests.yml deleted file mode 100644 index 624dc303568..00000000000 --- a/config/feature_flags/development/rate_limit_frontend_requests.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: rate_limit_frontend_requests -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/78082 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/350623 -milestone: '14.8' -type: development -group: group::integrations -default_enabled: false diff --git a/db/post_migrate/20220119201340_remove_ci_pipelines_vulnerability_statistics_latest_pipeline_id_fk.rb b/db/post_migrate/20220119201340_remove_ci_pipelines_vulnerability_statistics_latest_pipeline_id_fk.rb new file mode 100644 index 00000000000..bd80767cf6a --- /dev/null +++ b/db/post_migrate/20220119201340_remove_ci_pipelines_vulnerability_statistics_latest_pipeline_id_fk.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class RemoveCiPipelinesVulnerabilityStatisticsLatestPipelineIdFk < Gitlab::Database::Migration[1.0] + disable_ddl_transaction! + + def up + with_lock_retries do + execute('LOCK ci_pipelines, vulnerability_statistics IN ACCESS EXCLUSIVE MODE') + + remove_foreign_key_if_exists(:vulnerability_statistics, :ci_pipelines, name: "fk_e8b13c928f") + end + end + + def down + add_concurrent_foreign_key(:vulnerability_statistics, :ci_pipelines, name: "fk_e8b13c928f", column: :latest_pipeline_id, target_column: :id, on_delete: :nullify) + end +end diff --git a/db/schema_migrations/20220119201340 b/db/schema_migrations/20220119201340 new file mode 100644 index 00000000000..0dc36b2dfba --- /dev/null +++ b/db/schema_migrations/20220119201340 @@ -0,0 +1 @@ +62b35cc5bd0abc5b7bcf4347346de832bbbed723fee7860ec649185d4d5fb53c
\ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 6db27149186..3d0f2771fc5 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -29926,9 +29926,6 @@ ALTER TABLE ONLY sprints ALTER TABLE ONLY application_settings ADD CONSTRAINT fk_e8a145f3a7 FOREIGN KEY (instance_administrators_group_id) REFERENCES namespaces(id) ON DELETE SET NULL; -ALTER TABLE ONLY vulnerability_statistics - ADD CONSTRAINT fk_e8b13c928f FOREIGN KEY (latest_pipeline_id) REFERENCES ci_pipelines(id) ON DELETE SET NULL; - ALTER TABLE ONLY integrations ADD CONSTRAINT fk_e8fe908a34 FOREIGN KEY (group_id) REFERENCES namespaces(id) ON DELETE CASCADE; diff --git a/doc/ci/yaml/includes.md b/doc/ci/yaml/includes.md index 3c94ddb3c14..34db6c61d0b 100644 --- a/doc/ci/yaml/includes.md +++ b/doc/ci/yaml/includes.md @@ -217,6 +217,69 @@ default: - echo "Job complete." ``` +### Use nested includes with duplicate `includes` entries + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/28987) in GitLab 14.8 + +Nested includes can include the same configuration file. The duplicate configuration +file is included multiple times, but the effect is the same as if it was only +included once. + +For example, with the following nested includes, where `defaults.gitlab-ci.yml` +is included multiple times: + +- Contents of the `.gitlab-ci.yml` file: + + ```yaml + include: + - template: defaults.gitlab-ci.yml + - local: unit-tests.gitlab-ci.yml + - local: smoke-tests.gitlab-ci.yml + ``` + +- Contents of the `defaults.gitlab-ci.yml` file: + + ```yaml + default: + before_script: default-before-script.sh + retry: 2 + ``` + +- Contents of the `unit-tests.gitlab-ci.yml` file: + + ```yaml + include: + - template: defaults.gitlab-ci.yml + + unit-test-job: + script: unit-test.sh + retry: 0 + ``` + +- Contents of the `smoke-tests.gitlab-ci.yml` file: + + ```yaml + include: + - template: defaults.gitlab-ci.yml + + smoke-test-job: + script: smoke-test.sh + ``` + +The final configuration would be: + +```yaml +unit-test-job: + before_script: default-before-script.sh + script: unit-test.sh + retry: 0 + +smoke-test-job: + before_script: default-before-script.sh + script: smoke-test.sh + retry: 2 +``` + ## Use variables with `include` > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/284883) in GitLab 13.8. diff --git a/doc/install/installation.md b/doc/install/installation.md index 898ff59585c..b264b221cac 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -578,9 +578,10 @@ sudo -u git -H git config --global core.fsyncObjectFiles true # Configure Redis connection settings sudo -u git -H cp config/resque.yml.example config/resque.yml +sudo -u git -H cp config/cable.yml.example config/cable.yml # Change the Redis socket path if you are not using the default Debian / Ubuntu configuration -sudo -u git -H editor config/resque.yml +sudo -u git -H editor config/resque.yml config/cable.yml ``` Make sure to edit both `gitlab.yml` and `puma.rb` to match your setup. diff --git a/doc/update/upgrading_from_source.md b/doc/update/upgrading_from_source.md index 22ffcda9138..286083ce868 100644 --- a/doc/update/upgrading_from_source.md +++ b/doc/update/upgrading_from_source.md @@ -410,6 +410,20 @@ Example: Additional instructions here. --> +### 14.5.0 + +As part of [enabling real-time issue assignees](https://gitlab.com/gitlab-org/gitlab/-/issues/330117), Action Cable is now enabled by default, and requires `config/cable.yml` to be present. +You can configure this by running: + +```shell +cd /home/git/gitlab + +sudo -u git -H cp config/cable.yml.example config/cable.yml + +# Change the Redis socket path if you are not using the default Debian / Ubuntu configuration +sudo -u git -H editor config/cable.yml +``` + ### 13.0.1 As part of [deprecating Rack Attack throttles on Omnibus GitLab](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/4750), the Rack Attack initializer on GitLab diff --git a/doc/user/admin_area/settings/user_and_ip_rate_limits.md b/doc/user/admin_area/settings/user_and_ip_rate_limits.md index ad61f18343c..d713ef4b4e0 100644 --- a/doc/user/admin_area/settings/user_and_ip_rate_limits.md +++ b/doc/user/admin_area/settings/user_and_ip_rate_limits.md @@ -22,10 +22,6 @@ NOTE: By default, all Git operations are first tried unauthenticated. Because of this, HTTP Git operations may trigger the rate limits configured for unauthenticated requests. -NOTE: -The rate limits for API requests don't affect requests made by the frontend, as these are always -counted as web traffic. - ## Enable unauthenticated API request rate limit To enable the unauthenticated request rate limit: diff --git a/doc/user/project/merge_requests/approvals/index.md b/doc/user/project/merge_requests/approvals/index.md index dddd3925dbb..e940426dc67 100644 --- a/doc/user/project/merge_requests/approvals/index.md +++ b/doc/user/project/merge_requests/approvals/index.md @@ -8,7 +8,7 @@ disqus_identifier: 'https://docs.gitlab.com/ee/user/project/merge_requests/appro # Merge request approvals **(FREE)** -> Redesign [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/1979) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.8 and [feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/10685) in 12.0. +> Redesign [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/1979) in GitLab 11.8 and [feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/10685) in 12.0. You can configure your merge requests so that they must be approved before they can be merged. While [GitLab Free](https://about.gitlab.com/pricing/) allows @@ -89,7 +89,7 @@ a merge request from merging without approval. ## Required approvals **(PREMIUM)** -> Moved to [GitLab Premium](https://about.gitlab.com/pricing/) in 13.9. +> Moved to GitLab Premium in 13.9. Required approvals enforce code reviews by the number and type of users you specify. Without the approvals, the work cannot merge. Required approvals enable multiple use cases: @@ -103,7 +103,7 @@ Without the approvals, the work cannot merge. Required approvals enable multiple to determine who should review the work. - Require an [approval before merging code that causes test coverage to decline](../../../../ci/pipelines/settings.md#coverage-check-approval-rule) - [Require approval from a security team](../../../application_security/index.md#security-approvals-in-merge-requests) - before merging code that could introduce a vulnerability. **(ULTIMATE)** + before merging code that could introduce a vulnerability. ## Related topics diff --git a/doc/user/project/merge_requests/approvals/rules.md b/doc/user/project/merge_requests/approvals/rules.md index 129010010e7..9bed24b3571 100644 --- a/doc/user/project/merge_requests/approvals/rules.md +++ b/doc/user/project/merge_requests/approvals/rules.md @@ -76,9 +76,9 @@ To edit a merge request approval rule: select **{remove}** **Remove**. 1. Select **Update approval rule**. -## Add multiple approval rules **(PREMIUM)** +## Add multiple approval rules -> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/1979) in GitLab Premium 11.10. +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/1979) in GitLab 11.10. In GitLab Premium and higher tiers, you can enforce multiple approval rules on a merge request, and multiple default approval rules for a project. If your tier @@ -143,7 +143,7 @@ approve in these ways: [**Prevent committers approval**](settings.md#prevent-approvals-by-users-who-add-commits) project setting. -### Code owners as eligible approvers **(PREMIUM)** +### Code owners as eligible approvers > Moved to GitLab Premium in 13.9. @@ -158,9 +158,9 @@ become eligible approvers in the project. To enable this merge request approval You can also [require code owner approval](../../protected_branches.md#require-code-owner-approval-on-a-protected-branch) -for protected branches. **(PREMIUM)** +for protected branches. -## Merge request approval segregation of duties **(PREMIUM)** +## Merge request approval segregation of duties > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/40491) in GitLab 13.4. > - Moved to GitLab Premium in 13.9. @@ -202,7 +202,7 @@ on a merge request, you can either add or remove approvers: Administrators can change the [merge request approvals settings](settings.md#prevent-editing-approval-rules-in-merge-requests) to prevent users from overriding approval rules for merge requests. -## Configure optional approval rules **(PREMIUM)** +## Configure optional approval rules Merge request approvals can be optional for projects where approvals are appreciated, but not required. To make an approval rule optional: @@ -211,9 +211,9 @@ appreciated, but not required. To make an approval rule optional: - Use the [Merge requests approvals API](../../../../api/merge_request_approvals.md#update-merge-request-level-rule) to set the `approvals_required` attribute to `0`. -## Approvals for protected branches **(PREMIUM)** +## Approvals for protected branches -> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/460) in GitLab Premium 12.8. +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/460) in GitLab 12.8. Approval rules are often relevant only to specific branches, like your [default branch](../../repository/branches/default.md). To configure an diff --git a/doc/user/project/merge_requests/changes.md b/doc/user/project/merge_requests/changes.md index d348c00bdc2..03f10b8b952 100644 --- a/doc/user/project/merge_requests/changes.md +++ b/doc/user/project/merge_requests/changes.md @@ -119,7 +119,7 @@ changes to its content or the checkbox is unchecked. > - [Deployed behind a feature flag](../../feature_flags.md), disabled by default. > - Disabled on GitLab.com. > - Not recommended for production use. -> - To use in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-merge-request-conflicts-in-diff). **(FREE SELF)** +> - To use in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-merge-request-conflicts-in-diff). This in-development feature might not be available for your use. There can be [risks when enabling features still in development](../../../administration/feature_flags.md#risks-when-enabling-features-still-in-development). diff --git a/doc/user/project/merge_requests/commits.md b/doc/user/project/merge_requests/commits.md index 3d3032bb193..1c5c0a6854a 100644 --- a/doc/user/project/merge_requests/commits.md +++ b/doc/user/project/merge_requests/commits.md @@ -33,7 +33,7 @@ To seamlessly navigate among commits in a merge request: > - [Deployed behind a feature flag](../../feature_flags.md), enabled by default. > - Disabled on GitLab.com. > - Not recommended for production use. -> - To use in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-viewing-merge-request-commits-in-context). **(FREE SELF)** +> - To use in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-viewing-merge-request-commits-in-context). WARNING: This feature is in [beta](https://about.gitlab.com/handbook/product/gitlab-the-product/#beta) diff --git a/doc/user/project/merge_requests/confidential.md b/doc/user/project/merge_requests/confidential.md index 10c63421876..6900880417f 100644 --- a/doc/user/project/merge_requests/confidential.md +++ b/doc/user/project/merge_requests/confidential.md @@ -4,7 +4,7 @@ group: Code Review info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- -# Merge requests for confidential issues +# Merge requests for confidential issues **(FREE)** > [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/58583) in GitLab 12.1. diff --git a/doc/user/project/merge_requests/getting_started.md b/doc/user/project/merge_requests/getting_started.md index ec509f58723..1f93e4f7712 100644 --- a/doc/user/project/merge_requests/getting_started.md +++ b/doc/user/project/merge_requests/getting_started.md @@ -56,7 +56,7 @@ request's page at the top-right side, or by using - [Assign](#assignee) the merge request to a colleague for review. With [multiple assignees](#multiple-assignees), you can assign it to more than one person at a time. - Set a [milestone](../milestones/index.md) to track time-sensitive changes. - Add [labels](../labels.md) to help contextualize and filter your merge requests over time. -- [Require approval](approvals/index.md#required-approvals) from your team. **(PREMIUM)** +- [Require approval](approvals/index.md#required-approvals) from your team. - [Close issues automatically](#merge-requests-to-close-issues) when they are merged. - Enable the [delete source branch when merge request is accepted](#deleting-the-source-branch) option to keep your repository clean. - Enable the [squash commits when merge request is accepted](squash_and_merge.md) option to combine all the commits into one before merging, thus keep a clean commit history in your repository. @@ -66,7 +66,7 @@ After you have created the merge request, you can also: - [Discuss](../../discussions/index.md) your implementation with your team in the merge request thread. - [Perform inline code reviews](reviews/index.md). -- Add [merge request dependencies](merge_request_dependencies.md) to restrict it to be merged only when other merge requests have been merged. **(PREMIUM)** +- Add [merge request dependencies](merge_request_dependencies.md) to restrict it to be merged only when other merge requests have been merged. - Preview continuous integration [pipelines on the merge request widget](widgets.md). - Preview how your changes look directly on your deployed application with [Review Apps](widgets.md#live-preview-with-review-apps). - [Allow collaboration on merge requests across forks](allow_collaboration.md). diff --git a/doc/user/project/merge_requests/merge_request_dependencies.md b/doc/user/project/merge_requests/merge_request_dependencies.md index aace1f58c62..6bfef6ab134 100644 --- a/doc/user/project/merge_requests/merge_request_dependencies.md +++ b/doc/user/project/merge_requests/merge_request_dependencies.md @@ -7,9 +7,9 @@ type: reference, concepts # Merge request dependencies **(PREMIUM)** -> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/9688) in [GitLab Premium](https://about.gitlab.com/pricing/) 12.2. -> - [Renamed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/17291) from "Cross-project dependencies" to "Merge request dependencies" in [GitLab Premium](https://about.gitlab.com/pricing/) 12.4. -> - Intra-project MR dependencies were [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/16799) in [GitLab Premium](https://about.gitlab.com/pricing/) 12.4. +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/9688) in GitLab 12.2. +> - [Renamed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/17291) from "Cross-project dependencies" to "Merge request dependencies" in GitLab 12.4. +> - Intra-project MR dependencies were [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/16799) in GitLab 12.4. Merge request dependencies allows a required order of merging between merge requests to be expressed. If a merge request "depends on" another, diff --git a/doc/user/project/merge_requests/reviews/index.md b/doc/user/project/merge_requests/reviews/index.md index c34a8116625..04d0298f061 100644 --- a/doc/user/project/merge_requests/reviews/index.md +++ b/doc/user/project/merge_requests/reviews/index.md @@ -23,8 +23,8 @@ review merge requests in Visual Studio Code. ## Review a merge request -> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/4213) in GitLab Premium 11.4. -> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/28154) to GitLab Free in 13.1. +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/4213) in GitLab 11.4. +> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/28154) from GitLab Premium to GitLab Free in 13.1. When you review a merge request, you can create comments that are visible only to you. When you're ready, you can publish them together in a single action. @@ -179,9 +179,9 @@ To update multiple project merge requests at the same time: 1. Select the appropriate fields and their values from the sidebar. 1. Click **Update all**. -## Bulk edit merge requests at the group level +## Bulk edit merge requests at the group level **(PREMIUM)** -> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12719) in [GitLab Premium](https://about.gitlab.com/pricing/) 12.2. +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12719) in GitLab 12.2. Users with permission level of [Developer or higher](../../../permissions.md) can manage merge requests. diff --git a/lib/gitlab/ci/config/external/mapper.rb b/lib/gitlab/ci/config/external/mapper.rb index a5bf066c81f..7f1de6ce1ab 100644 --- a/lib/gitlab/ci/config/external/mapper.rb +++ b/lib/gitlab/ci/config/external/mapper.rb @@ -19,7 +19,6 @@ module Gitlab Error = Class.new(StandardError) AmbigiousSpecificationError = Class.new(Error) - DuplicateIncludesError = Class.new(Error) TooManyIncludesError = Class.new(Error) def initialize(values, context) @@ -114,25 +113,22 @@ module Gitlab def verify_duplicates!(location) logger.instrument(:config_mapper_verify) do - verify_duplicates_without_instrumentation!(location) + verify_max_includes_and_add_location!(location) end end - def verify_duplicates_without_instrumentation!(location) + def verify_max_includes_and_add_location!(location) if expandset.count >= MAX_INCLUDES raise TooManyIncludesError, "Maximum of #{MAX_INCLUDES} nested includes are allowed!" end - # We scope location to context, as this allows us to properly support - # relative includes, and similarly looking relative in another project - # does not trigger duplicate error + # Scope location to context to allow support of + # relative includes scoped_location = location.merge( context_project: context.project, context_sha: context.sha) - unless expandset.add?(scoped_location) - raise DuplicateIncludesError, "Include `#{location.to_json}` was already included!" - end + expandset.add(scoped_location) end def select_first_matching(location) diff --git a/lib/gitlab/rack_attack/request.rb b/lib/gitlab/rack_attack/request.rb index d4a65227a20..94ae29af3d0 100644 --- a/lib/gitlab/rack_attack/request.rb +++ b/lib/gitlab/rack_attack/request.rb @@ -3,8 +3,6 @@ module Gitlab module RackAttack module Request - include ::Gitlab::Utils::StrongMemoize - FILES_PATH_REGEX = %r{^/api/v\d+/projects/[^/]+/repository/files/.+}.freeze GROUP_PATH_REGEX = %r{^/api/v\d+/groups/[^/]+/?$}.freeze @@ -32,15 +30,15 @@ module Gitlab end def api_internal_request? - path.match?(%r{^/api/v\d+/internal/}) + path =~ %r{^/api/v\d+/internal/} end def health_check_request? - path.match?(%r{^/-/(health|liveness|readiness|metrics)}) + path =~ %r{^/-/(health|liveness|readiness|metrics)} end def container_registry_event? - path.match?(%r{^/api/v\d+/container_registry_event/}) + path =~ %r{^/api/v\d+/container_registry_event/} end def product_analytics_collector_request? @@ -60,7 +58,7 @@ module Gitlab end def protected_path_regex - path.match?(protected_paths_regex) + path =~ protected_paths_regex end def throttle?(throttle, authenticated:) @@ -72,7 +70,6 @@ module Gitlab def throttle_unauthenticated_api? api_request? && !should_be_skipped? && - !frontend_request? && !throttle_unauthenticated_packages_api? && !throttle_unauthenticated_files_api? && !throttle_unauthenticated_deprecated_api? && @@ -81,7 +78,7 @@ module Gitlab end def throttle_unauthenticated_web? - (web_request? || frontend_request?) && + web_request? && !should_be_skipped? && # TODO: Column will be renamed in https://gitlab.com/gitlab-org/gitlab/-/issues/340031 Gitlab::Throttle.settings.throttle_unauthenticated_enabled && @@ -90,7 +87,6 @@ module Gitlab def throttle_authenticated_api? api_request? && - !frontend_request? && !throttle_authenticated_packages_api? && !throttle_authenticated_files_api? && !throttle_authenticated_deprecated_api? && @@ -98,7 +94,7 @@ module Gitlab end def throttle_authenticated_web? - (web_request? || frontend_request?) && + web_request? && !throttle_authenticated_git_lfs? && Gitlab::Throttle.settings.throttle_authenticated_web_enabled end @@ -182,26 +178,15 @@ module Gitlab end def packages_api_path? - path.match?(::Gitlab::Regex::Packages::API_PATH_REGEX) + path =~ ::Gitlab::Regex::Packages::API_PATH_REGEX end def git_lfs_path? - path.match?(Gitlab::PathRegex.repository_git_lfs_route_regex) + path =~ Gitlab::PathRegex.repository_git_lfs_route_regex end def files_api_path? - path.match?(FILES_PATH_REGEX) - end - - def frontend_request? - return false unless Feature.enabled?(:rate_limit_frontend_requests, default_enabled: :yaml) - - strong_memoize(:frontend_request) do - next false unless env.include?('HTTP_X_CSRF_TOKEN') && session.include?(:_csrf_token) - - # CSRF tokens are not verified for GET/HEAD requests, so we pretend that we always have a POST request. - Gitlab::RequestForgeryProtection.verified?(env.merge('REQUEST_METHOD' => 'POST')) - end + path =~ FILES_PATH_REGEX end def deprecated_api_request? @@ -210,7 +195,7 @@ module Gitlab with_projects = params['with_projects'] with_projects = true if with_projects.blank? - path.match?(GROUP_PATH_REGEX) && Gitlab::Utils.to_boolean(with_projects) + path =~ GROUP_PATH_REGEX && Gitlab::Utils.to_boolean(with_projects) end end end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 5b0ae55cd1b..50ec90cac45 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -461,6 +461,12 @@ msgid_plural "%{bold_start}%{count}%{bold_end} opened merge requests" msgstr[0] "" msgstr[1] "" +msgid "%{codeStart}type%{codeEnd} is deprecated and will be removed in 15.0. Use %{codeStart}stage%{codeEnd} instead. %{linkStart}Learn More %{linkEnd}" +msgstr "" + +msgid "%{codeStart}types%{codeEnd} is deprecated and will be removed in 15.0. Use %{codeStart}stages%{codeEnd} instead. %{linkStart}Learn More %{linkEnd}" +msgstr "" + msgid "%{code_open}Masked:%{code_close} Hidden in job logs. Must match masking requirements." msgstr "" @@ -8716,6 +8722,9 @@ msgstr "" msgid "Commit Message" msgstr "" +msgid "Commit SHA" +msgstr "" + msgid "Commit changes" msgstr "" @@ -15557,6 +15566,9 @@ msgstr "" msgid "Found errors in your .gitlab-ci.yml:" msgstr "" +msgid "Found warning in your .gitlab-ci.yml" +msgstr "" + msgid "Framework successfully deleted" msgstr "" diff --git a/spec/channels/application_cable/connection_spec.rb b/spec/channels/application_cable/connection_spec.rb index affde0095cf..c10e0c0cab4 100644 --- a/spec/channels/application_cable/connection_spec.rb +++ b/spec/channels/application_cable/connection_spec.rb @@ -3,11 +3,15 @@ require 'spec_helper' RSpec.describe ApplicationCable::Connection, :clean_gitlab_redis_sessions do - include SessionHelpers + let(:session_id) { Rack::Session::SessionId.new('6919a6f1bb119dd7396fadc38fd18d0d') } context 'when session cookie is set' do before do - stub_session(session_hash) + Gitlab::Redis::Sessions.with do |redis| + redis.set("session:gitlab:#{session_id.private_id}", Marshal.dump(session_hash)) + end + + cookies[Gitlab::Application.config.session_options[:key]] = session_id.public_id end context 'when user is logged in' do diff --git a/spec/features/cycle_analytics_spec.rb b/spec/features/cycle_analytics_spec.rb index 69361f66a71..8c12bc9a76d 100644 --- a/spec/features/cycle_analytics_spec.rb +++ b/spec/features/cycle_analytics_spec.rb @@ -134,7 +134,7 @@ RSpec.describe 'Value Stream Analytics', :js do end it 'can filter the metrics by date' do - expect(metrics_values).to match_array(["21.0", "2.0", "1.0", "0.0"]) + expect(metrics_values).to match_array(%w[21 2 1 0]) set_daterange(from, to) diff --git a/spec/frontend/cycle_analytics/value_stream_metrics_spec.js b/spec/frontend/cycle_analytics/value_stream_metrics_spec.js index 082db2cc312..128654f2a51 100644 --- a/spec/frontend/cycle_analytics/value_stream_metrics_spec.js +++ b/spec/frontend/cycle_analytics/value_stream_metrics_spec.js @@ -88,12 +88,12 @@ describe('ValueStreamMetrics', () => { }); describe.each` - index | value | title | unit | clickable - ${0} | ${metricsData[0].value} | ${metricsData[0].title} | ${metricsData[0].unit} | ${false} - ${1} | ${metricsData[1].value} | ${metricsData[1].title} | ${metricsData[1].unit} | ${false} - ${2} | ${metricsData[2].value} | ${metricsData[2].title} | ${metricsData[2].unit} | ${false} - ${3} | ${metricsData[3].value} | ${metricsData[3].title} | ${metricsData[3].unit} | ${true} - `('metric tiles', ({ index, value, title, unit, clickable }) => { + index | value | title | unit | animationDecimalPlaces | clickable + ${0} | ${metricsData[0].value} | ${metricsData[0].title} | ${metricsData[0].unit} | ${0} | ${false} + ${1} | ${metricsData[1].value} | ${metricsData[1].title} | ${metricsData[1].unit} | ${0} | ${false} + ${2} | ${metricsData[2].value} | ${metricsData[2].title} | ${metricsData[2].unit} | ${0} | ${false} + ${3} | ${metricsData[3].value} | ${metricsData[3].title} | ${metricsData[3].unit} | ${1} | ${true} + `('metric tiles', ({ index, value, title, unit, animationDecimalPlaces, clickable }) => { it(`renders a single stat component for "${title}" with value and unit`, () => { const metric = findMetrics().at(index); expect(metric.props()).toMatchObject({ value, title, unit: unit ?? '' }); @@ -111,6 +111,11 @@ describe('ValueStreamMetrics', () => { expect(redirectTo).not.toHaveBeenCalled(); } }); + + it(`will render ${animationDecimalPlaces} decimal places for the ${title} metric with the value "${value}"`, () => { + const metric = findMetrics().at(index); + expect(metric.props('animationDecimalPlaces')).toBe(animationDecimalPlaces); + }); }); it('will not display a loading icon', () => { diff --git a/spec/frontend/environments/deployment_spec.js b/spec/frontend/environments/deployment_spec.js index 2127fb50b2c..a68e17fe6ac 100644 --- a/spec/frontend/environments/deployment_spec.js +++ b/spec/frontend/environments/deployment_spec.js @@ -1,5 +1,6 @@ import { mountExtended } from 'helpers/vue_test_utils_helper'; -import { s__ } from '~/locale'; +import { __, s__ } from '~/locale'; +import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; import Deployment from '~/environments/components/deployment.vue'; import DeploymentStatusBadge from '~/environments/components/deployment_status_badge.vue'; import { resolvedEnvironment } from './graphql/mock_data'; @@ -81,4 +82,47 @@ describe('~/environments/components/deployment.vue', () => { }); }); }); + + describe('shortSha', () => { + describe('is present', () => { + beforeEach(() => { + wrapper = createWrapper(); + }); + + it('shows the short SHA for the commit of the deployment', () => { + const sha = wrapper.findByTitle(__('Commit SHA')); + + expect(sha.exists()).toBe(true); + expect(sha.text()).toBe(deployment.commit.shortId); + }); + + it('shows the commit icon', () => { + const icon = wrapper.findComponent({ ref: 'deployment-commit-icon' }); + expect(icon.props('name')).toBe('commit'); + }); + + it('shows a copy button for the sha', () => { + const button = wrapper.findComponent(ClipboardButton); + expect(button.props()).toMatchObject({ + text: deployment.commit.shortId, + title: __('Copy commit SHA'), + }); + }); + }); + + describe('is not present', () => { + it('does not show the short SHA for the commit of the deployment', () => { + wrapper = createWrapper({ + propsData: { + deployment: { + ...deployment, + commit: null, + }, + }, + }); + const sha = wrapper.findByTestId('deployment-commit-sha'); + expect(sha.exists()).toBe(false); + }); + }); + }); }); diff --git a/spec/frontend/notes/components/discussion_counter_spec.js b/spec/frontend/notes/components/discussion_counter_spec.js index c454d502beb..924c89c6345 100644 --- a/spec/frontend/notes/components/discussion_counter_spec.js +++ b/spec/frontend/notes/components/discussion_counter_spec.js @@ -1,5 +1,6 @@ import { GlButton } from '@gitlab/ui'; -import { shallowMount, createLocalVue } from '@vue/test-utils'; +import { shallowMount } from '@vue/test-utils'; +import Vue from 'vue'; import Vuex from 'vuex'; import DiscussionCounter from '~/notes/components/discussion_counter.vue'; import notesModule from '~/notes/stores/modules'; @@ -10,9 +11,8 @@ describe('DiscussionCounter component', () => { let store; let wrapper; let setExpandDiscussionsFn; - const localVue = createLocalVue(); - localVue.use(Vuex); + Vue.use(Vuex); beforeEach(() => { window.mrTabs = {}; @@ -45,7 +45,7 @@ describe('DiscussionCounter component', () => { describe('has no discussions', () => { it('does not render', () => { - wrapper = shallowMount(DiscussionCounter, { store, localVue }); + wrapper = shallowMount(DiscussionCounter, { store }); expect(wrapper.find({ ref: 'discussionCounter' }).exists()).toBe(false); }); @@ -55,7 +55,7 @@ describe('DiscussionCounter component', () => { it('does not render', () => { store.commit(types.ADD_OR_UPDATE_DISCUSSIONS, [{ ...discussionMock, resolvable: false }]); store.dispatch('updateResolvableDiscussionsCounts'); - wrapper = shallowMount(DiscussionCounter, { store, localVue }); + wrapper = shallowMount(DiscussionCounter, { store }); expect(wrapper.find({ ref: 'discussionCounter' }).exists()).toBe(false); }); @@ -75,7 +75,7 @@ describe('DiscussionCounter component', () => { it('renders', () => { updateStore(); - wrapper = shallowMount(DiscussionCounter, { store, localVue }); + wrapper = shallowMount(DiscussionCounter, { store }); expect(wrapper.find({ ref: 'discussionCounter' }).exists()).toBe(true); }); @@ -86,7 +86,7 @@ describe('DiscussionCounter component', () => { ${'allResolved'} | ${true} | ${true} | ${1} `('renders correctly if $title', ({ resolved, isActive, groupLength }) => { updateStore({ resolvable: true, resolved }); - wrapper = shallowMount(DiscussionCounter, { store, localVue }); + wrapper = shallowMount(DiscussionCounter, { store }); expect(wrapper.find(`.is-active`).exists()).toBe(isActive); expect(wrapper.findAll(GlButton)).toHaveLength(groupLength); @@ -99,7 +99,7 @@ describe('DiscussionCounter component', () => { const discussion = { ...discussionMock, expanded }; store.commit(types.ADD_OR_UPDATE_DISCUSSIONS, [discussion]); store.dispatch('updateResolvableDiscussionsCounts'); - wrapper = shallowMount(DiscussionCounter, { store, localVue }); + wrapper = shallowMount(DiscussionCounter, { store }); toggleAllButton = wrapper.find('.toggle-all-discussions-btn'); }; diff --git a/spec/frontend/notes/components/discussion_filter_spec.js b/spec/frontend/notes/components/discussion_filter_spec.js index 17998dfc9d5..1f97bb97415 100644 --- a/spec/frontend/notes/components/discussion_filter_spec.js +++ b/spec/frontend/notes/components/discussion_filter_spec.js @@ -1,5 +1,6 @@ import { GlDropdown } from '@gitlab/ui'; -import { createLocalVue, mount } from '@vue/test-utils'; +import { mount } from '@vue/test-utils'; +import Vue from 'vue'; import AxiosMockAdapter from 'axios-mock-adapter'; import Vuex from 'vuex'; import { TEST_HOST } from 'helpers/test_constants'; @@ -12,9 +13,7 @@ import notesModule from '~/notes/stores/modules'; import { discussionFiltersMock, discussionMock } from '../mock_data'; -const localVue = createLocalVue(); - -localVue.use(Vuex); +Vue.use(Vuex); const DISCUSSION_PATH = `${TEST_HOST}/example`; @@ -58,7 +57,6 @@ describe('DiscussionFilter component', () => { filters: discussionFiltersMock, selectedValue: DISCUSSION_FILTERS_DEFAULT_VALUE, }, - localVue, }); }; diff --git a/spec/frontend/notes/components/discussion_navigator_spec.js b/spec/frontend/notes/components/discussion_navigator_spec.js index e430e18b76a..77ae7b2c3b5 100644 --- a/spec/frontend/notes/components/discussion_navigator_spec.js +++ b/spec/frontend/notes/components/discussion_navigator_spec.js @@ -1,6 +1,6 @@ /* global Mousetrap */ import 'mousetrap'; -import { shallowMount, createLocalVue } from '@vue/test-utils'; +import { shallowMount } from '@vue/test-utils'; import Vue from 'vue'; import { keysFor, @@ -11,8 +11,6 @@ import DiscussionNavigator from '~/notes/components/discussion_navigator.vue'; import eventHub from '~/notes/event_hub'; describe('notes/components/discussion_navigator', () => { - const localVue = createLocalVue(); - let wrapper; let jumpToNextDiscussion; let jumpToPreviousDiscussion; @@ -20,12 +18,12 @@ describe('notes/components/discussion_navigator', () => { const createComponent = () => { wrapper = shallowMount(DiscussionNavigator, { mixins: [ - localVue.extend({ + { methods: { jumpToNextDiscussion, jumpToPreviousDiscussion, }, - }), + }, ], }); }; @@ -48,7 +46,7 @@ describe('notes/components/discussion_navigator', () => { beforeEach(() => { onSpy = jest.spyOn(eventHub, '$on'); - vm = new (Vue.extend(DiscussionNavigator))(); + vm = new Vue(DiscussionNavigator); }); it('listens for jumpToFirstUnresolvedDiscussion events', () => { diff --git a/spec/frontend/notes/components/discussion_resolve_with_issue_button_spec.js b/spec/frontend/notes/components/discussion_resolve_with_issue_button_spec.js index 4348445f7ca..5bc6282db03 100644 --- a/spec/frontend/notes/components/discussion_resolve_with_issue_button_spec.js +++ b/spec/frontend/notes/components/discussion_resolve_with_issue_button_spec.js @@ -1,17 +1,14 @@ import { GlButton } from '@gitlab/ui'; -import { shallowMount, createLocalVue } from '@vue/test-utils'; +import { shallowMount } from '@vue/test-utils'; import { TEST_HOST } from 'spec/test_constants'; import ResolveWithIssueButton from '~/notes/components/discussion_resolve_with_issue_button.vue'; -const localVue = createLocalVue(); - describe('ResolveWithIssueButton', () => { let wrapper; const url = `${TEST_HOST}/hello-world/`; beforeEach(() => { wrapper = shallowMount(ResolveWithIssueButton, { - localVue, propsData: { url, }, diff --git a/spec/frontend/notes/components/note_actions_spec.js b/spec/frontend/notes/components/note_actions_spec.js index ecce854b00a..842c669c489 100644 --- a/spec/frontend/notes/components/note_actions_spec.js +++ b/spec/frontend/notes/components/note_actions_spec.js @@ -1,4 +1,4 @@ -import { mount, createLocalVue, createWrapper } from '@vue/test-utils'; +import { mount, createWrapper } from '@vue/test-utils'; import AxiosMockAdapter from 'axios-mock-adapter'; import Vue from 'vue'; import { TEST_HOST } from 'spec/test_constants'; @@ -20,11 +20,9 @@ describe('noteActions', () => { const findUserAccessRoleBadgeText = (idx) => findUserAccessRoleBadge(idx).text().trim(); const mountNoteActions = (propsData, computed) => { - const localVue = createLocalVue(); - return mount(localVue.extend(noteActions), { + return mount(noteActions, { store, propsData, - localVue, computed, }); }; diff --git a/spec/frontend/notes/components/note_header_spec.js b/spec/frontend/notes/components/note_header_spec.js index 774d5aaa7d3..8d82cf3d2c7 100644 --- a/spec/frontend/notes/components/note_header_spec.js +++ b/spec/frontend/notes/components/note_header_spec.js @@ -1,13 +1,12 @@ import { GlSprintf } from '@gitlab/ui'; -import { shallowMount, createLocalVue } from '@vue/test-utils'; -import { nextTick } from 'vue'; +import { shallowMount } from '@vue/test-utils'; +import Vue, { nextTick } from 'vue'; import Vuex from 'vuex'; import NoteHeader from '~/notes/components/note_header.vue'; import { AVAILABILITY_STATUS } from '~/set_status_modal/utils'; import UserNameWithStatus from '~/sidebar/components/assignees/user_name_with_status.vue'; -const localVue = createLocalVue(); -localVue.use(Vuex); +Vue.use(Vuex); const actions = { setTargetNoteHash: jest.fn(), @@ -42,7 +41,6 @@ describe('NoteHeader component', () => { const createComponent = (props) => { wrapper = shallowMount(NoteHeader, { - localVue, store: new Vuex.Store({ actions, }), diff --git a/spec/frontend/notes/components/sort_discussion_spec.js b/spec/frontend/notes/components/sort_discussion_spec.js index 60f03a0f5b5..a279dfd1ef3 100644 --- a/spec/frontend/notes/components/sort_discussion_spec.js +++ b/spec/frontend/notes/components/sort_discussion_spec.js @@ -1,4 +1,5 @@ -import { shallowMount, createLocalVue } from '@vue/test-utils'; +import { shallowMount } from '@vue/test-utils'; +import Vue from 'vue'; import Vuex from 'vuex'; import SortDiscussion from '~/notes/components/sort_discussion.vue'; import { ASC, DESC } from '~/notes/constants'; @@ -6,8 +7,7 @@ import createStore from '~/notes/stores'; import Tracking from '~/tracking'; import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue'; -const localVue = createLocalVue(); -localVue.use(Vuex); +Vue.use(Vuex); describe('Sort Discussion component', () => { let wrapper; @@ -17,7 +17,6 @@ describe('Sort Discussion component', () => { jest.spyOn(store, 'dispatch').mockImplementation(); wrapper = shallowMount(SortDiscussion, { - localVue, store, }); }; diff --git a/spec/frontend/notes/components/timeline_toggle_spec.js b/spec/frontend/notes/components/timeline_toggle_spec.js index 73fb2079e31..0b304e5181b 100644 --- a/spec/frontend/notes/components/timeline_toggle_spec.js +++ b/spec/frontend/notes/components/timeline_toggle_spec.js @@ -1,5 +1,6 @@ import { GlButton } from '@gitlab/ui'; -import { shallowMount, createLocalVue } from '@vue/test-utils'; +import { shallowMount } from '@vue/test-utils'; +import Vue from 'vue'; import Vuex from 'vuex'; import TimelineToggle, { timelineEnabledTooltip, @@ -10,8 +11,7 @@ import createStore from '~/notes/stores'; import { trackToggleTimelineView } from '~/notes/utils'; import Tracking from '~/tracking'; -const localVue = createLocalVue(); -localVue.use(Vuex); +Vue.use(Vuex); describe('Timeline toggle', () => { let wrapper; @@ -23,7 +23,6 @@ describe('Timeline toggle', () => { jest.spyOn(Tracking, 'event').mockImplementation(); wrapper = shallowMount(TimelineToggle, { - localVue, store, }); }; diff --git a/spec/frontend/notes/mixins/discussion_navigation_spec.js b/spec/frontend/notes/mixins/discussion_navigation_spec.js index 26a072b82f8..3daf23c83cf 100644 --- a/spec/frontend/notes/mixins/discussion_navigation_spec.js +++ b/spec/frontend/notes/mixins/discussion_navigation_spec.js @@ -1,5 +1,5 @@ -import { shallowMount, createLocalVue } from '@vue/test-utils'; -import { nextTick } from 'vue'; +import { shallowMount } from '@vue/test-utils'; +import Vue, { nextTick } from 'vue'; import Vuex from 'vuex'; import { setHTMLFixture } from 'helpers/fixtures'; import createEventHub from '~/helpers/event_hub_factory'; @@ -27,8 +27,7 @@ const createComponent = () => ({ }); describe('Discussion navigation mixin', () => { - const localVue = createLocalVue(); - localVue.use(Vuex); + Vue.use(Vuex); let wrapper; let store; @@ -65,7 +64,7 @@ describe('Discussion navigation mixin', () => { }); store.state.notes.discussions = createDiscussions(); - wrapper = shallowMount(createComponent(), { store, localVue }); + wrapper = shallowMount(createComponent(), { store }); }); afterEach(() => { diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/cli_commands_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/cli_commands_spec.js index 86fb6f4c77d..7727bf167fe 100644 --- a/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/cli_commands_spec.js +++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/cli_commands_spec.js @@ -1,5 +1,6 @@ import { GlDropdown } from '@gitlab/ui'; -import { mount, createLocalVue } from '@vue/test-utils'; +import { mount } from '@vue/test-utils'; +import Vue from 'vue'; import Vuex from 'vuex'; import QuickstartDropdown from '~/packages_and_registries/shared/components/cli_commands.vue'; import { @@ -16,8 +17,7 @@ import CodeInstruction from '~/vue_shared/components/registry/code_instruction.v import { dockerCommands } from '../../mock_data'; -const localVue = createLocalVue(); -localVue.use(Vuex); +Vue.use(Vuex); describe('cli_commands', () => { let wrapper; @@ -27,7 +27,6 @@ describe('cli_commands', () => { const mountComponent = () => { wrapper = mount(QuickstartDropdown, { - localVue, propsData: { ...dockerCommands, }, diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/group_empty_state_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/group_empty_state_spec.js index 027cdf732bc..d2086943e4f 100644 --- a/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/group_empty_state_spec.js +++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/group_empty_state_spec.js @@ -1,11 +1,11 @@ import { GlSprintf } from '@gitlab/ui'; -import { shallowMount, createLocalVue } from '@vue/test-utils'; +import { shallowMount } from '@vue/test-utils'; +import Vue from 'vue'; import Vuex from 'vuex'; import groupEmptyState from '~/packages_and_registries/container_registry/explorer/components/list_page/group_empty_state.vue'; import { GlEmptyState } from '../../stubs'; -const localVue = createLocalVue(); -localVue.use(Vuex); +Vue.use(Vuex); describe('Registry Group Empty state', () => { let wrapper; @@ -16,7 +16,6 @@ describe('Registry Group Empty state', () => { beforeEach(() => { wrapper = shallowMount(groupEmptyState, { - localVue, stubs: { GlEmptyState, GlSprintf, diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/project_empty_state_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/project_empty_state_spec.js index 21748ae2813..8cfa8128021 100644 --- a/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/project_empty_state_spec.js +++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/project_empty_state_spec.js @@ -1,12 +1,12 @@ import { GlSprintf } from '@gitlab/ui'; -import { shallowMount, createLocalVue } from '@vue/test-utils'; +import { shallowMount } from '@vue/test-utils'; +import Vue from 'vue'; import Vuex from 'vuex'; import projectEmptyState from '~/packages_and_registries/container_registry/explorer/components/list_page/project_empty_state.vue'; import { dockerCommands } from '../../mock_data'; import { GlEmptyState } from '../../stubs'; -const localVue = createLocalVue(); -localVue.use(Vuex); +Vue.use(Vuex); describe('Registry Project Empty state', () => { let wrapper; @@ -21,7 +21,6 @@ describe('Registry Project Empty state', () => { beforeEach(() => { wrapper = shallowMount(projectEmptyState, { - localVue, stubs: { GlEmptyState, GlSprintf, diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/app_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/app_spec.js index 2868af84181..69c78e64e22 100644 --- a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/app_spec.js +++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/app_spec.js @@ -1,6 +1,6 @@ import { GlEmptyState } from '@gitlab/ui'; -import { mount, createLocalVue } from '@vue/test-utils'; -import { nextTick } from 'vue'; +import { mount } from '@vue/test-utils'; +import Vue, { nextTick } from 'vue'; import Vuex from 'vuex'; import { useMockLocationHelper } from 'helpers/mock_window_location_helper'; import stubChildren from 'helpers/stub_children'; @@ -19,8 +19,7 @@ import Tracking from '~/tracking'; import { mavenPackage, mavenFiles, npmPackage } from '../../mock_data'; -const localVue = createLocalVue(); -localVue.use(Vuex); +Vue.use(Vuex); useMockLocationHelper(); @@ -60,7 +59,6 @@ describe('PackagesApp', () => { }); wrapper = mount(PackagesApp, { - localVue, store, stubs: { ...stubChildren(PackagesApp), diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/details_title_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/details_title_spec.js index 24bd80ba80c..95b70cb010d 100644 --- a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/details_title_spec.js +++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/details_title_spec.js @@ -1,11 +1,11 @@ -import { shallowMount, createLocalVue } from '@vue/test-utils'; +import { shallowMount } from '@vue/test-utils'; +import Vue from 'vue'; import Vuex from 'vuex'; import component from '~/packages_and_registries/infrastructure_registry/details/components/details_title.vue'; import TitleArea from '~/vue_shared/components/registry/title_area.vue'; import { terraformModule, mavenFiles, npmPackage } from '../../mock_data'; -const localVue = createLocalVue(); -localVue.use(Vuex); +Vue.use(Vuex); describe('PackageTitle', () => { let wrapper; @@ -23,7 +23,6 @@ describe('PackageTitle', () => { }); wrapper = shallowMount(component, { - localVue, store, stubs: { TitleArea, diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/terraform_installation_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/terraform_installation_spec.js index 6ff4a4c51ef..78c1b840dbc 100644 --- a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/terraform_installation_spec.js +++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/terraform_installation_spec.js @@ -1,11 +1,11 @@ -import { shallowMount, createLocalVue } from '@vue/test-utils'; +import { shallowMount } from '@vue/test-utils'; +import Vue from 'vue'; import Vuex from 'vuex'; import TerraformInstallation from '~/packages_and_registries/infrastructure_registry/details/components/terraform_installation.vue'; import CodeInstructions from '~/vue_shared/components/registry/code_instruction.vue'; import { terraformModule as packageEntity } from '../../mock_data'; -const localVue = createLocalVue(); -localVue.use(Vuex); +Vue.use(Vuex); describe('TerraformInstallation', () => { let wrapper; @@ -22,7 +22,6 @@ describe('TerraformInstallation', () => { function createComponent() { wrapper = shallowMount(TerraformInstallation, { - localVue, store, }); } diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/infrastructure_search_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/infrastructure_search_spec.js index b519ab00d06..e5230417c78 100644 --- a/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/infrastructure_search_spec.js +++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/infrastructure_search_spec.js @@ -1,11 +1,11 @@ -import { shallowMount, createLocalVue } from '@vue/test-utils'; +import { shallowMount } from '@vue/test-utils'; +import Vue from 'vue'; import Vuex from 'vuex'; import component from '~/packages_and_registries/infrastructure_registry/list/components/infrastructure_search.vue'; import RegistrySearch from '~/vue_shared/components/registry/registry_search.vue'; import UrlSync from '~/vue_shared/components/url_sync.vue'; -const localVue = createLocalVue(); -localVue.use(Vuex); +Vue.use(Vuex); describe('Infrastructure Search', () => { let wrapper; @@ -48,7 +48,6 @@ describe('Infrastructure Search', () => { createStore(isGroupPage); wrapper = shallowMount(component, { - localVue, store, stubs: { UrlSync, diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/packages_list_app_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/packages_list_app_spec.js index cad75d2a858..4c536b6d56a 100644 --- a/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/packages_list_app_spec.js +++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/packages_list_app_spec.js @@ -1,5 +1,6 @@ import { GlEmptyState, GlSprintf, GlLink } from '@gitlab/ui'; -import { shallowMount, createLocalVue } from '@vue/test-utils'; +import { shallowMount } from '@vue/test-utils'; +import Vue from 'vue'; import Vuex from 'vuex'; import setWindowLocation from 'helpers/set_window_location_helper'; import createFlash from '~/flash'; @@ -17,8 +18,7 @@ import InfrastructureSearch from '~/packages_and_registries/infrastructure_regis jest.mock('~/lib/utils/common_utils'); jest.mock('~/flash'); -const localVue = createLocalVue(); -localVue.use(Vuex); +Vue.use(Vuex); describe('packages_list_app', () => { let wrapper; @@ -53,7 +53,6 @@ describe('packages_list_app', () => { const mountComponent = (provide) => { wrapper = shallowMount(PackageListApp, { - localVue, store, stubs: { GlEmptyState, diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/packages_list_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/packages_list_spec.js index 26569f20e94..5efd23f8d7e 100644 --- a/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/packages_list_spec.js +++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/packages_list_spec.js @@ -1,5 +1,6 @@ import { GlTable, GlPagination, GlModal } from '@gitlab/ui'; -import { mount, createLocalVue } from '@vue/test-utils'; +import { mount } from '@vue/test-utils'; +import Vue from 'vue'; import { last } from 'lodash'; import Vuex from 'vuex'; import stubChildren from 'helpers/stub_children'; @@ -11,8 +12,7 @@ import { TRACK_CATEGORY } from '~/packages_and_registries/infrastructure_registr import Tracking from '~/tracking'; import { packageList } from '../../mock_data'; -const localVue = createLocalVue(); -localVue.use(Vuex); +Vue.use(Vuex); describe('packages_list', () => { let wrapper; @@ -61,7 +61,6 @@ describe('packages_list', () => { createStore(isGroupPage, packages, isLoading); wrapper = mount(PackagesList, { - localVue, store, stubs: { ...stubChildren(PackagesList), diff --git a/spec/frontend/packages_and_registries/package_registry/components/list/package_list_row_spec.js b/spec/frontend/packages_and_registries/package_registry/components/list/package_list_row_spec.js index 9467a613b2a..05f20f8150b 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/list/package_list_row_spec.js +++ b/spec/frontend/packages_and_registries/package_registry/components/list/package_list_row_spec.js @@ -1,5 +1,5 @@ import { GlSprintf } from '@gitlab/ui'; -import { createLocalVue } from '@vue/test-utils'; +import Vue from 'vue'; import VueRouter from 'vue-router'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; @@ -17,8 +17,7 @@ import { PACKAGE_ERROR_STATUS } from '~/packages_and_registries/package_registry import ListItem from '~/vue_shared/components/registry/list_item.vue'; import { packageData, packagePipelines, packageProject, packageTags } from '../../mock_data'; -const localVue = createLocalVue(); -localVue.use(VueRouter); +Vue.use(VueRouter); describe('packages_list_row', () => { let wrapper; @@ -47,7 +46,6 @@ describe('packages_list_row', () => { provide = defaultProvide, } = {}) => { wrapper = shallowMountExtended(PackagesListRow, { - localVue, provide, stubs: { ListItem, diff --git a/spec/frontend/pipeline_editor/components/commit/commit_section_spec.js b/spec/frontend/pipeline_editor/components/commit/commit_section_spec.js index bc77b7045eb..c822d2c0338 100644 --- a/spec/frontend/pipeline_editor/components/commit/commit_section_spec.js +++ b/spec/frontend/pipeline_editor/components/commit/commit_section_spec.js @@ -1,6 +1,7 @@ import VueApollo from 'vue-apollo'; import { GlFormTextarea, GlFormInput, GlLoadingIcon } from '@gitlab/ui'; -import { createLocalVue, mount } from '@vue/test-utils'; +import { mount } from '@vue/test-utils'; +import Vue from 'vue'; import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; import { objectToQuery, redirectTo } from '~/lib/utils/url_utility'; @@ -26,8 +27,6 @@ import { mockNewMergeRequestPath, } from '../../mock_data'; -const localVue = createLocalVue(); - jest.mock('~/lib/utils/url_utility', () => ({ redirectTo: jest.fn(), refreshCurrentPage: jest.fn(), @@ -79,11 +78,10 @@ describe('Pipeline Editor | Commit section', () => { const createComponentWithApollo = (options) => { const handlers = [[commitCreate, mockMutateCommitData]]; - localVue.use(VueApollo); + Vue.use(VueApollo); mockApollo = createMockApollo(handlers); const apolloConfig = { - localVue, apolloProvider: mockApollo, }; diff --git a/spec/frontend/pipelines/notification/deprecated_type_keyword_notification_spec.js b/spec/frontend/pipelines/notification/deprecated_type_keyword_notification_spec.js new file mode 100644 index 00000000000..10ee55818ac --- /dev/null +++ b/spec/frontend/pipelines/notification/deprecated_type_keyword_notification_spec.js @@ -0,0 +1,140 @@ +import VueApollo from 'vue-apollo'; +import { createLocalVue, shallowMount } from '@vue/test-utils'; +import { GlAlert, GlSprintf } from '@gitlab/ui'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import DeprecatedTypeKeywordNotification from '~/pipelines/components/notification/deprecated_type_keyword_notification.vue'; +import getPipelineWarnings from '~/pipelines/graphql/queries/get_pipeline_warnings.query.graphql'; +import { + mockWarningsWithoutDeprecation, + mockWarningsRootType, + mockWarningsType, + mockWarningsTypesAll, +} from './mock_data'; + +const defaultProvide = { + deprecatedKeywordsDocPath: '/help/ci/yaml/index.md#deprecated-keywords', + fullPath: '/namespace/my-project', + pipelineIid: 4, +}; + +let wrapper; + +const mockWarnings = jest.fn(); + +const createComponent = ({ isLoading = false, options = {} } = {}) => { + return shallowMount(DeprecatedTypeKeywordNotification, { + stubs: { + GlSprintf, + }, + provide: { + ...defaultProvide, + }, + mocks: { + $apollo: { + queries: { + warnings: { + loading: isLoading, + }, + }, + }, + }, + ...options, + }); +}; + +const createComponentWithApollo = () => { + const localVue = createLocalVue(); + localVue.use(VueApollo); + + const handlers = [[getPipelineWarnings, mockWarnings]]; + const mockApollo = createMockApollo(handlers); + + return createComponent({ + options: { + localVue, + apolloProvider: mockApollo, + mocks: {}, + }, + }); +}; + +const findAlert = () => wrapper.findComponent(GlAlert); +const findAlertItems = () => findAlert().findAll('li'); + +afterEach(() => { + wrapper.destroy(); +}); + +describe('Deprecated keyword notification', () => { + describe('while loading the pipeline warnings', () => { + beforeEach(() => { + wrapper = createComponent({ isLoading: true }); + }); + + it('does not display the notification', () => { + expect(findAlert().exists()).toBe(false); + }); + }); + + describe('if there is an error in the query', () => { + beforeEach(() => { + mockWarnings.mockResolvedValue({ errors: ['It didnt work'] }); + wrapper = createComponentWithApollo(); + }); + + it('does not display the notification', () => { + expect(findAlert().exists()).toBe(false); + }); + }); + + describe('with a valid query result', () => { + describe('if there are no deprecation warnings', () => { + beforeEach(() => { + mockWarnings.mockResolvedValue(mockWarningsWithoutDeprecation); + wrapper = createComponentWithApollo(); + }); + it('does not show the notification', () => { + expect(findAlert().exists()).toBe(false); + }); + }); + + describe('with a root type deprecation message', () => { + beforeEach(() => { + mockWarnings.mockResolvedValue(mockWarningsRootType); + wrapper = createComponentWithApollo(); + }); + it('shows the notification with one item', () => { + expect(findAlert().exists()).toBe(true); + expect(findAlertItems()).toHaveLength(1); + expect(findAlertItems().at(0).text()).toContain('types'); + }); + }); + + describe('with a job type deprecation message', () => { + beforeEach(() => { + mockWarnings.mockResolvedValue(mockWarningsType); + wrapper = createComponentWithApollo(); + }); + it('shows the notification with one item', () => { + expect(findAlert().exists()).toBe(true); + expect(findAlertItems()).toHaveLength(1); + expect(findAlertItems().at(0).text()).toContain('type'); + expect(findAlertItems().at(0).text()).not.toContain('types'); + }); + }); + + describe('with both the root types and job type deprecation message', () => { + beforeEach(() => { + mockWarnings.mockResolvedValue(mockWarningsTypesAll); + wrapper = createComponentWithApollo(); + }); + it('shows the notification with two items', () => { + expect(findAlert().exists()).toBe(true); + expect(findAlertItems()).toHaveLength(2); + expect(findAlertItems().at(0).text()).toContain('types'); + expect(findAlertItems().at(1).text()).toContain('type'); + expect(findAlertItems().at(1).text()).not.toContain('types'); + }); + }); + }); +}); diff --git a/spec/frontend/pipelines/notification/mock_data.js b/spec/frontend/pipelines/notification/mock_data.js new file mode 100644 index 00000000000..e36f391a854 --- /dev/null +++ b/spec/frontend/pipelines/notification/mock_data.js @@ -0,0 +1,33 @@ +const randomWarning = { + content: 'another random warning', + id: 'gid://gitlab/Ci::PipelineMessage/272', +}; + +const rootTypeWarning = { + content: 'root `types` will be removed in 15.0.', + id: 'gid://gitlab/Ci::PipelineMessage/273', +}; + +const typeWarning = { + content: '`type` will be removed in 15.0.', + id: 'gid://gitlab/Ci::PipelineMessage/274', +}; + +function createWarningMock(warnings) { + return { + data: { + project: { + id: 'gid://gitlab/Project/28"', + pipeline: { + id: 'gid://gitlab/Ci::Pipeline/183', + warningMessages: warnings, + }, + }, + }, + }; +} + +export const mockWarningsWithoutDeprecation = createWarningMock([randomWarning]); +export const mockWarningsRootType = createWarningMock([rootTypeWarning]); +export const mockWarningsType = createWarningMock([typeWarning]); +export const mockWarningsTypesAll = createWarningMock([rootTypeWarning, typeWarning]); diff --git a/spec/frontend/pipelines/test_reports/test_case_details_spec.js b/spec/frontend/pipelines/test_reports/test_case_details_spec.js index c995eb864d1..4b33c1522a5 100644 --- a/spec/frontend/pipelines/test_reports/test_case_details_spec.js +++ b/spec/frontend/pipelines/test_reports/test_case_details_spec.js @@ -1,11 +1,9 @@ import { GlModal } from '@gitlab/ui'; -import { shallowMount, createLocalVue } from '@vue/test-utils'; +import { shallowMount } from '@vue/test-utils'; import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import TestCaseDetails from '~/pipelines/components/test_reports/test_case_details.vue'; import CodeBlock from '~/vue_shared/components/code_block.vue'; -const localVue = createLocalVue(); - describe('Test case details', () => { let wrapper; const defaultTestCase = { @@ -29,7 +27,6 @@ describe('Test case details', () => { const createComponent = (testCase = {}) => { wrapper = extendedWrapper( shallowMount(TestCaseDetails, { - localVue, propsData: { modalId: 'my-modal', testCase: { diff --git a/spec/frontend/pipelines/test_reports/test_reports_spec.js b/spec/frontend/pipelines/test_reports/test_reports_spec.js index 384b7cf6930..e0daf8cb4b5 100644 --- a/spec/frontend/pipelines/test_reports/test_reports_spec.js +++ b/spec/frontend/pipelines/test_reports/test_reports_spec.js @@ -1,5 +1,6 @@ import { GlLoadingIcon } from '@gitlab/ui'; -import { shallowMount, createLocalVue } from '@vue/test-utils'; +import { shallowMount } from '@vue/test-utils'; +import Vue from 'vue'; import Vuex from 'vuex'; import testReports from 'test_fixtures/pipelines/test_report.json'; import { extendedWrapper } from 'helpers/vue_test_utils_helper'; @@ -9,8 +10,7 @@ import TestSummary from '~/pipelines/components/test_reports/test_summary.vue'; import TestSummaryTable from '~/pipelines/components/test_reports/test_summary_table.vue'; import * as getters from '~/pipelines/stores/test_reports/getters'; -const localVue = createLocalVue(); -localVue.use(Vuex); +Vue.use(Vuex); describe('Test reports app', () => { let wrapper; @@ -44,7 +44,6 @@ describe('Test reports app', () => { wrapper = extendedWrapper( shallowMount(TestReports, { store, - localVue, }), ); }; diff --git a/spec/frontend/pipelines/test_reports/test_suite_table_spec.js b/spec/frontend/pipelines/test_reports/test_suite_table_spec.js index 793bad6b82a..97241e14129 100644 --- a/spec/frontend/pipelines/test_reports/test_suite_table_spec.js +++ b/spec/frontend/pipelines/test_reports/test_suite_table_spec.js @@ -1,5 +1,6 @@ import { GlButton, GlFriendlyWrap, GlLink, GlPagination } from '@gitlab/ui'; -import { shallowMount, createLocalVue } from '@vue/test-utils'; +import { shallowMount } from '@vue/test-utils'; +import Vue from 'vue'; import Vuex from 'vuex'; import testReports from 'test_fixtures/pipelines/test_report.json'; import SuiteTable from '~/pipelines/components/test_reports/test_suite_table.vue'; @@ -8,8 +9,7 @@ import * as getters from '~/pipelines/stores/test_reports/getters'; import { formatFilePath } from '~/pipelines/stores/test_reports/utils'; import skippedTestCases from './mock_data'; -const localVue = createLocalVue(); -localVue.use(Vuex); +Vue.use(Vuex); describe('Test reports suite table', () => { let wrapper; @@ -47,7 +47,6 @@ describe('Test reports suite table', () => { wrapper = shallowMount(SuiteTable, { store, - localVue, stubs: { GlFriendlyWrap }, }); }; diff --git a/spec/frontend/pipelines/test_reports/test_summary_table_spec.js b/spec/frontend/pipelines/test_reports/test_summary_table_spec.js index 0813739d72f..1598d5c337f 100644 --- a/spec/frontend/pipelines/test_reports/test_summary_table_spec.js +++ b/spec/frontend/pipelines/test_reports/test_summary_table_spec.js @@ -1,11 +1,11 @@ -import { mount, createLocalVue } from '@vue/test-utils'; +import { mount } from '@vue/test-utils'; +import Vue from 'vue'; import Vuex from 'vuex'; import testReports from 'test_fixtures/pipelines/test_report.json'; import SummaryTable from '~/pipelines/components/test_reports/test_summary_table.vue'; import * as getters from '~/pipelines/stores/test_reports/getters'; -const localVue = createLocalVue(); -localVue.use(Vuex); +Vue.use(Vuex); describe('Test reports summary table', () => { let wrapper; @@ -29,7 +29,6 @@ describe('Test reports summary table', () => { wrapper = mount(SummaryTable, { propsData: defaultProps, store, - localVue, }); }; diff --git a/spec/frontend/projects/commits/components/author_select_spec.js b/spec/frontend/projects/commits/components/author_select_spec.js index 23b4cccd92c..eda61758101 100644 --- a/spec/frontend/projects/commits/components/author_select_spec.js +++ b/spec/frontend/projects/commits/components/author_select_spec.js @@ -1,12 +1,12 @@ import { GlDropdown, GlDropdownSectionHeader, GlSearchBoxByType, GlDropdownItem } from '@gitlab/ui'; -import { shallowMount, createLocalVue } from '@vue/test-utils'; +import { shallowMount } from '@vue/test-utils'; +import Vue from 'vue'; import Vuex from 'vuex'; import * as urlUtility from '~/lib/utils/url_utility'; import AuthorSelect from '~/projects/commits/components/author_select.vue'; import { createStore } from '~/projects/commits/store'; -const localVue = createLocalVue(); -localVue.use(Vuex); +Vue.use(Vuex); const commitsPath = 'author/search/url'; const currentAuthor = 'lorem'; @@ -38,7 +38,6 @@ describe('Author Select', () => { `); wrapper = shallowMount(AuthorSelect, { - localVue, store: new Vuex.Store(store), propsData: { projectCommitsEl: document.querySelector('.js-project-commits-show'), diff --git a/spec/frontend/ref/components/ref_selector_spec.js b/spec/frontend/ref/components/ref_selector_spec.js index b486992ac4b..b4e50c8f634 100644 --- a/spec/frontend/ref/components/ref_selector_spec.js +++ b/spec/frontend/ref/components/ref_selector_spec.js @@ -1,5 +1,6 @@ import { GlLoadingIcon, GlSearchBoxByType, GlDropdownItem, GlDropdown, GlIcon } from '@gitlab/ui'; -import { mount, createLocalVue } from '@vue/test-utils'; +import { mount } from '@vue/test-utils'; +import Vue, { nextTick } from 'vue'; import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; import { merge, last } from 'lodash'; @@ -20,8 +21,7 @@ import { } from '~/ref/constants'; import createStore from '~/ref/stores/'; -const localVue = createLocalVue(); -localVue.use(Vuex); +Vue.use(Vuex); describe('Ref selector component', () => { const fixtures = { branches, tags, commit }; @@ -52,7 +52,6 @@ describe('Ref selector component', () => { stubs: { GlSearchBoxByType: true, }, - localVue, store: createStore(), }, mountOverrides, @@ -223,7 +222,7 @@ describe('Ref selector component', () => { it('renders the updated ref name', () => { wrapper.setProps({ value: updatedRef }); - return localVue.nextTick().then(() => { + return nextTick().then(() => { expect(findButtonContent().text()).toBe(updatedRef); }); }); @@ -547,7 +546,7 @@ describe('Ref selector component', () => { await selectFirstBranch(); - return localVue.nextTick().then(() => { + return nextTick().then(() => { expect(findButtonContent().text()).toBe(fixtures.branches[0].name); }); }); @@ -567,7 +566,7 @@ describe('Ref selector component', () => { await selectFirstTag(); - return localVue.nextTick().then(() => { + return nextTick().then(() => { expect(findButtonContent().text()).toBe(fixtures.tags[0].name); }); }); @@ -587,7 +586,7 @@ describe('Ref selector component', () => { await selectFirstCommit(); - return localVue.nextTick().then(() => { + return nextTick().then(() => { expect(findButtonContent().text()).toBe(fixtures.commit.id); }); }); diff --git a/spec/frontend/releases/components/asset_links_form_spec.js b/spec/frontend/releases/components/asset_links_form_spec.js index 839d127e00f..c0f7738bec5 100644 --- a/spec/frontend/releases/components/asset_links_form_spec.js +++ b/spec/frontend/releases/components/asset_links_form_spec.js @@ -1,4 +1,5 @@ -import { mount, createLocalVue } from '@vue/test-utils'; +import { mount } from '@vue/test-utils'; +import Vue from 'vue'; import Vuex from 'vuex'; import originalRelease from 'test_fixtures/api/releases/release.json'; import * as commonUtils from '~/lib/utils/common_utils'; @@ -6,8 +7,7 @@ import { ENTER_KEY } from '~/lib/utils/keys'; import AssetLinksForm from '~/releases/components/asset_links_form.vue'; import { ASSET_LINK_TYPE, DEFAULT_ASSET_LINK_TYPE } from '~/releases/constants'; -const localVue = createLocalVue(); -localVue.use(Vuex); +Vue.use(Vuex); describe('Release edit component', () => { let wrapper; @@ -52,7 +52,6 @@ describe('Release edit component', () => { }); wrapper = mount(AssetLinksForm, { - localVue, store, }); }; diff --git a/spec/frontend/releases/components/releases_pagination_spec.js b/spec/frontend/releases/components/releases_pagination_spec.js index 2d08f72ad8b..b8c69b0ea70 100644 --- a/spec/frontend/releases/components/releases_pagination_spec.js +++ b/spec/frontend/releases/components/releases_pagination_spec.js @@ -1,5 +1,6 @@ import { GlKeysetPagination } from '@gitlab/ui'; -import { mount, createLocalVue } from '@vue/test-utils'; +import { mount } from '@vue/test-utils'; +import Vue from 'vue'; import Vuex from 'vuex'; import { historyPushState } from '~/lib/utils/common_utils'; import ReleasesPagination from '~/releases/components/releases_pagination.vue'; @@ -11,8 +12,7 @@ jest.mock('~/lib/utils/common_utils', () => ({ historyPushState: jest.fn(), })); -const localVue = createLocalVue(); -localVue.use(Vuex); +Vue.use(Vuex); describe('~/releases/components/releases_pagination.vue', () => { let wrapper; @@ -39,7 +39,6 @@ describe('~/releases/components/releases_pagination.vue', () => { }, featureFlags: {}, }), - localVue, }); }; diff --git a/spec/frontend/releases/components/releases_sort_spec.js b/spec/frontend/releases/components/releases_sort_spec.js index b16f80b9c73..7774532bc12 100644 --- a/spec/frontend/releases/components/releases_sort_spec.js +++ b/spec/frontend/releases/components/releases_sort_spec.js @@ -1,12 +1,12 @@ import { GlSorting, GlSortingItem } from '@gitlab/ui'; -import { shallowMount, createLocalVue } from '@vue/test-utils'; +import { shallowMount } from '@vue/test-utils'; +import Vue from 'vue'; import Vuex from 'vuex'; import ReleasesSort from '~/releases/components/releases_sort.vue'; import createStore from '~/releases/stores'; import createIndexModule from '~/releases/stores/modules/index'; -const localVue = createLocalVue(); -localVue.use(Vuex); +Vue.use(Vuex); describe('~/releases/components/releases_sort.vue', () => { let wrapper; @@ -30,7 +30,6 @@ describe('~/releases/components/releases_sort.vue', () => { stubs: { GlSortingItem, }, - localVue, }); }; diff --git a/spec/frontend/releases/components/tag_field_exsting_spec.js b/spec/frontend/releases/components/tag_field_exsting_spec.js index 294538086b4..f45a28392b7 100644 --- a/spec/frontend/releases/components/tag_field_exsting_spec.js +++ b/spec/frontend/releases/components/tag_field_exsting_spec.js @@ -1,5 +1,6 @@ import { GlFormInput } from '@gitlab/ui'; -import { shallowMount, mount, createLocalVue } from '@vue/test-utils'; +import { shallowMount, mount } from '@vue/test-utils'; +import Vue from 'vue'; import Vuex from 'vuex'; import TagFieldExisting from '~/releases/components/tag_field_existing.vue'; import createStore from '~/releases/stores'; @@ -7,8 +8,7 @@ import createEditNewModule from '~/releases/stores/modules/edit_new'; const TEST_TAG_NAME = 'test-tag-name'; -const localVue = createLocalVue(); -localVue.use(Vuex); +Vue.use(Vuex); describe('releases/components/tag_field_existing', () => { let store; @@ -17,7 +17,6 @@ describe('releases/components/tag_field_existing', () => { const createComponent = (mountFn = shallowMount) => { wrapper = mountFn(TagFieldExisting, { store, - localVue, }); }; diff --git a/spec/frontend/reports/accessibility_report/grouped_accessibility_reports_app_spec.js b/spec/frontend/reports/accessibility_report/grouped_accessibility_reports_app_spec.js index b716d54c9fc..34b1cdd92bc 100644 --- a/spec/frontend/reports/accessibility_report/grouped_accessibility_reports_app_spec.js +++ b/spec/frontend/reports/accessibility_report/grouped_accessibility_reports_app_spec.js @@ -1,22 +1,20 @@ -import { mount, createLocalVue } from '@vue/test-utils'; +import { mount } from '@vue/test-utils'; +import Vue from 'vue'; import Vuex from 'vuex'; import AccessibilityIssueBody from '~/reports/accessibility_report/components/accessibility_issue_body.vue'; import GroupedAccessibilityReportsApp from '~/reports/accessibility_report/grouped_accessibility_reports_app.vue'; import { getStoreConfig } from '~/reports/accessibility_report/store'; import { mockReport } from './mock_data'; -const localVue = createLocalVue(); -localVue.use(Vuex); +Vue.use(Vuex); describe('Grouped accessibility reports app', () => { - const Component = localVue.extend(GroupedAccessibilityReportsApp); let wrapper; let mockStore; const mountComponent = () => { - wrapper = mount(Component, { + wrapper = mount(GroupedAccessibilityReportsApp, { store: mockStore, - localVue, propsData: { endpoint: 'endpoint.json', }, diff --git a/spec/frontend/reports/codequality_report/grouped_codequality_reports_app_spec.js b/spec/frontend/reports/codequality_report/grouped_codequality_reports_app_spec.js index 685a1c50a46..1f923f41274 100644 --- a/spec/frontend/reports/codequality_report/grouped_codequality_reports_app_spec.js +++ b/spec/frontend/reports/codequality_report/grouped_codequality_reports_app_spec.js @@ -1,4 +1,5 @@ -import { mount, createLocalVue } from '@vue/test-utils'; +import { mount } from '@vue/test-utils'; +import Vue from 'vue'; import Vuex from 'vuex'; import CodequalityIssueBody from '~/reports/codequality_report/components/codequality_issue_body.vue'; import GroupedCodequalityReportsApp from '~/reports/codequality_report/grouped_codequality_reports_app.vue'; @@ -6,8 +7,7 @@ import { getStoreConfig } from '~/reports/codequality_report/store'; import { STATUS_NOT_FOUND } from '~/reports/constants'; import { parsedReportIssues } from './mock_data'; -const localVue = createLocalVue(); -localVue.use(Vuex); +Vue.use(Vuex); describe('Grouped code quality reports app', () => { let wrapper; @@ -22,7 +22,6 @@ describe('Grouped code quality reports app', () => { const mountComponent = (props = {}) => { wrapper = mount(GroupedCodequalityReportsApp, { store: mockStore, - localVue, propsData: { ...PATHS, ...props, diff --git a/spec/frontend/reports/grouped_test_report/components/test_issue_body_spec.js b/spec/frontend/reports/grouped_test_report/components/test_issue_body_spec.js index 2f6f62ca1d3..8a854a92ad7 100644 --- a/spec/frontend/reports/grouped_test_report/components/test_issue_body_spec.js +++ b/spec/frontend/reports/grouped_test_report/components/test_issue_body_spec.js @@ -1,13 +1,13 @@ import { GlBadge, GlButton } from '@gitlab/ui'; -import { shallowMount, createLocalVue } from '@vue/test-utils'; +import { shallowMount } from '@vue/test-utils'; +import Vue from 'vue'; import Vuex from 'vuex'; import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import IssueStatusIcon from '~/reports/components/issue_status_icon.vue'; import TestIssueBody from '~/reports/grouped_test_report/components/test_issue_body.vue'; import { failedIssue, successIssue } from '../../mock_data/mock_data'; -const localVue = createLocalVue(); -localVue.use(Vuex); +Vue.use(Vuex); describe('Test issue body', () => { let wrapper; @@ -29,7 +29,6 @@ describe('Test issue body', () => { wrapper = extendedWrapper( shallowMount(TestIssueBody, { store, - localVue, propsData: { issue, }, diff --git a/spec/frontend/reports/grouped_test_report/grouped_test_reports_app_spec.js b/spec/frontend/reports/grouped_test_report/grouped_test_reports_app_spec.js index c60c1f7b63c..90edb27d1d6 100644 --- a/spec/frontend/reports/grouped_test_report/grouped_test_reports_app_spec.js +++ b/spec/frontend/reports/grouped_test_report/grouped_test_reports_app_spec.js @@ -1,4 +1,5 @@ -import { mount, createLocalVue } from '@vue/test-utils'; +import { mount } from '@vue/test-utils'; +import Vue from 'vue'; import Vuex from 'vuex'; import Api from '~/api'; import GroupedTestReportsApp from '~/reports/grouped_test_report/grouped_test_reports_app.vue'; @@ -14,8 +15,7 @@ import resolvedFailures from '../mock_data/resolved_failures.json'; jest.mock('~/api.js'); -const localVue = createLocalVue(); -localVue.use(Vuex); +Vue.use(Vuex); describe('Grouped test reports app', () => { const endpoint = 'endpoint.json'; @@ -27,7 +27,6 @@ describe('Grouped test reports app', () => { const mountComponent = ({ props = { pipelinePath } } = {}) => { wrapper = mount(GroupedTestReportsApp, { store: mockStore, - localVue, propsData: { endpoint, headBlobPath, diff --git a/spec/frontend/search/sidebar/components/confidentiality_filter_spec.js b/spec/frontend/search/sidebar/components/confidentiality_filter_spec.js index 3713e1d414f..a377ddae0eb 100644 --- a/spec/frontend/search/sidebar/components/confidentiality_filter_spec.js +++ b/spec/frontend/search/sidebar/components/confidentiality_filter_spec.js @@ -1,11 +1,11 @@ -import { createLocalVue, shallowMount } from '@vue/test-utils'; +import { shallowMount } from '@vue/test-utils'; +import Vue from 'vue'; import Vuex from 'vuex'; import { MOCK_QUERY } from 'jest/search/mock_data'; import ConfidentialityFilter from '~/search/sidebar/components/confidentiality_filter.vue'; import RadioFilter from '~/search/sidebar/components/radio_filter.vue'; -const localVue = createLocalVue(); -localVue.use(Vuex); +Vue.use(Vuex); describe('ConfidentialityFilter', () => { let wrapper; @@ -25,7 +25,6 @@ describe('ConfidentialityFilter', () => { }); wrapper = shallowMount(ConfidentialityFilter, { - localVue, store, }); }; diff --git a/spec/frontend/search/sidebar/components/radio_filter_spec.js b/spec/frontend/search/sidebar/components/radio_filter_spec.js index 4c81312e479..39d5ee581ec 100644 --- a/spec/frontend/search/sidebar/components/radio_filter_spec.js +++ b/spec/frontend/search/sidebar/components/radio_filter_spec.js @@ -1,13 +1,13 @@ import { GlFormRadioGroup, GlFormRadio } from '@gitlab/ui'; -import { createLocalVue, shallowMount } from '@vue/test-utils'; +import { shallowMount } from '@vue/test-utils'; +import Vue from 'vue'; import Vuex from 'vuex'; import { MOCK_QUERY } from 'jest/search/mock_data'; import RadioFilter from '~/search/sidebar/components/radio_filter.vue'; import { confidentialFilterData } from '~/search/sidebar/constants/confidential_filter_data'; import { stateFilterData } from '~/search/sidebar/constants/state_filter_data'; -const localVue = createLocalVue(); -localVue.use(Vuex); +Vue.use(Vuex); describe('RadioFilter', () => { let wrapper; @@ -30,7 +30,6 @@ describe('RadioFilter', () => { }); wrapper = shallowMount(RadioFilter, { - localVue, store, propsData: { ...defaultProps, diff --git a/spec/frontend/search/sidebar/components/status_filter_spec.js b/spec/frontend/search/sidebar/components/status_filter_spec.js index 08ce57b206b..5d8ecd8733a 100644 --- a/spec/frontend/search/sidebar/components/status_filter_spec.js +++ b/spec/frontend/search/sidebar/components/status_filter_spec.js @@ -1,11 +1,11 @@ -import { createLocalVue, shallowMount } from '@vue/test-utils'; +import { shallowMount } from '@vue/test-utils'; +import Vue from 'vue'; import Vuex from 'vuex'; import { MOCK_QUERY } from 'jest/search/mock_data'; import RadioFilter from '~/search/sidebar/components/radio_filter.vue'; import StatusFilter from '~/search/sidebar/components/status_filter.vue'; -const localVue = createLocalVue(); -localVue.use(Vuex); +Vue.use(Vuex); describe('StatusFilter', () => { let wrapper; @@ -25,7 +25,6 @@ describe('StatusFilter', () => { }); wrapper = shallowMount(StatusFilter, { - localVue, store, }); }; diff --git a/spec/frontend/search/sort/components/app_spec.js b/spec/frontend/search/sort/components/app_spec.js index 5806d6b51d2..04520a3e704 100644 --- a/spec/frontend/search/sort/components/app_spec.js +++ b/spec/frontend/search/sort/components/app_spec.js @@ -1,12 +1,12 @@ import { GlButtonGroup, GlButton, GlDropdown, GlDropdownItem } from '@gitlab/ui'; -import { createLocalVue, shallowMount } from '@vue/test-utils'; +import { shallowMount } from '@vue/test-utils'; +import Vue from 'vue'; import Vuex from 'vuex'; import { MOCK_QUERY, MOCK_SORT_OPTIONS } from 'jest/search/mock_data'; import GlobalSearchSort from '~/search/sort/components/app.vue'; import { SORT_DIRECTION_UI } from '~/search/sort/constants'; -const localVue = createLocalVue(); -localVue.use(Vuex); +Vue.use(Vuex); describe('GlobalSearchSort', () => { let wrapper; @@ -30,7 +30,6 @@ describe('GlobalSearchSort', () => { }); wrapper = shallowMount(GlobalSearchSort, { - localVue, store, propsData: { ...defaultProps, diff --git a/spec/frontend/serverless/components/function_details_spec.js b/spec/frontend/serverless/components/function_details_spec.js index d2b8de71e01..0c9b2498589 100644 --- a/spec/frontend/serverless/components/function_details_spec.js +++ b/spec/frontend/serverless/components/function_details_spec.js @@ -1,17 +1,16 @@ -import { createLocalVue, shallowMount } from '@vue/test-utils'; +import { shallowMount } from '@vue/test-utils'; +import Vue from 'vue'; import Vuex from 'vuex'; import functionDetailsComponent from '~/serverless/components/function_details.vue'; import { createStore } from '~/serverless/store'; describe('functionDetailsComponent', () => { - let localVue; let component; let store; beforeEach(() => { - localVue = createLocalVue(); - localVue.use(Vuex); + Vue.use(Vuex); store = createStore({ clustersPath: '/clusters', helpPath: '/help' }); }); @@ -33,7 +32,6 @@ describe('functionDetailsComponent', () => { it('has a name, description, URL, and no pods loaded', () => { component = shallowMount(functionDetailsComponent, { - localVue, store, propsData: { func: serviceStub, @@ -58,7 +56,6 @@ describe('functionDetailsComponent', () => { serviceStub.podcount = 1; component = shallowMount(functionDetailsComponent, { - localVue, store, propsData: { func: serviceStub, @@ -73,7 +70,6 @@ describe('functionDetailsComponent', () => { serviceStub.podcount = 3; component = shallowMount(functionDetailsComponent, { - localVue, store, propsData: { func: serviceStub, @@ -88,7 +84,6 @@ describe('functionDetailsComponent', () => { serviceStub.description = null; component = shallowMount(functionDetailsComponent, { - localVue, store, propsData: { func: serviceStub, diff --git a/spec/frontend/serverless/components/functions_spec.js b/spec/frontend/serverless/components/functions_spec.js index 01dd512c5d3..1e33e56c2f3 100644 --- a/spec/frontend/serverless/components/functions_spec.js +++ b/spec/frontend/serverless/components/functions_spec.js @@ -1,5 +1,6 @@ import { GlLoadingIcon } from '@gitlab/ui'; -import { createLocalVue, shallowMount } from '@vue/test-utils'; +import { shallowMount } from '@vue/test-utils'; +import Vue from 'vue'; import AxiosMockAdapter from 'axios-mock-adapter'; import Vuex from 'vuex'; import { TEST_HOST } from 'helpers/test_constants'; @@ -15,15 +16,13 @@ describe('functionsComponent', () => { let component; let store; - let localVue; let axiosMock; beforeEach(() => { axiosMock = new AxiosMockAdapter(axios); axiosMock.onGet(statusPath).reply(200); - localVue = createLocalVue(); - localVue.use(Vuex); + Vue.use(Vuex); store = createStore({}); }); @@ -35,21 +34,21 @@ describe('functionsComponent', () => { it('should render empty state when Knative is not installed', () => { store.dispatch('receiveFunctionsSuccess', { knative_installed: false }); - component = shallowMount(functionsComponent, { localVue, store }); + component = shallowMount(functionsComponent, { store }); expect(component.find(EmptyState).exists()).toBe(true); }); it('should render a loading component', () => { store.dispatch('requestFunctionsLoading'); - component = shallowMount(functionsComponent, { localVue, store }); + component = shallowMount(functionsComponent, { store }); expect(component.find(GlLoadingIcon).exists()).toBe(true); }); it('should render empty state when there is no function data', () => { store.dispatch('receiveFunctionsNoDataSuccess', { knative_installed: true }); - component = shallowMount(functionsComponent, { localVue, store }); + component = shallowMount(functionsComponent, { store }); expect( component.vm.$el @@ -68,7 +67,7 @@ describe('functionsComponent', () => { knative_installed: 'checking', }); - component = shallowMount(functionsComponent, { localVue, store }); + component = shallowMount(functionsComponent, { store }); expect(component.find('.js-functions-wrapper').exists()).toBe(true); expect(component.find('.js-functions-loader').exists()).toBe(true); @@ -77,7 +76,7 @@ describe('functionsComponent', () => { it('should render the functions list', () => { store = createStore({ clustersPath: 'clustersPath', helpPath: 'helpPath', statusPath }); - component = shallowMount(functionsComponent, { localVue, store }); + component = shallowMount(functionsComponent, { store }); component.vm.$store.dispatch('receiveFunctionsSuccess', mockServerlessFunctions); diff --git a/spec/frontend/static_site_editor/pages/home_spec.js b/spec/frontend/static_site_editor/pages/home_spec.js index eb056469603..4f6f2bb3c76 100644 --- a/spec/frontend/static_site_editor/pages/home_spec.js +++ b/spec/frontend/static_site_editor/pages/home_spec.js @@ -1,4 +1,4 @@ -import { shallowMount, createLocalVue } from '@vue/test-utils'; +import { shallowMount } from '@vue/test-utils'; import { mockTracking, unmockTracking } from 'helpers/tracking_helper'; import EditArea from '~/static_site_editor/components/edit_area.vue'; import EditMetaModal from '~/static_site_editor/components/edit_meta_modal.vue'; @@ -29,8 +29,6 @@ import { imageRoot, } from '../mock_data'; -const localVue = createLocalVue(); - describe('static_site_editor/pages/home', () => { let wrapper; let store; @@ -78,7 +76,6 @@ describe('static_site_editor/pages/home', () => { const buildWrapper = (data = {}) => { wrapper = shallowMount(Home, { - localVue, store, mocks: { $apollo, diff --git a/spec/frontend/user_lists/components/edit_user_list_spec.js b/spec/frontend/user_lists/components/edit_user_list_spec.js index bd71a677a24..4731f4b9c02 100644 --- a/spec/frontend/user_lists/components/edit_user_list_spec.js +++ b/spec/frontend/user_lists/components/edit_user_list_spec.js @@ -1,5 +1,5 @@ import { GlAlert, GlLoadingIcon } from '@gitlab/ui'; -import { createLocalVue, mount } from '@vue/test-utils'; +import { mount } from '@vue/test-utils'; import Vue from 'vue'; import Vuex from 'vuex'; import waitForPromises from 'helpers/wait_for_promises'; @@ -13,8 +13,7 @@ import { userList } from '../../feature_flags/mock_data'; jest.mock('~/api'); jest.mock('~/lib/utils/url_utility'); -const localVue = createLocalVue(Vue); -localVue.use(Vuex); +Vue.use(Vuex); describe('user_lists/components/edit_user_list', () => { let wrapper; @@ -30,7 +29,6 @@ describe('user_lists/components/edit_user_list', () => { destroy(); wrapper = mount(EditUserList, { - localVue, store: createStore({ projectId: '1', userListIid: '2' }), provide: { userListsDocsPath: '/docs/user_lists', diff --git a/spec/frontend/user_lists/components/new_user_list_spec.js b/spec/frontend/user_lists/components/new_user_list_spec.js index a81e8912714..1cce53c7963 100644 --- a/spec/frontend/user_lists/components/new_user_list_spec.js +++ b/spec/frontend/user_lists/components/new_user_list_spec.js @@ -1,5 +1,5 @@ import { GlAlert } from '@gitlab/ui'; -import { mount, createLocalVue } from '@vue/test-utils'; +import { mount } from '@vue/test-utils'; import Vue from 'vue'; import Vuex from 'vuex'; import waitForPromises from 'helpers/wait_for_promises'; @@ -12,8 +12,7 @@ import { userList } from '../../feature_flags/mock_data'; jest.mock('~/api'); jest.mock('~/lib/utils/url_utility'); -const localVue = createLocalVue(Vue); -localVue.use(Vuex); +Vue.use(Vuex); describe('user_lists/components/new_user_list', () => { let wrapper; @@ -24,7 +23,6 @@ describe('user_lists/components/new_user_list', () => { beforeEach(() => { wrapper = mount(NewUserList, { - localVue, store: createStore({ projectId: '1' }), provide: { featureFlagsPath: '/feature_flags', diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js index 6abdbd11f5e..6ea2e8675d3 100644 --- a/spec/frontend/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js +++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js @@ -1,16 +1,13 @@ import { GlFormCheckbox, GlLink } from '@gitlab/ui'; -import { createLocalVue, shallowMount } from '@vue/test-utils'; +import { shallowMount } from '@vue/test-utils'; import SquashBeforeMerge from '~/vue_merge_request_widget/components/states/squash_before_merge.vue'; import { SQUASH_BEFORE_MERGE } from '~/vue_merge_request_widget/i18n'; -const localVue = createLocalVue(); - describe('Squash before merge component', () => { let wrapper; const createComponent = (props) => { - wrapper = shallowMount(localVue.extend(SquashBeforeMerge), { - localVue, + wrapper = shallowMount(SquashBeforeMerge, { propsData: { ...props, }, diff --git a/spec/frontend/whats_new/components/app_spec.js b/spec/frontend/whats_new/components/app_spec.js index 12034346aba..45f28c2c51b 100644 --- a/spec/frontend/whats_new/components/app_spec.js +++ b/spec/frontend/whats_new/components/app_spec.js @@ -1,5 +1,6 @@ import { GlDrawer, GlInfiniteScroll } from '@gitlab/ui'; -import { createLocalVue, mount } from '@vue/test-utils'; +import { mount } from '@vue/test-utils'; +import Vue from 'vue'; import Vuex from 'vuex'; import { mockTracking, unmockTracking, triggerEvent } from 'helpers/tracking_helper'; import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; @@ -12,8 +13,7 @@ jest.mock('~/whats_new/utils/get_drawer_body_height', () => ({ getDrawerBodyHeight: jest.fn().mockImplementation(() => MOCK_DRAWER_BODY_HEIGHT), })); -const localVue = createLocalVue(); -localVue.use(Vuex); +Vue.use(Vuex); describe('App', () => { let wrapper; @@ -46,7 +46,6 @@ describe('App', () => { }); wrapper = mount(App, { - localVue, store, propsData: buildProps(), directives: { diff --git a/spec/lib/gitlab/ci/config/external/mapper_spec.rb b/spec/lib/gitlab/ci/config/external/mapper_spec.rb index cebe8984741..f8754d7e124 100644 --- a/spec/lib/gitlab/ci/config/external/mapper_spec.rb +++ b/spec/lib/gitlab/ci/config/external/mapper_spec.rb @@ -175,27 +175,35 @@ RSpec.describe Gitlab::Ci::Config::External::Mapper do end end - context "when duplicate 'include' is defined" do + context "when duplicate 'include's are defined" do + let(:values) do + { include: [ + { 'local' => local_file }, + { 'local' => local_file } + ], + image: 'ruby:2.7' } + end + + it 'does not raise an exception' do + expect { subject }.not_to raise_error + end + end + + context 'when passing max number of files' do let(:values) do { include: [ { 'local' => local_file }, - { 'local' => local_file } + { 'remote' => remote_url } ], image: 'ruby:2.7' } end - it 'raises an exception' do - expect { subject }.to raise_error(described_class::DuplicateIncludesError) + before do + stub_const("#{described_class}::MAX_INCLUDES", 2) end - context 'when including multiple files from a project' do - let(:values) do - { include: { project: project.full_path, file: [local_file, local_file] } } - end - - it 'raises an exception' do - expect { subject }.to raise_error(described_class::DuplicateIncludesError) - end + it 'does not raise an exception' do + expect { subject }.not_to raise_error end end diff --git a/spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb b/spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb index 03634ebeeb9..ab584f92716 100644 --- a/spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb +++ b/spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb @@ -44,7 +44,6 @@ RSpec.describe 'cross-database foreign keys' do dast_site_profiles_pipelines.ci_pipeline_id external_pull_requests.project_id vulnerability_feedback.pipeline_id - vulnerability_statistics.latest_pipeline_id ).freeze end diff --git a/spec/lib/gitlab/rack_attack/request_spec.rb b/spec/lib/gitlab/rack_attack/request_spec.rb index 72592832a89..ecdcc23e588 100644 --- a/spec/lib/gitlab/rack_attack/request_spec.rb +++ b/spec/lib/gitlab/rack_attack/request_spec.rb @@ -5,19 +5,6 @@ require 'spec_helper' RSpec.describe Gitlab::RackAttack::Request do using RSpec::Parameterized::TableSyntax - let(:env) { {} } - let(:session) { {} } - let(:request) do - ::Rack::Attack::Request.new( - env.reverse_merge( - 'REQUEST_METHOD' => 'GET', - 'PATH_INFO' => path, - 'rack.input' => StringIO.new, - 'rack.session' => session - ) - ) - end - describe 'FILES_PATH_REGEX' do subject { described_class::FILES_PATH_REGEX } @@ -29,80 +16,11 @@ RSpec.describe Gitlab::RackAttack::Request do it { is_expected.not_to match('/api/v4/projects/some/nested/repo/repository/files/README') } end - describe '#api_request?' do - subject { request.api_request? } - - where(:path, :expected) do - '/' | false - '/groups' | false - - '/api' | true - '/api/v4/groups/1' | true - end - - with_them do - it { is_expected.to eq(expected) } - end - end - - describe '#web_request?' do - subject { request.web_request? } - - where(:path, :expected) do - '/' | true - '/groups' | true - - '/api' | false - '/api/v4/groups/1' | false - end - - with_them do - it { is_expected.to eq(expected) } - end - end - - describe '#frontend_request?', :allow_forgery_protection do - subject { request.send(:frontend_request?) } - - let(:path) { '/' } - - # Define these as local variables so we can use them in the `where` block. - valid_token = SecureRandom.base64(ActionController::RequestForgeryProtection::AUTHENTICITY_TOKEN_LENGTH) - other_token = SecureRandom.base64(ActionController::RequestForgeryProtection::AUTHENTICITY_TOKEN_LENGTH) - - where(:session, :env, :expected) do - {} | {} | false # rubocop:disable Lint/BinaryOperatorWithIdenticalOperands - {} | { 'HTTP_X_CSRF_TOKEN' => valid_token } | false - { _csrf_token: valid_token } | { 'HTTP_X_CSRF_TOKEN' => other_token } | false - { _csrf_token: valid_token } | { 'HTTP_X_CSRF_TOKEN' => valid_token } | true - end - - with_them do - it { is_expected.to eq(expected) } - end - - context 'when the feature flag is disabled' do - before do - stub_feature_flags(rate_limit_frontend_requests: false) - end - - where(:session, :env) do - {} | {} # rubocop:disable Lint/BinaryOperatorWithIdenticalOperands - {} | { 'HTTP_X_CSRF_TOKEN' => valid_token } - { _csrf_token: valid_token } | { 'HTTP_X_CSRF_TOKEN' => other_token } - { _csrf_token: valid_token } | { 'HTTP_X_CSRF_TOKEN' => valid_token } - end - - with_them do - it { is_expected.to be(false) } - end - end - end - describe '#deprecated_api_request?' do - subject { request.send(:deprecated_api_request?) } + let(:env) { { 'REQUEST_METHOD' => 'GET', 'rack.input' => StringIO.new, 'PATH_INFO' => path, 'QUERY_STRING' => query } } + let(:request) { ::Rack::Attack::Request.new(env) } - let(:env) { { 'QUERY_STRING' => query } } + subject { !!request.__send__(:deprecated_api_request?) } where(:path, :query, :expected) do '/' | '' | false diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb index 07c8f76cbbf..2bc642f8b14 100644 --- a/spec/requests/api/commits_spec.rb +++ b/spec/requests/api/commits_spec.rb @@ -5,7 +5,6 @@ require 'mime/types' RSpec.describe API::Commits do include ProjectForksHelper - include SessionHelpers let(:user) { create(:user) } let(:guest) { create(:user).tap { |u| project.add_guest(u) } } @@ -379,7 +378,14 @@ RSpec.describe API::Commits do context 'when using warden' do it 'increments usage counters', :clean_gitlab_redis_sessions do - stub_session('warden.user.user.key' => [[user.id], user.encrypted_password[0, 29]]) + session_id = Rack::Session::SessionId.new('6919a6f1bb119dd7396fadc38fd18d0d') + session_hash = { 'warden.user.user.key' => [[user.id], user.encrypted_password[0, 29]] } + + Gitlab::Redis::Sessions.with do |redis| + redis.set("session:gitlab:#{session_id.private_id}", Marshal.dump(session_hash)) + end + + cookies[Gitlab::Application.config.session_options[:key]] = session_id.public_id expect(::Gitlab::UsageDataCounters::WebIdeCounter).to receive(:increment_commits_count) expect(::Gitlab::UsageDataCounters::EditorUniqueCounter).to receive(:track_web_ide_edit_action) diff --git a/spec/requests/api/graphql/project/cluster_agents_spec.rb b/spec/requests/api/graphql/project/cluster_agents_spec.rb index 585126f3849..c9900fea277 100644 --- a/spec/requests/api/graphql/project/cluster_agents_spec.rb +++ b/spec/requests/api/graphql/project/cluster_agents_spec.rb @@ -126,7 +126,7 @@ RSpec.describe 'Project.cluster_agents' do }) end - it 'preloads associations to prevent N+1 queries' do + it 'preloads associations to prevent N+1 queries', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/350868' do user = create(:user) token = create(:cluster_agent_token, agent: agents.second) create(:agent_activity_event, agent: agents.second, agent_token: token, user: user) diff --git a/spec/requests/rack_attack_global_spec.rb b/spec/requests/rack_attack_global_spec.rb index f8c8351e1b3..793438808a5 100644 --- a/spec/requests/rack_attack_global_spec.rb +++ b/spec/requests/rack_attack_global_spec.rb @@ -4,7 +4,6 @@ require 'spec_helper' RSpec.describe 'Rack Attack global throttles', :use_clean_rails_memory_store_caching do include RackAttackSpecHelpers - include SessionHelpers let(:settings) { Gitlab::CurrentSettings.current_application_settings } @@ -64,22 +63,6 @@ RSpec.describe 'Rack Attack global throttles', :use_clean_rails_memory_store_cac end end - describe 'API requests from the frontend', :api, :clean_gitlab_redis_sessions do - context 'when unauthenticated' do - it_behaves_like 'rate-limited frontend API requests' do - let(:throttle_setting_prefix) { 'throttle_unauthenticated' } - end - end - - context 'when authenticated' do - it_behaves_like 'rate-limited frontend API requests' do - let_it_be(:personal_access_token) { create(:personal_access_token) } - - let(:throttle_setting_prefix) { 'throttle_authenticated' } - end - end - end - describe 'API requests authenticated with personal access token', :api do let_it_be(:user) { create(:user) } let_it_be(:token) { create(:personal_access_token, user: user) } diff --git a/spec/services/alert_management/alerts/update_service_spec.rb b/spec/services/alert_management/alerts/update_service_spec.rb index 35697ac79a0..882543fd701 100644 --- a/spec/services/alert_management/alerts/update_service_spec.rb +++ b/spec/services/alert_management/alerts/update_service_spec.rb @@ -28,8 +28,11 @@ RSpec.describe AlertManagement::Alerts::UpdateService do specify { expect { response }.not_to change(Note, :count) } end - shared_examples 'adds a system note' do - specify { expect { response }.to change { alert.reload.notes.count }.by(1) } + shared_examples 'adds a system note' do |note_matcher = nil| + specify do + expect { response }.to change { alert.reload.notes.count }.by(1) + expect(alert.notes.last.note).to match(note_matcher) if note_matcher + end end shared_examples 'error response' do |message| @@ -288,6 +291,12 @@ RSpec.describe AlertManagement::Alerts::UpdateService do end end end + + context 'when a status change reason is included' do + let(:params) { { status: new_status, status_change_reason: ' by changing the incident status' } } + + it_behaves_like 'adds a system note', /changed the status to \*\*Acknowledged\*\* by changing the incident status/ + end end end end diff --git a/spec/services/incident_management/issuable_escalation_statuses/after_update_service_spec.rb b/spec/services/incident_management/issuable_escalation_statuses/after_update_service_spec.rb index e9db6ba8d28..731406613dd 100644 --- a/spec/services/incident_management/issuable_escalation_statuses/after_update_service_spec.rb +++ b/spec/services/incident_management/issuable_escalation_statuses/after_update_service_spec.rb @@ -30,7 +30,15 @@ RSpec.describe IncidentManagement::IssuableEscalationStatuses::AfterUpdateServic end end + shared_examples 'adds a status change system note' do + specify do + expect { result }.to change { issue.reload.notes.count }.by(1) + end + end + context 'with status attributes' do + it_behaves_like 'adds a status change system note' + it 'updates the alert with the new alert status' do expect(::AlertManagement::Alerts::UpdateService).to receive(:new).once.and_call_original expect(described_class).to receive(:new).once.and_call_original @@ -45,12 +53,15 @@ RSpec.describe IncidentManagement::IssuableEscalationStatuses::AfterUpdateServic end it_behaves_like 'does not attempt to update the alert' + it_behaves_like 'adds a status change system note' end context 'when new status matches the current status' do let(:status_event) { :trigger } it_behaves_like 'does not attempt to update the alert' + + specify { expect { result }.not_to change { issue.reload.notes.count } } end end end diff --git a/spec/services/incident_management/issuable_escalation_statuses/prepare_update_service_spec.rb b/spec/services/incident_management/issuable_escalation_statuses/prepare_update_service_spec.rb index bfed5319028..e5717108703 100644 --- a/spec/services/incident_management/issuable_escalation_statuses/prepare_update_service_spec.rb +++ b/spec/services/incident_management/issuable_escalation_statuses/prepare_update_service_spec.rb @@ -105,4 +105,10 @@ RSpec.describe IncidentManagement::IssuableEscalationStatuses::PrepareUpdateServ it_behaves_like 'successful response', { status_event: :acknowledge } end end + + context 'with status_change_reason param' do + let(:params) { { status_change_reason: ' by changing the incident status' } } + + it_behaves_like 'successful response', { status_change_reason: ' by changing the incident status' } + end end diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb index 11ed47b84d9..c53231c7042 100644 --- a/spec/services/issues/update_service_spec.rb +++ b/spec/services/issues/update_service_spec.rb @@ -1147,11 +1147,11 @@ RSpec.describe Issues::UpdateService, :mailer do let(:opts) { { escalation_status: { status: 'acknowledged' } } } let(:escalation_update_class) { ::IncidentManagement::IssuableEscalationStatuses::AfterUpdateService } - shared_examples 'updates the escalation status record' do |expected_status| + shared_examples 'updates the escalation status record' do |expected_status, expected_reason = nil| let(:service_double) { instance_double(escalation_update_class) } it 'has correct value' do - expect(escalation_update_class).to receive(:new).with(issue, user).and_return(service_double) + expect(escalation_update_class).to receive(:new).with(issue, user, status_change_reason: expected_reason).and_return(service_double) expect(service_double).to receive(:execute) update_issue(opts) @@ -1197,6 +1197,12 @@ RSpec.describe Issues::UpdateService, :mailer do end end + context 'with a status change reason provided' do + let(:opts) { { escalation_status: { status: 'acknowledged', status_change_reason: ' by changing the alert status' } } } + + it_behaves_like 'updates the escalation status record', :acknowledged, ' by changing the alert status' + end + context 'with unsupported status value' do let(:opts) { { escalation_status: { status: 'unsupported-status' } } } diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index 3ec2c71b20c..bbf8735add6 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -572,12 +572,26 @@ RSpec.describe SystemNoteService do describe '.change_alert_status' do let(:alert) { build(:alert_management_alert) } - it 'calls AlertManagementService' do - expect_next_instance_of(SystemNotes::AlertManagementService) do |service| - expect(service).to receive(:change_alert_status).with(alert) + context 'with status change reason' do + let(:reason) { 'reason for status change' } + + it 'calls AlertManagementService' do + expect_next_instance_of(SystemNotes::AlertManagementService) do |service| + expect(service).to receive(:change_alert_status).with(reason) + end + + described_class.change_alert_status(alert, author, reason) end + end - described_class.change_alert_status(alert, author) + context 'without status change reason' do + it 'calls AlertManagementService' do + expect_next_instance_of(SystemNotes::AlertManagementService) do |service| + expect(service).to receive(:change_alert_status).with(nil) + end + + described_class.change_alert_status(alert, author) + end end end @@ -630,6 +644,32 @@ RSpec.describe SystemNoteService do end end + describe '.change_incident_status' do + let(:incident) { instance_double('Issue', project: project) } + + context 'with status change reason' do + let(:reason) { 'reason for status change' } + + it 'calls IncidentService' do + expect_next_instance_of(SystemNotes::IncidentService) do |service| + expect(service).to receive(:change_incident_status).with(reason) + end + + described_class.change_incident_status(incident, author, reason) + end + end + + context 'without status change reason' do + it 'calls IncidentService' do + expect_next_instance_of(SystemNotes::IncidentService) do |service| + expect(service).to receive(:change_incident_status).with(nil) + end + + described_class.change_incident_status(incident, author) + end + end + end + describe '.log_resolving_alert' do let(:alert) { build(:alert_management_alert) } let(:monitoring_tool) { 'Prometheus' } diff --git a/spec/services/system_notes/alert_management_service_spec.rb b/spec/services/system_notes/alert_management_service_spec.rb index 6e6bfeaa205..cd7393def0c 100644 --- a/spec/services/system_notes/alert_management_service_spec.rb +++ b/spec/services/system_notes/alert_management_service_spec.rb @@ -21,14 +21,26 @@ RSpec.describe ::SystemNotes::AlertManagementService do end describe '#change_alert_status' do - subject { described_class.new(noteable: noteable, project: project, author: author).change_alert_status(noteable) } + subject { described_class.new(noteable: noteable, project: project, author: author).change_alert_status(reason) } - it_behaves_like 'a system note' do - let(:action) { 'status' } + context 'with no specified reason' do + let(:reason) { nil } + + it_behaves_like 'a system note' do + let(:action) { 'status' } + end + + it 'has the appropriate message' do + expect(subject.note).to eq("changed the status to **Acknowledged**") + end end - it 'has the appropriate message' do - expect(subject.note).to eq("changed the status to **Acknowledged**") + context 'with reason provided' do + let(:reason) { ' by changing incident status' } + + it 'has the appropriate message' do + expect(subject.note).to eq("changed the status to **Acknowledged** by changing incident status") + end end end diff --git a/spec/services/system_notes/incident_service_spec.rb b/spec/services/system_notes/incident_service_spec.rb index 669e357b7a4..d1addaa3b8d 100644 --- a/spec/services/system_notes/incident_service_spec.rb +++ b/spec/services/system_notes/incident_service_spec.rb @@ -66,4 +66,28 @@ RSpec.describe ::SystemNotes::IncidentService do expect(noteable.notes.last.note).to eq('changed the status to **Resolved** by closing the incident') end end + + describe '#change_incident_status' do + let_it_be(:escalation_status) { create(:incident_management_issuable_escalation_status, issue: noteable) } + + let(:service) { described_class.new(noteable: noteable, project: project, author: author) } + + context 'with a provided reason' do + subject(:change_incident_status) { service.change_incident_status(' by changing the alert status') } + + it 'creates a new note for an incident status change', :aggregate_failures do + expect { change_incident_status }.to change { noteable.notes.count }.by(1) + expect(noteable.notes.last.note).to eq("changed the incident status to **Triggered** by changing the alert status") + end + end + + context 'without provided reason' do + subject(:change_incident_status) { service.change_incident_status(nil) } + + it 'creates a new note for an incident status change', :aggregate_failures do + expect { change_incident_status }.to change { noteable.notes.count }.by(1) + expect(noteable.notes.last.note).to eq("changed the incident status to **Triggered**") + end + end + end end diff --git a/spec/support/helpers/rack_attack_spec_helpers.rb b/spec/support/helpers/rack_attack_spec_helpers.rb index c82a87dc58e..d50a6382a40 100644 --- a/spec/support/helpers/rack_attack_spec_helpers.rb +++ b/spec/support/helpers/rack_attack_spec_helpers.rb @@ -26,14 +26,14 @@ module RackAttackSpecHelpers { 'AUTHORIZATION' => "Basic #{encoded_login}" } end - def expect_rejection(name = nil, &block) + def expect_rejection(&block) yield expect(response).to have_gitlab_http_status(:too_many_requests) expect(response.headers.to_h).to include( 'RateLimit-Limit' => a_string_matching(/^\d+$/), - 'RateLimit-Name' => name || a_string_matching(/^throttle_.*$/), + 'RateLimit-Name' => a_string_matching(/^throttle_.*$/), 'RateLimit-Observed' => a_string_matching(/^\d+$/), 'RateLimit-Remaining' => a_string_matching(/^\d+$/), 'Retry-After' => a_string_matching(/^\d+$/) diff --git a/spec/support/helpers/session_helpers.rb b/spec/support/helpers/session_helpers.rb index 394a401afca..236585296e5 100644 --- a/spec/support/helpers/session_helpers.rb +++ b/spec/support/helpers/session_helpers.rb @@ -1,22 +1,6 @@ # frozen_string_literal: true module SessionHelpers - # Stub a session in Redis, for use in request specs where we can't mock the session directly. - # This also needs the :clean_gitlab_redis_sessions tag on the spec. - def stub_session(session_hash) - unless RSpec.current_example.metadata[:clean_gitlab_redis_sessions] - raise 'Add :clean_gitlab_redis_sessions to your spec!' - end - - session_id = Rack::Session::SessionId.new(SecureRandom.hex) - - Gitlab::Redis::Sessions.with do |redis| - redis.set("session:gitlab:#{session_id.private_id}", Marshal.dump(session_hash)) - end - - cookies[Gitlab::Application.config.session_options[:key]] = session_id.public_id - end - def expect_single_session_with_authenticated_ttl expect_single_session_with_expiration(Settings.gitlab['session_expire_delay'] * 60) end diff --git a/spec/support/shared_examples/requests/api/graphql/mutations/snippets_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/mutations/snippets_shared_examples.rb index a42a1fda62e..8bffd1f71e9 100644 --- a/spec/support/shared_examples/requests/api/graphql/mutations/snippets_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/graphql/mutations/snippets_shared_examples.rb @@ -10,8 +10,6 @@ RSpec.shared_examples 'when the snippet is not found' do end RSpec.shared_examples 'snippet edit usage data counters' do - include SessionHelpers - context 'when user is sessionless' do it 'does not track usage data actions' do expect(::Gitlab::UsageDataCounters::EditorUniqueCounter).not_to receive(:track_snippet_editor_edit_action) @@ -22,7 +20,14 @@ RSpec.shared_examples 'snippet edit usage data counters' do context 'when user is not sessionless', :clean_gitlab_redis_sessions do before do - stub_session('warden.user.user.key' => [[current_user.id], current_user.encrypted_password[0, 29]]) + session_id = Rack::Session::SessionId.new('6919a6f1bb119dd7396fadc38fd18d0d') + session_hash = { 'warden.user.user.key' => [[current_user.id], current_user.encrypted_password[0, 29]] } + + Gitlab::Redis::Sessions.with do |redis| + redis.set("session:gitlab:#{session_id.private_id}", Marshal.dump(session_hash)) + end + + cookies[Gitlab::Application.config.session_options[:key]] = session_id.public_id end it 'tracks usage data actions', :clean_gitlab_redis_sessions do diff --git a/spec/support/shared_examples/requests/rack_attack_shared_examples.rb b/spec/support/shared_examples/requests/rack_attack_shared_examples.rb index c6c6c44dce8..b294467d482 100644 --- a/spec/support/shared_examples/requests/rack_attack_shared_examples.rb +++ b/spec/support/shared_examples/requests/rack_attack_shared_examples.rb @@ -580,88 +580,3 @@ RSpec.shared_examples 'rate-limited unauthenticated requests' do end end end - -# Requires let variables: -# * throttle_setting_prefix: "throttle_authenticated", "throttle_unauthenticated" -RSpec.shared_examples 'rate-limited frontend API requests' do - let(:requests_per_period) { 1 } - let(:csrf_token) { SecureRandom.base64(ActionController::RequestForgeryProtection::AUTHENTICITY_TOKEN_LENGTH) } - let(:csrf_session) { { _csrf_token: csrf_token } } - let(:personal_access_token) { nil } - - let(:api_path) { '/projects' } - - # These don't actually exist, so a 404 is the expected response. - let(:files_api_path) { '/projects/1/repository/files/ref/path' } - let(:packages_api_path) { '/projects/1/packages/foo' } - let(:deprecated_api_path) { '/groups/1?with_projects=true' } - - def get_api(path: api_path, csrf: false) - headers = csrf ? { 'X-CSRF-Token' => csrf_token } : nil - get api(path, personal_access_token: personal_access_token), headers: headers - end - - def expect_not_found(&block) - yield - - expect(response).to have_gitlab_http_status(:not_found) - end - - before do - stub_application_setting( - "#{throttle_setting_prefix}_enabled" => true, - "#{throttle_setting_prefix}_requests_per_period" => requests_per_period, - "#{throttle_setting_prefix}_api_enabled" => true, - "#{throttle_setting_prefix}_api_requests_per_period" => requests_per_period, - "#{throttle_setting_prefix}_web_enabled" => true, - "#{throttle_setting_prefix}_web_requests_per_period" => requests_per_period, - "#{throttle_setting_prefix}_files_api_enabled" => true, - "#{throttle_setting_prefix}_packages_api_enabled" => true, - "#{throttle_setting_prefix}_deprecated_api_enabled" => true - ) - - stub_session(csrf_session) - end - - context 'with a CSRF token' do - it 'uses the rate limit for web requests' do - requests_per_period.times { get_api csrf: true } - - expect_rejection("#{throttle_setting_prefix}_web") { get_api csrf: true } - expect_rejection("#{throttle_setting_prefix}_web") { get_api csrf: true, path: files_api_path } - expect_rejection("#{throttle_setting_prefix}_web") { get_api csrf: true, path: packages_api_path } - expect_rejection("#{throttle_setting_prefix}_web") { get_api csrf: true, path: deprecated_api_path } - - # API rate limit is not triggered yet - expect_ok { get_api } - expect_not_found { get_api path: files_api_path } - expect_not_found { get_api path: packages_api_path } - expect_not_found { get_api path: deprecated_api_path } - end - - context 'without a CSRF session' do - let(:csrf_session) { nil } - - it 'always uses the rate limit for API requests' do - requests_per_period.times { get_api csrf: true } - - expect_rejection("#{throttle_setting_prefix}_api") { get_api csrf: true } - expect_rejection("#{throttle_setting_prefix}_api") { get_api } - end - end - end - - context 'without a CSRF token' do - it 'uses the rate limit for API requests' do - requests_per_period.times { get_api } - - expect_rejection("#{throttle_setting_prefix}_api") { get_api } - - # Web and custom API rate limits are not triggered yet - expect_ok { get_api csrf: true } - expect_not_found { get_api path: files_api_path } - expect_not_found { get_api path: packages_api_path } - expect_not_found { get_api path: deprecated_api_path } - end - end -end |
