diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-02-01 15:17:55 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-02-01 15:17:55 +0300 |
commit | 1ae73e5bfcb2dc4a49f40886f729fc4797ba2a65 (patch) | |
tree | 03cb6dac129fb29c9500651a051d1f61966d79b8 | |
parent | 719524126026437133586ce042dfc48c0d166ecd (diff) |
Add latest changes from gitlab-org/gitlab@master
49 files changed, 1301 insertions, 1243 deletions
diff --git a/Gemfile.lock b/Gemfile.lock index bcbf0a1df1f..bca63aff732 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1119,7 +1119,7 @@ GEM rubocop-ast (>= 0.7.1) ruby-fogbugz (0.2.1) crack (~> 0.4) - ruby-magic (0.5.3) + ruby-magic (0.5.4) mini_portile2 (~> 2.6) ruby-prof (1.3.1) ruby-progressbar (1.11.0) diff --git a/app/assets/javascripts/blob/components/blob_header.vue b/app/assets/javascripts/blob/components/blob_header.vue index 4ef984c0da4..c5ab28e6ec5 100644 --- a/app/assets/javascripts/blob/components/blob_header.vue +++ b/app/assets/javascripts/blob/components/blob_header.vue @@ -42,6 +42,11 @@ export default { required: false, default: false, }, + showPath: { + type: Boolean, + required: false, + default: true, + }, }, data() { return { @@ -55,6 +60,9 @@ export default { showDefaultActions() { return !this.hideDefaultActions; }, + isEmpty() { + return this.blob.rawSize === 0; + }, }, watch: { viewer(newVal, oldVal) { @@ -74,7 +82,7 @@ export default { <div class="js-file-title file-title-flex-parent"> <div class="gl-display-flex"> <table-of-contents class="gl-pr-2" /> - <blob-filepath :blob="blob"> + <blob-filepath :blob="blob" :show-path="showPath"> <template #filepath-prepend> <slot name="prepend"></slot> </template> @@ -88,12 +96,13 @@ export default { <default-actions v-if="showDefaultActions" - :raw-path="blob.rawPath" + :raw-path="blob.externalStorageUrl || blob.rawPath" :active-viewer="viewer" :has-render-error="hasRenderError" :is-binary="isBinary" :environment-name="blob.environmentFormattedExternalUrl" :environment-path="blob.environmentExternalUrlForRouteMap" + :is-empty="isEmpty" @copy="proxyCopyRequest" /> </div> diff --git a/app/assets/javascripts/blob/components/blob_header_default_actions.vue b/app/assets/javascripts/blob/components/blob_header_default_actions.vue index b7b254687e2..12bcb24b0cc 100644 --- a/app/assets/javascripts/blob/components/blob_header_default_actions.vue +++ b/app/assets/javascripts/blob/components/blob_header_default_actions.vue @@ -48,6 +48,11 @@ export default { required: false, default: null, }, + isEmpty: { + type: Boolean, + required: false, + default: false, + }, }, computed: { downloadUrl() { @@ -87,6 +92,7 @@ export default { icon="copy-to-clipboard" category="primary" variant="default" + class="js-copy-blob-source-btn" /> <gl-button v-if="!isBinary" @@ -100,6 +106,7 @@ export default { variant="default" /> <gl-button + v-if="!isEmpty" v-gl-tooltip.hover :aria-label="$options.BTN_DOWNLOAD_TITLE" :title="$options.BTN_DOWNLOAD_TITLE" diff --git a/app/assets/javascripts/blob/components/blob_header_filepath.vue b/app/assets/javascripts/blob/components/blob_header_filepath.vue index 90d01358451..62355306655 100644 --- a/app/assets/javascripts/blob/components/blob_header_filepath.vue +++ b/app/assets/javascripts/blob/components/blob_header_filepath.vue @@ -15,6 +15,11 @@ export default { type: Object, required: true, }, + showPath: { + type: Boolean, + required: false, + default: true, + }, }, computed: { blobSize() { @@ -26,6 +31,13 @@ export default { showLfsBadge() { return this.blob.storedExternally && this.blob.externalStorage === 'lfs'; }, + fileName() { + if (this.showPath) { + return this.blob.path; + } + + return this.blob.name; + }, }, }; </script> @@ -33,12 +45,12 @@ export default { <div class="file-header-content d-flex align-items-center lh-100"> <slot name="filepath-prepend"></slot> - <template v-if="blob.path"> - <file-icon :file-name="blob.path" :size="16" aria-hidden="true" css-classes="mr-2" /> + <template v-if="fileName"> + <file-icon :file-name="fileName" :size="16" aria-hidden="true" css-classes="mr-2" /> <strong class="file-title-name mr-1 js-blob-header-filepath" data-qa-selector="file_title_content" - >{{ blob.path }}</strong + >{{ fileName }}</strong > </template> diff --git a/app/assets/javascripts/environments/components/environment_pin.vue b/app/assets/javascripts/environments/components/environment_pin.vue index 0b753d53ee3..f5a83b97552 100644 --- a/app/assets/javascripts/environments/components/environment_pin.vue +++ b/app/assets/javascripts/environments/components/environment_pin.vue @@ -6,6 +6,7 @@ import { GlDropdownItem } from '@gitlab/ui'; import { __ } from '~/locale'; import eventHub from '../event_hub'; +import cancelAutoStopMutation from '../graphql/mutations/cancel_auto_stop.mutation.graphql'; export default { components: { @@ -16,10 +17,22 @@ export default { type: String, required: true, }, + graphql: { + type: Boolean, + required: false, + default: false, + }, }, methods: { onPinClick() { - eventHub.$emit('cancelAutoStop', this.autoStopUrl); + if (this.graphql) { + this.$apollo.mutate({ + mutation: cancelAutoStopMutation, + variables: { autoStopUrl: this.autoStopUrl }, + }); + } else { + eventHub.$emit('cancelAutoStop', this.autoStopUrl); + } }, }, title: __('Prevent auto-stopping'), diff --git a/app/assets/javascripts/environments/components/new_environment_item.vue b/app/assets/javascripts/environments/components/new_environment_item.vue index 26379faa185..e23a02b3dfd 100644 --- a/app/assets/javascripts/environments/components/new_environment_item.vue +++ b/app/assets/javascripts/environments/components/new_environment_item.vue @@ -243,6 +243,7 @@ export default { <pin v-if="canShowAutoStopDate" :auto-stop-url="autoStopPath" + graphql data-track-action="click_button" data-track-label="environment_pin" /> diff --git a/app/assets/javascripts/environments/graphql/mutations/cancel_auto_stop.mutation.graphql b/app/assets/javascripts/environments/graphql/mutations/cancel_auto_stop.mutation.graphql index 22dfb8a7a89..0b473495710 100644 --- a/app/assets/javascripts/environments/graphql/mutations/cancel_auto_stop.mutation.graphql +++ b/app/assets/javascripts/environments/graphql/mutations/cancel_auto_stop.mutation.graphql @@ -1,5 +1,5 @@ -mutation cancelAutoStop($environment: LocalEnvironment) { - cancelAutoStop(environment: $environment) @client { +mutation cancelAutoStop($autoStopUrl: String!) { + cancelAutoStop(autoStopUrl: $autoStopUrl) @client { errors } } diff --git a/app/assets/javascripts/environments/graphql/resolvers.js b/app/assets/javascripts/environments/graphql/resolvers.js index 812fa0c81f0..9e0e0fff47f 100644 --- a/app/assets/javascripts/environments/graphql/resolvers.js +++ b/app/assets/javascripts/environments/graphql/resolvers.js @@ -134,9 +134,9 @@ export const resolvers = (endpoint) => ({ data: { environmentToRollback: environment }, }); }, - cancelAutoStop(_, { environment: { autoStopPath } }) { + cancelAutoStop(_, { autoStopUrl }) { return axios - .post(autoStopPath) + .post(autoStopUrl) .then(() => buildErrors()) .catch((err) => buildErrors([ diff --git a/app/assets/javascripts/environments/graphql/typedefs.graphql b/app/assets/javascripts/environments/graphql/typedefs.graphql index c02f6b2838a..cc54b4ecd67 100644 --- a/app/assets/javascripts/environments/graphql/typedefs.graphql +++ b/app/assets/javascripts/environments/graphql/typedefs.graphql @@ -77,7 +77,7 @@ extend type Mutation { stopEnvironment(environment: LocalEnvironmentInput): LocalErrors deleteEnvironment(environment: LocalEnvironmentInput): LocalErrors rollbackEnvironment(environment: LocalEnvironmentInput): LocalErrors - cancelAutoStop(environment: LocalEnvironmentInput): LocalErrors + cancelAutoStop(autoStopUrl: String!): LocalErrors setEnvironmentToDelete(environment: LocalEnvironmentInput): LocalErrors setEnvironmentToRollback(environment: LocalEnvironmentInput): LocalErrors setEnvironmentToStop(environment: LocalEnvironmentInput): LocalErrors diff --git a/app/assets/javascripts/repository/components/blob_content_viewer.vue b/app/assets/javascripts/repository/components/blob_content_viewer.vue index 2ac1fa61631..547e41019a2 100644 --- a/app/assets/javascripts/repository/components/blob_content_viewer.vue +++ b/app/assets/javascripts/repository/components/blob_content_viewer.vue @@ -153,7 +153,7 @@ export default { }, blobViewer() { const { fileType } = this.viewer; - return loadViewer(fileType); + return loadViewer(fileType, this.isUsingLfs); }, viewerProps() { const { fileType } = this.viewer; @@ -185,6 +185,9 @@ export default { ? this.blobInfo.ideForkAndEditPath : this.blobInfo.forkAndEditPath; }, + isUsingLfs() { + return this.blobInfo.storedExternally && this.blobInfo.externalStorage === 'lfs'; + }, }, methods: { loadLegacyViewer(type) { @@ -245,10 +248,11 @@ export default { <div v-if="blobInfo && !isLoading" class="file-holder"> <blob-header :blob="blobInfo" - :hide-viewer-switcher="!hasRichViewer || isBinaryFileType" + :hide-viewer-switcher="!hasRichViewer || isBinaryFileType || isUsingLfs" :is-binary="isBinaryFileType" :active-viewer-type="viewer.type" :has-render-error="hasRenderError" + :show-path="false" @viewer-changed="switchViewer" > <template #actions> diff --git a/app/assets/javascripts/repository/components/blob_viewers/index.js b/app/assets/javascripts/repository/components/blob_viewers/index.js index 8f6f2d15215..6e4e36579d3 100644 --- a/app/assets/javascripts/repository/components/blob_viewers/index.js +++ b/app/assets/javascripts/repository/components/blob_viewers/index.js @@ -1,29 +1,25 @@ -export const loadViewer = (type) => { - switch (type) { - case 'empty': - return () => import(/* webpackChunkName: 'blob_empty_viewer' */ './empty_viewer.vue'); - case 'text': - return gon.features.highlightJs - ? () => - import( - /* webpackChunkName: 'blob_text_viewer' */ '~/vue_shared/components/source_viewer.vue' - ) - : null; - case 'download': - return () => import(/* webpackChunkName: 'blob_download_viewer' */ './download_viewer.vue'); - case 'image': - return () => import(/* webpackChunkName: 'blob_image_viewer' */ './image_viewer.vue'); - case 'video': - return () => import(/* webpackChunkName: 'blob_video_viewer' */ './video_viewer.vue'); - case 'pdf': - return () => import(/* webpackChunkName: 'blob_pdf_viewer' */ './pdf_viewer.vue'); - default: - return null; +const viewers = { + download: () => import('./download_viewer.vue'), + image: () => import('./image_viewer.vue'), + video: () => import('./video_viewer.vue'), + empty: () => import('./empty_viewer.vue'), + text: () => import('~/vue_shared/components/source_viewer.vue'), + pdf: () => import('./pdf_viewer.vue'), + lfs: () => import('./lfs_viewer.vue'), +}; + +export const loadViewer = (type, isUsingLfs) => { + let viewer = viewers[type]; + + if (!viewer && isUsingLfs) { + viewer = viewers.lfs; } + + return viewer; }; export const viewerProps = (type, blob) => { - return { + const props = { text: { content: blob.rawTextBlob, autoDetect: true, // We'll eventually disable autoDetect and pass the language explicitly to reduce the footprint (https://gitlab.com/gitlab-org/gitlab/-/issues/348145) @@ -44,5 +40,11 @@ export const viewerProps = (type, blob) => { url: blob.rawPath, fileSize: blob.rawSize, }, - }[type]; + lfs: { + fileName: blob.name, + filePath: blob.rawPath, + }, + }; + + return props[type] || props[blob.externalStorage]; }; diff --git a/app/assets/javascripts/repository/components/blob_viewers/lfs_viewer.vue b/app/assets/javascripts/repository/components/blob_viewers/lfs_viewer.vue new file mode 100644 index 00000000000..1596c5c91b1 --- /dev/null +++ b/app/assets/javascripts/repository/components/blob_viewers/lfs_viewer.vue @@ -0,0 +1,36 @@ +<script> +import { GlLink, GlSprintf } from '@gitlab/ui'; +import { __ } from '~/locale'; + +export default { + i18n: { + lfsText: __( + 'This content could not be displayed because it is stored in LFS. You can %{linkStart}download it%{linkEnd} instead.', + ), + }, + components: { + GlLink, + GlSprintf, + }, + props: { + fileName: { + type: String, + required: true, + }, + filePath: { + type: String, + required: true, + }, + }, +}; +</script> + +<template> + <div class="gl-text-center gl-py-13 gl-bg-gray-50" data-type="lfs"> + <gl-sprintf :message="$options.i18n.lfsText"> + <template #link="{ content }"> + <gl-link :href="filePath" :download="fileName" target="_blank">{{ content }}</gl-link> + </template> + </gl-sprintf> + </div> +</template> diff --git a/app/assets/javascripts/repository/queries/blob_info.query.graphql b/app/assets/javascripts/repository/queries/blob_info.query.graphql index 66da13fa1e5..aeba84d481d 100644 --- a/app/assets/javascripts/repository/queries/blob_info.query.graphql +++ b/app/assets/javascripts/repository/queries/blob_info.query.graphql @@ -32,6 +32,7 @@ query getBlobInfo($projectPath: ID!, $filePath: String!, $ref: String!) { archived storedExternally externalStorage + externalStorageUrl rawPath replacePath pipelineEditorPath diff --git a/app/assets/javascripts/security_configuration/components/constants.js b/app/assets/javascripts/security_configuration/components/constants.js index 034dba29196..971706c941e 100644 --- a/app/assets/javascripts/security_configuration/components/constants.js +++ b/app/assets/javascripts/security_configuration/components/constants.js @@ -217,6 +217,10 @@ export const securityFeatures = [ helpPath: CONTAINER_SCANNING_HELP_PATH, configurationHelpPath: CONTAINER_SCANNING_CONFIG_HELP_PATH, type: REPORT_TYPE_CONTAINER_SCANNING, + + // This field will eventually come from the backend, the progress is + // tracked in https://gitlab.com/gitlab-org/gitlab/-/issues/331621 + canEnableByMergeRequest: true, }, { name: CLUSTER_IMAGE_SCANNING_NAME, diff --git a/app/assets/javascripts/vue_shared/components/source_viewer.vue b/app/assets/javascripts/vue_shared/components/source_viewer.vue index 99895926653..ba6f27698b7 100644 --- a/app/assets/javascripts/vue_shared/components/source_viewer.vue +++ b/app/assets/javascripts/vue_shared/components/source_viewer.vue @@ -115,7 +115,12 @@ export default { }; </script> <template> - <div class="file-content code js-syntax-highlight" :class="$options.userColorScheme"> + <div + class="file-content code js-syntax-highlight blob-content" + :class="$options.userColorScheme" + data-type="simple" + data-qa-selector="blob_viewer_file_content" + > <line-numbers :lines="lineNumbers" /> <pre class="code"><code v-safe-html="highlightedContent"></code> </pre> diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 084c962d34c..89b32a61e2e 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -54,14 +54,14 @@ module ProjectsHelper default_opts = { avatar: true, name: true, title: ":name" } opts = default_opts.merge(opts) + return "(deleted)" unless author + data_attrs = { user_id: author.id, username: author.username, name: author.name } - return "(deleted)" unless author - author_html = [] # Build avatar image tag diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 032959c385e..0883df5ed6b 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -73,7 +73,6 @@ module Ci has_many :trigger_requests, dependent: :destroy, foreign_key: :commit_id # rubocop:disable Cop/ActiveRecordDependent has_many :variables, class_name: 'Ci::PipelineVariable' has_many :deployments, through: :builds - has_many :environments, -> { distinct.allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/338658') }, through: :deployments has_many :latest_builds, -> { latest.with_project_and_metadata }, foreign_key: :commit_id, inverse_of: :pipeline, class_name: 'Ci::Build' has_many :downloadable_artifacts, -> do not_expired.or(where_exists(::Ci::Pipeline.artifacts_locked.where('ci_pipelines.id = ci_builds.commit_id'))).downloadable.with_job diff --git a/app/presenters/blob_presenter.rb b/app/presenters/blob_presenter.rb index 3fb7b1c51ed..efd29b5601b 100644 --- a/app/presenters/blob_presenter.rb +++ b/app/presenters/blob_presenter.rb @@ -125,7 +125,7 @@ class BlobPresenter < Gitlab::View::Presenter::Delegated def external_storage_url return unless static_objects_external_storage_enabled? - external_storage_url_or_path(url_helpers.project_raw_url(project, ref_qualified_path)) + external_storage_url_or_path(url_helpers.project_raw_url(project, ref_qualified_path), project) end private diff --git a/app/serializers/test_case_entity.rb b/app/serializers/test_case_entity.rb index 0e64b843fd3..8a5fadf53a6 100644 --- a/app/serializers/test_case_entity.rb +++ b/app/serializers/test_case_entity.rb @@ -4,7 +4,7 @@ class TestCaseEntity < Grape::Entity include API::Helpers::RelatedResourcesHelpers expose :status - expose :name + expose :name, default: "(No name)" expose :classname expose :file expose :execution_time diff --git a/app/workers/authorized_project_update/user_refresh_over_user_range_worker.rb b/app/workers/authorized_project_update/user_refresh_over_user_range_worker.rb index f5327449242..8452f2a7821 100644 --- a/app/workers/authorized_project_update/user_refresh_over_user_range_worker.rb +++ b/app/workers/authorized_project_update/user_refresh_over_user_range_worker.rb @@ -20,7 +20,6 @@ module AuthorizedProjectUpdate urgency :low queue_namespace :authorized_project_update - deduplicate :until_executing, including_scheduled: true data_consistency :delayed idempotent! diff --git a/app/workers/expire_job_cache_worker.rb b/app/workers/expire_job_cache_worker.rb index 49f0222e9c9..eaa8810a78e 100644 --- a/app/workers/expire_job_cache_worker.rb +++ b/app/workers/expire_job_cache_worker.rb @@ -10,8 +10,6 @@ class ExpireJobCacheWorker # rubocop:disable Scalability/IdempotentWorker queue_namespace :pipeline_cache urgency :high - - deduplicate :until_executing, including_scheduled: true idempotent! def perform(job_id) diff --git a/doc/user/group/epics/manage_epics.md b/doc/user/group/epics/manage_epics.md index 91565b40ee8..3350b0f1169 100644 --- a/doc/user/group/epics/manage_epics.md +++ b/doc/user/group/epics/manage_epics.md @@ -164,6 +164,7 @@ than 1000. The cached value is rounded to thousands or millions and updated ever > - Sorting by epic titles [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/331625) in GitLab 14.1. > - Searching by milestone and confidentiality [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/268372) in GitLab 14.2 [with a flag](../../../administration/feature_flags.md) named `vue_epics_list`. Disabled by default. > - [Enabled on GitLab.com and self-managed](https://gitlab.com/gitlab-org/gitlab/-/issues/276189) in GitLab 14.7. +> - [Feature flag `vue_epics_list`](https://gitlab.com/gitlab-org/gitlab/-/issues/327320) removed in GitLab 14.8. You can search for an epic from the list of epics using filtered search bar based on following parameters: diff --git a/lib/api/usage_data_non_sql_metrics.rb b/lib/api/usage_data_non_sql_metrics.rb index d9e0d153e58..36325c3b742 100644 --- a/lib/api/usage_data_non_sql_metrics.rb +++ b/lib/api/usage_data_non_sql_metrics.rb @@ -18,7 +18,7 @@ module API get 'non_sql_metrics' do Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/325534') - data = Gitlab::UsageDataNonSqlMetrics.uncached_data + data = Gitlab::UsageDataNonSqlMetrics.data(force_refresh: true) present data end diff --git a/lib/api/usage_data_queries.rb b/lib/api/usage_data_queries.rb index 22e83fe0294..1ce8feac4ab 100644 --- a/lib/api/usage_data_queries.rb +++ b/lib/api/usage_data_queries.rb @@ -18,7 +18,7 @@ module API get 'queries' do Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/325534') - queries = Gitlab::UsageDataQueries.uncached_data + queries = Gitlab::UsageDataQueries.data(force_refresh: true) present queries end diff --git a/lib/gitlab/rack_attack/request.rb b/lib/gitlab/rack_attack/request.rb index 94ae29af3d0..7c8dc0be1c6 100644 --- a/lib/gitlab/rack_attack/request.rb +++ b/lib/gitlab/rack_attack/request.rb @@ -30,15 +30,15 @@ module Gitlab end def api_internal_request? - path =~ %r{^/api/v\d+/internal/} + path.match?(%r{^/api/v\d+/internal/}) end def health_check_request? - path =~ %r{^/-/(health|liveness|readiness|metrics)} + path.match?(%r{^/-/(health|liveness|readiness|metrics)}) end def container_registry_event? - path =~ %r{^/api/v\d+/container_registry_event/} + path.match?(%r{^/api/v\d+/container_registry_event/}) end def product_analytics_collector_request? @@ -54,11 +54,7 @@ module Gitlab end def protected_path? - !protected_path_regex.nil? - end - - def protected_path_regex - path =~ protected_paths_regex + path.match?(protected_paths_regex) end def throttle?(throttle, authenticated:) @@ -178,15 +174,15 @@ module Gitlab end def packages_api_path? - path =~ ::Gitlab::Regex::Packages::API_PATH_REGEX + path.match?(::Gitlab::Regex::Packages::API_PATH_REGEX) end def git_lfs_path? - path =~ Gitlab::PathRegex.repository_git_lfs_route_regex + path.match?(::Gitlab::PathRegex.repository_git_lfs_route_regex) end def files_api_path? - path =~ FILES_PATH_REGEX + path.match?(FILES_PATH_REGEX) end def deprecated_api_request? @@ -195,7 +191,7 @@ module Gitlab with_projects = params['with_projects'] with_projects = true if with_projects.blank? - path =~ GROUP_PATH_REGEX && Gitlab::Utils.to_boolean(with_projects) + path.match?(GROUP_PATH_REGEX) && Gitlab::Utils.to_boolean(with_projects) end end end diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb index 42e67543342..83c26ee9ade 100644 --- a/lib/gitlab/usage_data.rb +++ b/lib/gitlab/usage_data.rb @@ -47,14 +47,6 @@ module Gitlab end end - def uncached_data - clear_memoized - - with_finished_at(:recording_ce_finished_at) do - usage_data_metrics - end - end - def to_json(force_refresh: false) data(force_refresh: force_refresh).to_json end @@ -697,6 +689,14 @@ module Gitlab private + def uncached_data + clear_memoized + + with_finished_at(:recording_ce_finished_at) do + usage_data_metrics + end + end + def stage_manage_events(time_period) if time_period.empty? Gitlab::Utils::UsageData::FALLBACK diff --git a/lib/tasks/gitlab/usage_data.rake b/lib/tasks/gitlab/usage_data.rake index 694c49240ed..dc1d7cf4cdc 100644 --- a/lib/tasks/gitlab/usage_data.rake +++ b/lib/tasks/gitlab/usage_data.rake @@ -4,17 +4,17 @@ namespace :gitlab do namespace :usage_data do desc 'GitLab | UsageData | Generate raw SQLs for usage ping in YAML' task dump_sql_in_yaml: :environment do - puts Gitlab::UsageDataQueries.uncached_data.to_yaml + puts Gitlab::UsageDataQueries.data(force_refresh: true).to_yaml end desc 'GitLab | UsageData | Generate raw SQLs for usage ping in JSON' task dump_sql_in_json: :environment do - puts Gitlab::Json.pretty_generate(Gitlab::UsageDataQueries.uncached_data) + puts Gitlab::Json.pretty_generate(Gitlab::UsageDataQueries.data(force_refresh: true)) end desc 'GitLab | UsageData | Generate usage ping in JSON' task generate: :environment do - puts Gitlab::Json.pretty_generate(Gitlab::UsageData.uncached_data) + puts Gitlab::Json.pretty_generate(Gitlab::UsageData.data(force: true)) end desc 'GitLab | UsageData | Generate usage ping and send it to Versions Application' diff --git a/locale/gitlab.pot b/locale/gitlab.pot index d338a879858..46179ff75d0 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -15288,12 +15288,6 @@ msgstr "" msgid "Filter by" msgstr "" -msgid "Filter by %{issuable_type} that are currently closed." -msgstr "" - -msgid "Filter by %{issuable_type} that are currently open." -msgstr "" - msgid "Filter by %{page_context_word} that are currently open." msgstr "" @@ -33721,9 +33715,6 @@ msgstr "" msgid "Something went wrong. Try again later." msgstr "" -msgid "Sorry, no epics matched your search" -msgstr "" - msgid "Sorry, no projects matched your search" msgstr "" @@ -36587,6 +36578,9 @@ msgstr "" msgid "This content could not be displayed because %{reason}. You can %{options} instead." msgstr "" +msgid "This content could not be displayed because it is stored in LFS. You can %{linkStart}download it%{linkEnd} instead." +msgstr "" + msgid "This credential has expired" msgstr "" @@ -37593,9 +37587,6 @@ msgstr "" msgid "To widen your search, change or remove filters above." msgstr "" -msgid "To widen your search, change or remove filters." -msgstr "" - msgid "To-Do List" msgstr "" diff --git a/qa/qa/page/component/blob_content.rb b/qa/qa/page/component/blob_content.rb index 4d36a6dcefe..16a258ff0bc 100644 --- a/qa/qa/page/component/blob_content.rb +++ b/qa/qa/page/component/blob_content.rb @@ -22,6 +22,10 @@ module QA element :copy_contents_button end + base.view 'app/assets/javascripts/vue_shared/components/source_viewer.vue' do + element :blob_viewer_file_content + end + base.view 'app/views/projects/blob/_header_content.html.haml' do element :file_name_content end diff --git a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_issue_spec.rb b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_issue_spec.rb index 0715fa8ba37..dc981ebceab 100644 --- a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_issue_spec.rb +++ b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_issue_spec.rb @@ -3,7 +3,11 @@ require_relative 'gitlab_project_migration_common' module QA - RSpec.describe 'Manage', :requires_admin do + RSpec.describe 'Manage', :requires_admin, quarantine: { + only: { subdomain: :staging }, + type: :investigating, + issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/351596' + } do describe 'Gitlab migration' do include_context 'with gitlab project migration' diff --git a/qa/qa/specs/features/api/1_manage/migration/gitlab_project_migration_common.rb b/qa/qa/specs/features/api/1_manage/migration/gitlab_project_migration_common.rb index 0abc21531a6..fea68114ae0 100644 --- a/qa/qa/specs/features/api/1_manage/migration/gitlab_project_migration_common.rb +++ b/qa/qa/specs/features/api/1_manage/migration/gitlab_project_migration_common.rb @@ -2,12 +2,9 @@ module QA RSpec.shared_context 'with gitlab project migration', quarantine: { - only: { job: 'praefect', subdomain: :staging }, + only: { job: 'praefect' }, type: :investigating, - issue: [ - 'https://gitlab.com/gitlab-org/gitlab/-/issues/348999', - 'https://gitlab.com/gitlab-org/gitlab/-/issues/350965' - ] + issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/348999' } do let(:source_project_with_readme) { false } let(:import_wait_duration) { { max_duration: 300, sleep_interval: 2 } } diff --git a/rubocop/cop/sidekiq_load_balancing/worker_data_consistency_with_deduplication.rb b/rubocop/cop/sidekiq_load_balancing/worker_data_consistency_with_deduplication.rb deleted file mode 100644 index e8b4b513a23..00000000000 --- a/rubocop/cop/sidekiq_load_balancing/worker_data_consistency_with_deduplication.rb +++ /dev/null @@ -1,154 +0,0 @@ -# frozen_string_literal: true - -require_relative '../../code_reuse_helpers' - -module RuboCop - module Cop - module SidekiqLoadBalancing - # This cop checks for including_scheduled: true option in idempotent Sidekiq workers that utilize load balancing capabilities. - # - # @example - # - # # bad - # class BadWorker - # include ApplicationWorker - # - # data_consistency :delayed - # idempotent! - # - # def perform - # end - # end - # - # # bad - # class BadWorker - # include ApplicationWorker - # - # data_consistency :delayed - # - # deduplicate :until_executing - # idempotent! - # - # def perform - # end - # end - # - # # good - # class GoodWorker - # include ApplicationWorker - # - # data_consistency :delayed - # - # deduplicate :until_executing, including_scheduled: true - # idempotent! - # - # def perform - # end - # end - # - class WorkerDataConsistencyWithDeduplication < RuboCop::Cop::Base - include CodeReuseHelpers - extend AutoCorrector - - HELP_LINK = 'https://docs.gitlab.com/ee/development/sidekiq_style_guide.html#scheduling-jobs-in-the-future' - REPLACEMENT = ', including_scheduled: true' - DEFAULT_STRATEGY = ':until_executing' - - MSG = <<~MSG - Workers that declare either `:sticky` or `:delayed` data consistency become eligible for database load-balancing. - In both cases, jobs are enqueued with a short delay. - - If you do want to deduplicate jobs that utilize load-balancing, you need to specify including_scheduled: true - argument when defining deduplication strategy. - - See #{HELP_LINK} for a more detailed explanation of these settings. - MSG - - def_node_search :application_worker?, <<~PATTERN - `(send nil? :include (const nil? :ApplicationWorker)) - PATTERN - - def_node_search :idempotent_worker?, <<~PATTERN - `(send nil? :idempotent!) - PATTERN - - def_node_search :data_consistency_defined?, <<~PATTERN - `(send nil? :data_consistency (sym {:sticky :delayed })) - PATTERN - - def_node_matcher :including_scheduled?, <<~PATTERN - `(hash <(pair (sym :including_scheduled) (%1)) ...>) - PATTERN - - def_node_matcher :deduplicate_strategy?, <<~PATTERN - `(send nil? :deduplicate (sym $_) $(...)?) - PATTERN - - def on_class(node) - return unless in_worker?(node) - return unless application_worker?(node) - return unless idempotent_worker?(node) - return unless data_consistency_defined?(node) - - @strategy, options = deduplicate_strategy?(node) - including_scheduled = false - if options - @deduplicate_options = options[0] - including_scheduled = including_scheduled?(@deduplicate_options, :true) # rubocop:disable Lint/BooleanSymbol - end - - @offense = !(including_scheduled || @strategy == :none) - end - - def on_send(node) - return unless offense - - if node.children[1] == :deduplicate - add_offense(node.loc.expression) do |corrector| - autocorrect_deduplicate_strategy(node, corrector) - end - elsif node.children[1] == :idempotent! && !strategy - add_offense(node.loc.expression) do |corrector| - autocorrect_missing_deduplicate_strategy(node, corrector) - end - end - end - - private - - attr_reader :offense, :deduplicate_options, :strategy - - def autocorrect_deduplicate_with_options(corrector) - if including_scheduled?(deduplicate_options, :false) # rubocop:disable Lint/BooleanSymbol - replacement = deduplicate_options.source.sub("including_scheduled: false", "including_scheduled: true") - corrector.replace(deduplicate_options.loc.expression, replacement) - else - corrector.insert_after(deduplicate_options.loc.expression, REPLACEMENT) - end - end - - def autocorrect_deduplicate_without_options(node, corrector) - corrector.insert_after(node.loc.expression, REPLACEMENT) - end - - def autocorrect_missing_deduplicate_strategy(node, corrector) - indent_found = node.source_range.source_line =~ /^( +)/ - # Get indentation size - whitespaces = Regexp.last_match(1).size if indent_found - replacement = "deduplicate #{DEFAULT_STRATEGY}#{REPLACEMENT}\n" - # Add indentation in the end since we are inserting a whole line before idempotent! - replacement += ' ' * whitespaces.to_i - corrector.insert_before(node.source_range, replacement) - end - - def autocorrect_deduplicate_strategy(node, corrector) - if deduplicate_options - autocorrect_deduplicate_with_options(corrector) - else - autocorrect_deduplicate_without_options(node, corrector) - end - end - end - end - end -end diff --git a/spec/features/projects/blobs/blob_show_spec.rb b/spec/features/projects/blobs/blob_show_spec.rb index 988051bca40..c8cbf396098 100644 --- a/spec/features/projects/blobs/blob_show_spec.rb +++ b/spec/features/projects/blobs/blob_show_spec.rb @@ -29,443 +29,421 @@ RSpec.describe 'File blob', :js do ).execute end - before do - stub_feature_flags(refactor_blob_viewer: false) # This stub will be removed in https://gitlab.com/gitlab-org/gitlab/-/issues/350455 - end - - context 'Ruby file' do - before do - visit_blob('files/ruby/popen.rb') - - wait_for_requests - end - - it 'displays the blob' do - aggregate_failures do - # shows highlighted Ruby code - expect(page).to have_css(".js-syntax-highlight") - expect(page).to have_content("require 'fileutils'") - - # does not show a viewer switcher - expect(page).not_to have_selector('.js-blob-viewer-switcher') - - # shows an enabled copy button - expect(page).to have_selector('.js-copy-blob-source-btn:not(.disabled)') - - # shows a raw button - expect(page).to have_link('Open raw') - end - end - - it 'displays file actions on all screen sizes' do - file_actions_selector = '.file-actions' - - resize_screen_sm - expect(page).to have_selector(file_actions_selector, visible: true) - - resize_screen_xs - expect(page).to have_selector(file_actions_selector, visible: true) - end - end - - context 'Markdown file' do - context 'visiting directly' do + context 'with refactor_blob_viewer feature flag enabled' do + context 'Ruby file' do before do - visit_blob('files/markdown/ruby-style-guide.md') + visit_blob('files/ruby/popen.rb') wait_for_requests end - it 'displays the blob using the rich viewer' do + it 'displays the blob' do aggregate_failures do - # hides the simple viewer - expect(page).to have_selector('.blob-viewer[data-type="simple"]', visible: false) - expect(page).to have_selector('.blob-viewer[data-type="rich"]') - - # shows rendered Markdown - expect(page).to have_link("PEP-8") + # shows highlighted Ruby code + expect(page).to have_css(".js-syntax-highlight") + expect(page).to have_content("require 'fileutils'") - # shows a viewer switcher - expect(page).to have_selector('.js-blob-viewer-switcher') + # does not show a viewer switcher + expect(page).not_to have_selector('.js-blob-viewer-switcher') - # shows a disabled copy button - expect(page).to have_selector('.js-copy-blob-source-btn.disabled') + # shows an enabled copy button + expect(page).to have_selector('.js-copy-blob-source-btn:not(.disabled)') # shows a raw button expect(page).to have_link('Open raw') end end - context 'switching to the simple viewer' do + it 'displays file actions on all screen sizes' do + file_actions_selector = '.file-actions' + + resize_screen_sm + expect(page).to have_selector(file_actions_selector, visible: true) + + resize_screen_xs + expect(page).to have_selector(file_actions_selector, visible: true) + end + end + + context 'Markdown file' do + context 'visiting directly' do before do - find('.js-blob-viewer-switch-btn[data-viewer=simple]').click + visit_blob('files/markdown/ruby-style-guide.md') wait_for_requests end - it 'displays the blob using the simple viewer' do + it 'displays the blob using the rich viewer' do aggregate_failures do - # hides the rich viewer - expect(page).to have_selector('.blob-viewer[data-type="simple"]') - expect(page).to have_selector('.blob-viewer[data-type="rich"]', visible: false) + # hides the simple viewer + expect(page).not_to have_selector('.blob-viewer[data-type="simple"]') + expect(page).to have_selector('.blob-viewer[data-type="rich"]') - # shows highlighted Markdown code - expect(page).to have_css(".js-syntax-highlight") - expect(page).to have_content("[PEP-8](http://www.python.org/dev/peps/pep-0008/)") + # shows rendered Markdown + expect(page).to have_link("PEP-8") - # shows an enabled copy button - expect(page).to have_selector('.js-copy-blob-source-btn:not(.disabled)') + # shows a viewer switcher + expect(page).to have_selector('.js-blob-viewer-switcher') + + # shows a disabled copy button + expect(page).to have_selector('.js-copy-blob-source-btn.disabled') + + # shows a raw button + expect(page).to have_link('Open raw') end end - context 'switching to the rich viewer again' do + context 'switching to the simple viewer' do before do - find('.js-blob-viewer-switch-btn[data-viewer=rich]').click + find('.js-blob-viewer-switch-btn[data-viewer=simple]').click wait_for_requests end - it 'displays the blob using the rich viewer' do + it 'displays the blob using the simple viewer' do aggregate_failures do - # hides the simple viewer - expect(page).to have_selector('.blob-viewer[data-type="simple"]', visible: false) - expect(page).to have_selector('.blob-viewer[data-type="rich"]') + # hides the rich viewer + expect(page).to have_selector('.blob-viewer[data-type="simple"]') + expect(page).not_to have_selector('.blob-viewer[data-type="rich"]') + + # shows highlighted Markdown code + expect(page).to have_css(".js-syntax-highlight") + expect(page).to have_content("[PEP-8](http://www.python.org/dev/peps/pep-0008/)") # shows an enabled copy button expect(page).to have_selector('.js-copy-blob-source-btn:not(.disabled)') end end - end - end - end - context 'when ref switch' do - def switch_ref_to(ref_name) - first('.qa-branches-select').click # rubocop:disable QA/SelectorUsage - - page.within '.project-refs-form' do - click_link ref_name - wait_for_requests - end - end + context 'switching to the rich viewer again' do + before do + find('.js-blob-viewer-switch-btn[data-viewer=rich]').click - it 'displays single highlighted line number of different ref' do - visit_blob('files/js/application.js', anchor: 'L1') + wait_for_requests + end - switch_ref_to('feature') + it 'displays the blob using the rich viewer' do + aggregate_failures do + # hides the simple viewer + expect(page).not_to have_selector('.blob-viewer[data-type="simple"]') + expect(page).to have_selector('.blob-viewer[data-type="rich"]') - page.within '.blob-content' do - expect(find_by_id('LC1')[:class]).to include("hll") + # shows a disabled copy button + expect(page).to have_selector('.js-copy-blob-source-btn.disabled') + end + end + end end end - it 'displays multiple highlighted line numbers of different ref' do - visit_blob('files/js/application.js', anchor: 'L1-3') + context 'when ref switch' do + def switch_ref_to(ref_name) + first('.qa-branches-select').click # rubocop:disable QA/SelectorUsage - switch_ref_to('feature') - - page.within '.blob-content' do - expect(find_by_id('LC1')[:class]).to include("hll") - expect(find_by_id('LC2')[:class]).to include("hll") - expect(find_by_id('LC3')[:class]).to include("hll") + page.within '.project-refs-form' do + click_link ref_name + wait_for_requests + end end - end - it 'displays no highlighted number of different ref' do - Files::UpdateService.new( - project, - project.first_owner, - commit_message: 'Update', - start_branch: 'feature', - branch_name: 'feature', - file_path: 'files/js/application.js', - file_content: 'new content' - ).execute + it 'displays no highlighted number of different ref' do + Files::UpdateService.new( + project, + project.first_owner, + commit_message: 'Update', + start_branch: 'feature', + branch_name: 'feature', + file_path: 'files/js/application.js', + file_content: 'new content' + ).execute - project.commit('feature').diffs.diff_files.first + project.commit('feature').diffs.diff_files.first - visit_blob('files/js/application.js', anchor: 'L3') - switch_ref_to('feature') + visit_blob('files/js/application.js', anchor: 'L3') + switch_ref_to('feature') - page.within '.blob-content' do - expect(page).not_to have_css('.hll') + page.within '.blob-content' do + expect(page).not_to have_css('.hll') + end end - end - context 'successfully change ref of similar name' do - before do - project.repository.create_branch('dev') - project.repository.create_branch('development') - end + context 'successfully change ref of similar name' do + before do + project.repository.create_branch('dev') + project.repository.create_branch('development') + end - it 'switch ref from longer to shorter ref name' do - visit_blob('files/js/application.js', ref: 'development') - switch_ref_to('dev') + it 'switch ref from longer to shorter ref name' do + visit_blob('files/js/application.js', ref: 'development') + switch_ref_to('dev') - aggregate_failures do - expect(page.find('.file-title-name').text).to eq('application.js') - expect(page).not_to have_css('flash-container') + aggregate_failures do + expect(page.find('.file-title-name').text).to eq('application.js') + expect(page).not_to have_css('flash-container') + end end - end - it 'switch ref from shorter to longer ref name' do - visit_blob('files/js/application.js', ref: 'dev') - switch_ref_to('development') + it 'switch ref from shorter to longer ref name' do + visit_blob('files/js/application.js', ref: 'dev') + switch_ref_to('development') - aggregate_failures do - expect(page.find('.file-title-name').text).to eq('application.js') - expect(page).not_to have_css('flash-container') + aggregate_failures do + expect(page.find('.file-title-name').text).to eq('application.js') + expect(page).not_to have_css('flash-container') + end end end - end - it 'successfully changes ref when the ref name matches the project name' do - project.repository.create_branch(project.name) + it 'successfully changes ref when the ref name matches the project name' do + project.repository.create_branch(project.name) - visit_blob('files/js/application.js', ref: project.name) - switch_ref_to('master') + visit_blob('files/js/application.js', ref: project.name) + switch_ref_to('master') - aggregate_failures do - expect(page.find('.file-title-name').text).to eq('application.js') - expect(page).not_to have_css('flash-container') + aggregate_failures do + expect(page.find('.file-title-name').text).to eq('application.js') + expect(page).not_to have_css('flash-container') + end end end end - context 'visiting with a line number anchor' do + context 'Markdown rendering' do before do - visit_blob('files/markdown/ruby-style-guide.md', anchor: 'L1') - end + project.add_maintainer(project.creator) - it 'displays the blob using the simple viewer' do - aggregate_failures do - # hides the rich viewer - expect(page).to have_selector('.blob-viewer[data-type="simple"]') - expect(page).to have_selector('.blob-viewer[data-type="rich"]', visible: false) + Files::CreateService.new( + project, + project.creator, + start_branch: 'master', + branch_name: 'master', + commit_message: "Add RedCarpet and CommonMark Markdown ", + file_path: 'files/commonmark/file.md', + file_content: "1. one\n - sublist\n" + ).execute + end - # highlights the line in question - expect(page).to have_selector('#LC1.hll') + context 'when rendering default markdown' do + before do + visit_blob('files/commonmark/file.md') - # shows highlighted Markdown code - expect(page).to have_css(".js-syntax-highlight") - expect(page).to have_content("[PEP-8](http://www.python.org/dev/peps/pep-0008/)") + wait_for_requests + end - # shows an enabled copy button - expect(page).to have_selector('.js-copy-blob-source-btn:not(.disabled)') + it 'renders using CommonMark' do + aggregate_failures do + expect(page).to have_content("sublist") + expect(page).not_to have_xpath("//ol//li//ul") + end end end end - end - - context 'Markdown rendering' do - before do - project.add_maintainer(project.creator) - - Files::CreateService.new( - project, - project.creator, - start_branch: 'master', - branch_name: 'master', - commit_message: "Add RedCarpet and CommonMark Markdown ", - file_path: 'files/commonmark/file.md', - file_content: "1. one\n - sublist\n" - ).execute - end - context 'when rendering default markdown' do + context 'Markdown file (stored in LFS)' do before do - visit_blob('files/commonmark/file.md') - - wait_for_requests - end + project.add_maintainer(project.creator) - it 'renders using CommonMark' do - aggregate_failures do - expect(page).to have_content("sublist") - expect(page).not_to have_xpath("//ol//li//ul") - end + Files::CreateService.new( + project, + project.creator, + start_branch: 'master', + branch_name: 'master', + commit_message: "Add Markdown in LFS", + file_path: 'files/lfs/file.md', + file_content: project.repository.blob_at('master', 'files/lfs/lfs_object.iso').data + ).execute end - end - end - context 'Markdown file (stored in LFS)' do - before do - project.add_maintainer(project.creator) - - Files::CreateService.new( - project, - project.creator, - start_branch: 'master', - branch_name: 'master', - commit_message: "Add Markdown in LFS", - file_path: 'files/lfs/file.md', - file_content: project.repository.blob_at('master', 'files/lfs/lfs_object.iso').data - ).execute - end - - context 'when LFS is enabled on the project' do - before do - allow(Gitlab.config.lfs).to receive(:enabled).and_return(true) - project.update_attribute(:lfs_enabled, true) + context 'when LFS is enabled on the project' do + before do + allow(Gitlab.config.lfs).to receive(:enabled).and_return(true) + project.update_attribute(:lfs_enabled, true) - visit_blob('files/lfs/file.md') + visit_blob('files/lfs/file.md') - wait_for_requests - end + wait_for_requests + end - it 'displays an error' do - aggregate_failures do - # hides the simple viewer - expect(page).to have_selector('.blob-viewer[data-type="simple"]', visible: false) - expect(page).to have_selector('.blob-viewer[data-type="rich"]') + it 'displays an error' do + aggregate_failures do + # hides the simple viewer + expect(page).not_to have_selector('.blob-viewer[data-type="simple"]') + expect(page).not_to have_selector('.blob-viewer[data-type="rich"]') - # shows an error message - expect(page).to have_content('The rendered file could not be displayed because it is stored in LFS. You can download it instead.') + # shows an error message + expect(page).to have_content('This content could not be displayed because it is stored in LFS. You can download it instead.') - # shows a viewer switcher - expect(page).to have_selector('.js-blob-viewer-switcher') + # does not show a viewer switcher + expect(page).not_to have_selector('.js-blob-viewer-switcher') - # does not show a copy button - expect(page).not_to have_selector('.js-copy-blob-source-btn') + # does not show a copy button + expect(page).not_to have_selector('.js-copy-blob-source-btn') - # shows a download button - expect(page).to have_link('Download') + # shows a download button + expect(page).to have_link('Download') + end end end - context 'switching to the simple viewer' do + context 'when LFS is disabled on the project' do before do - find('.js-blob-viewer-switcher .js-blob-viewer-switch-btn[data-viewer=simple]').click + visit_blob('files/lfs/file.md') wait_for_requests end - it 'displays an error' do + it 'displays the blob' do aggregate_failures do - # hides the rich viewer - expect(page).to have_selector('.blob-viewer[data-type="simple"]') - expect(page).to have_selector('.blob-viewer[data-type="rich"]', visible: false) + # shows text + expect(page).to have_content('size 1575078') - # shows an error message - expect(page).to have_content('The source could not be displayed because it is stored in LFS. You can download it instead.') + # does not show a viewer switcher + expect(page).not_to have_selector('.js-blob-viewer-switcher') - # does not show a copy button - expect(page).not_to have_selector('.js-copy-blob-source-btn') + # shows an enabled copy button + expect(page).to have_selector('.js-copy-blob-source-btn:not(.disabled)') + + # shows a raw button + expect(page).to have_link('Open raw') end end end end - context 'when LFS is disabled on the project' do + context 'PDF file' do before do - visit_blob('files/lfs/file.md') + project.add_maintainer(project.creator) + + Files::CreateService.new( + project, + project.creator, + start_branch: 'master', + branch_name: 'master', + commit_message: "Add PDF", + file_path: 'files/test.pdf', + file_content: project.repository.blob_at('add-pdf-file', 'files/pdf/test.pdf').data + ).execute + + visit_blob('files/test.pdf') wait_for_requests end it 'displays the blob' do aggregate_failures do - # shows text - expect(page).to have_content('size 1575078') + # shows rendered PDF + expect(page).to have_selector('.js-pdf-viewer') # does not show a viewer switcher expect(page).not_to have_selector('.js-blob-viewer-switcher') - # shows an enabled copy button - expect(page).to have_selector('.js-copy-blob-source-btn:not(.disabled)') + # does not show a copy button + expect(page).not_to have_selector('.js-copy-blob-source-btn') - # shows a raw button - expect(page).to have_link('Open raw') + # shows a download button + expect(page).to have_link('Download') end end end - end - context 'PDF file' do - before do - project.add_maintainer(project.creator) + context 'Jupiter Notebook file' do + before do + project.add_maintainer(project.creator) - Files::CreateService.new( - project, - project.creator, - start_branch: 'master', - branch_name: 'master', - commit_message: "Add PDF", - file_path: 'files/test.pdf', - file_content: project.repository.blob_at('add-pdf-file', 'files/pdf/test.pdf').data - ).execute + Files::CreateService.new( + project, + project.creator, + start_branch: 'master', + branch_name: 'master', + commit_message: "Add Jupiter Notebook", + file_path: 'files/basic.ipynb', + file_content: project.repository.blob_at('add-ipython-files', 'files/ipython/basic.ipynb').data + ).execute - visit_blob('files/test.pdf') + visit_blob('files/basic.ipynb') - wait_for_requests - end + wait_for_requests + end - it 'displays the blob' do - aggregate_failures do - # shows rendered PDF - expect(page).to have_selector('.js-pdf-viewer') + it 'displays the blob' do + aggregate_failures do + # shows rendered notebook + expect(page).to have_selector('.js-notebook-viewer-mounted') + + # does show a viewer switcher + expect(page).to have_selector('.js-blob-viewer-switcher') + + # show a disabled copy button + expect(page).to have_selector('.js-copy-blob-source-btn.disabled') - # does not show a viewer switcher - expect(page).not_to have_selector('.js-blob-viewer-switcher') + # shows a raw button + expect(page).to have_link('Open raw') - # does not show a copy button - expect(page).not_to have_selector('.js-copy-blob-source-btn') + # shows a download button + expect(page).to have_link('Download') - # shows a download button - expect(page).to have_link('Download') + # shows the rendered notebook + expect(page).to have_content('test') + end end end - end - context 'Jupiter Notebook file' do - before do - project.add_maintainer(project.creator) + context 'ISO file (stored in LFS)' do + context 'when LFS is enabled on the project' do + before do + allow(Gitlab.config.lfs).to receive(:enabled).and_return(true) + project.update_attribute(:lfs_enabled, true) - Files::CreateService.new( - project, - project.creator, - start_branch: 'master', - branch_name: 'master', - commit_message: "Add Jupiter Notebook", - file_path: 'files/basic.ipynb', - file_content: project.repository.blob_at('add-ipython-files', 'files/ipython/basic.ipynb').data - ).execute + visit_blob('files/lfs/lfs_object.iso') - visit_blob('files/basic.ipynb') + wait_for_requests + end - wait_for_requests - end + it 'displays the blob' do + aggregate_failures do + # shows a download link + expect(page).to have_link('Download (1.50 MiB)') + + # does not show a viewer switcher + expect(page).not_to have_selector('.js-blob-viewer-switcher') + + # does not show a copy button + expect(page).not_to have_selector('.js-copy-blob-source-btn') + + # shows a download button + expect(page).to have_link('Download') + end + end + end - it 'displays the blob' do - aggregate_failures do - # shows rendered notebook - expect(page).to have_selector('.js-notebook-viewer-mounted') + context 'when LFS is disabled on the project' do + before do + visit_blob('files/lfs/lfs_object.iso') - # does show a viewer switcher - expect(page).to have_selector('.js-blob-viewer-switcher') + wait_for_requests + end - # show a disabled copy button - expect(page).to have_selector('.js-copy-blob-source-btn.disabled') + it 'displays the blob' do + aggregate_failures do + # shows text + expect(page).to have_content('size 1575078') - # shows a raw button - expect(page).to have_link('Open raw') + # does not show a viewer switcher + expect(page).not_to have_selector('.js-blob-viewer-switcher') - # shows a download button - expect(page).to have_link('Download') + # shows an enabled copy button + expect(page).to have_selector('.js-copy-blob-source-btn:not(.disabled)') - # shows the rendered notebook - expect(page).to have_content('test') + # shows a raw button + expect(page).to have_link('Open raw') + end + end end end - end - context 'ISO file (stored in LFS)' do - context 'when LFS is enabled on the project' do + context 'ZIP file' do before do - allow(Gitlab.config.lfs).to receive(:enabled).and_return(true) - project.update_attribute(:lfs_enabled, true) - - visit_blob('files/lfs/lfs_object.iso') + visit_blob('Gemfile.zip') wait_for_requests end @@ -473,7 +451,7 @@ RSpec.describe 'File blob', :js do it 'displays the blob' do aggregate_failures do # shows a download link - expect(page).to have_link('Download (1.5 MB)') + expect(page).to have_link('Download (2.11 KiB)') # does not show a viewer switcher expect(page).not_to have_selector('.js-blob-viewer-switcher') @@ -487,578 +465,703 @@ RSpec.describe 'File blob', :js do end end - context 'when LFS is disabled on the project' do + context 'empty file' do before do - visit_blob('files/lfs/lfs_object.iso') + project.add_maintainer(project.creator) + + Files::CreateService.new( + project, + project.creator, + start_branch: 'master', + branch_name: 'master', + commit_message: "Add empty file", + file_path: 'files/empty.md', + file_content: '' + ).execute + + visit_blob('files/empty.md') wait_for_requests end - it 'displays the blob' do + it 'displays an error' do aggregate_failures do - # shows text - expect(page).to have_content('size 1575078') + # shows an error message + expect(page).to have_content('Empty file') # does not show a viewer switcher expect(page).not_to have_selector('.js-blob-viewer-switcher') - # shows an enabled copy button - expect(page).to have_selector('.js-copy-blob-source-btn:not(.disabled)') + # does not show a copy button + expect(page).not_to have_selector('.js-copy-blob-source-btn') - # shows a raw button - expect(page).to have_link('Open raw') + # does not show a download or raw button + expect(page).not_to have_link('Download') + expect(page).not_to have_link('Open raw') end end end - end - - context 'ZIP file' do - before do - visit_blob('Gemfile.zip') - wait_for_requests - end + context 'files with auxiliary viewers' do + describe '.gitlab-ci.yml' do + before do + project.add_maintainer(project.creator) - it 'displays the blob' do - aggregate_failures do - # shows a download link - expect(page).to have_link('Download (2.11 KB)') + Files::CreateService.new( + project, + project.creator, + start_branch: 'master', + branch_name: 'master', + commit_message: "Add .gitlab-ci.yml", + file_path: '.gitlab-ci.yml', + file_content: File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml')) + ).execute - # does not show a viewer switcher - expect(page).not_to have_selector('.js-blob-viewer-switcher') + visit_blob('.gitlab-ci.yml') + end - # does not show a copy button - expect(page).not_to have_selector('.js-copy-blob-source-btn') + it 'displays an auxiliary viewer' do + aggregate_failures do + # shows that configuration is valid + expect(page).to have_content('This GitLab CI configuration is valid.') - # shows a download button - expect(page).to have_link('Download') + # shows a learn more link + expect(page).to have_link('Learn more') + end + end end - end - end - context 'empty file' do - before do - project.add_maintainer(project.creator) + describe '.gitlab/route-map.yml' do + before do + project.add_maintainer(project.creator) + + Files::CreateService.new( + project, + project.creator, + start_branch: 'master', + branch_name: 'master', + commit_message: "Add .gitlab/route-map.yml", + file_path: '.gitlab/route-map.yml', + file_content: <<-MAP.strip_heredoc + # Team data + - source: 'data/team.yml' + public: 'team/' + MAP + ).execute - Files::CreateService.new( - project, - project.creator, - start_branch: 'master', - branch_name: 'master', - commit_message: "Add empty file", - file_path: 'files/empty.md', - file_content: '' - ).execute + visit_blob('.gitlab/route-map.yml') + end - visit_blob('files/empty.md') + it 'displays an auxiliary viewer' do + aggregate_failures do + # shows that map is valid + expect(page).to have_content('This Route Map is valid.') - wait_for_requests - end + # shows a learn more link + expect(page).to have_link('Learn more') + end + end + end - it 'displays an error' do - aggregate_failures do - # shows an error message - expect(page).to have_content('Empty file') + describe '.gitlab/dashboards/custom-dashboard.yml' do + before do + project.add_maintainer(project.creator) - # does not show a viewer switcher - expect(page).not_to have_selector('.js-blob-viewer-switcher') + Files::CreateService.new( + project, + project.creator, + start_branch: 'master', + branch_name: 'master', + commit_message: "Add .gitlab/dashboards/custom-dashboard.yml", + file_path: '.gitlab/dashboards/custom-dashboard.yml', + file_content: file_content + ).execute + end - # does not show a copy button - expect(page).not_to have_selector('.js-copy-blob-source-btn') + context 'with metrics_dashboard_exhaustive_validations feature flag off' do + before do + stub_feature_flags(metrics_dashboard_exhaustive_validations: false) + visit_blob('.gitlab/dashboards/custom-dashboard.yml') + end - # does not show a download or raw button - expect(page).not_to have_link('Download') - expect(page).not_to have_link('Open raw') - end - end - end + context 'valid dashboard file' do + let(:file_content) { File.read(Rails.root.join('config/prometheus/common_metrics.yml')) } - context 'binary file that appears to be text in the first 1024 bytes' do - before do - visit_blob('encoding/binary-1.bin', ref: 'binary-encoding') - end + it 'displays an auxiliary viewer' do + aggregate_failures do + # shows that dashboard yaml is valid + expect(page).to have_content('Metrics Dashboard YAML definition is valid.') - it 'displays the blob' do - aggregate_failures do - # shows a download link - expect(page).to have_link('Download (23.8 KB)') + # shows a learn more link + expect(page).to have_link('Learn more') + end + end + end - # does not show a viewer switcher - expect(page).not_to have_selector('.js-blob-viewer-switcher') + context 'invalid dashboard file' do + let(:file_content) { "dashboard: 'invalid'" } - # The specs below verify an arguably incorrect result, but since we only - # learn that the file is not actually text once the text viewer content - # is loaded asynchronously, there is no straightforward way to get these - # synchronously loaded elements to display correctly. - # - # Clicking the copy button will result in nothing being copied. - # Clicking the raw button will result in the binary file being downloaded, - # as expected. + it 'displays an auxiliary viewer' do + aggregate_failures do + # shows that dashboard yaml is invalid + expect(page).to have_content('Metrics Dashboard YAML definition is invalid:') + expect(page).to have_content("panel_groups: should be an array of panel_groups objects") - # shows an enabled copy button, incorrectly - expect(page).to have_selector('.js-copy-blob-source-btn:not(.disabled)') + # shows a learn more link + expect(page).to have_link('Learn more') + end + end + end + end - # shows a raw button, incorrectly - expect(page).to have_link('Open raw') - end - end - end + context 'with metrics_dashboard_exhaustive_validations feature flag on' do + before do + stub_feature_flags(metrics_dashboard_exhaustive_validations: true) + visit_blob('.gitlab/dashboards/custom-dashboard.yml') + end - context 'files with auxiliary viewers' do - before do - stub_feature_flags(refactor_blob_viewer: true) - end + context 'valid dashboard file' do + let(:file_content) { File.read(Rails.root.join('config/prometheus/common_metrics.yml')) } - describe '.gitlab-ci.yml' do - before do - project.add_maintainer(project.creator) + it 'displays an auxiliary viewer' do + aggregate_failures do + # shows that dashboard yaml is valid + expect(page).to have_content('Metrics Dashboard YAML definition is valid.') - Files::CreateService.new( - project, - project.creator, - start_branch: 'master', - branch_name: 'master', - commit_message: "Add .gitlab-ci.yml", - file_path: '.gitlab-ci.yml', - file_content: File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml')) - ).execute + # shows a learn more link + expect(page).to have_link('Learn more') + end + end + end - visit_blob('.gitlab-ci.yml') - end + context 'invalid dashboard file' do + let(:file_content) { "dashboard: 'invalid'" } - it 'displays an auxiliary viewer' do - aggregate_failures do - # shows that configuration is valid - expect(page).to have_content('This GitLab CI configuration is valid.') + it 'displays an auxiliary viewer' do + aggregate_failures do + # shows that dashboard yaml is invalid + expect(page).to have_content('Metrics Dashboard YAML definition is invalid:') + expect(page).to have_content("root is missing required keys: panel_groups") - # shows a learn more link - expect(page).to have_link('Learn more') + # shows a learn more link + expect(page).to have_link('Learn more') + end + end + end end end - end - describe '.gitlab/route-map.yml' do - before do - project.add_maintainer(project.creator) + context 'LICENSE' do + before do + visit_blob('LICENSE') + end - Files::CreateService.new( - project, - project.creator, - start_branch: 'master', - branch_name: 'master', - commit_message: "Add .gitlab/route-map.yml", - file_path: '.gitlab/route-map.yml', - file_content: <<-MAP.strip_heredoc - # Team data - - source: 'data/team.yml' - public: 'team/' - MAP - ).execute + it 'displays an auxiliary viewer' do + aggregate_failures do + # shows license + expect(page).to have_content('This project is licensed under the MIT License.') - visit_blob('.gitlab/route-map.yml') + # shows a learn more link + expect(page).to have_link('Learn more', href: 'http://choosealicense.com/licenses/mit/') + end + end end - it 'displays an auxiliary viewer' do - aggregate_failures do - # shows that map is valid - expect(page).to have_content('This Route Map is valid.') + context '*.gemspec' do + before do + project.add_maintainer(project.creator) - # shows a learn more link - expect(page).to have_link('Learn more') + Files::CreateService.new( + project, + project.creator, + start_branch: 'master', + branch_name: 'master', + commit_message: "Add activerecord.gemspec", + file_path: 'activerecord.gemspec', + file_content: <<-SPEC.strip_heredoc + Gem::Specification.new do |s| + s.platform = Gem::Platform::RUBY + s.name = "activerecord" + end + SPEC + ).execute + + visit_blob('activerecord.gemspec') end - end - end - describe '.gitlab/dashboards/custom-dashboard.yml' do - before do - project.add_maintainer(project.creator) + it 'displays an auxiliary viewer' do + aggregate_failures do + # shows names of dependency manager and package + expect(page).to have_content('This project manages its dependencies using RubyGems.') - Files::CreateService.new( - project, - project.creator, - start_branch: 'master', - branch_name: 'master', - commit_message: "Add .gitlab/dashboards/custom-dashboard.yml", - file_path: '.gitlab/dashboards/custom-dashboard.yml', - file_content: file_content - ).execute + # shows a learn more link + expect(page).to have_link('Learn more', href: 'https://rubygems.org/') + end + end end - context 'with metrics_dashboard_exhaustive_validations feature flag off' do + context 'CONTRIBUTING.md' do before do - stub_feature_flags(metrics_dashboard_exhaustive_validations: false) - visit_blob('.gitlab/dashboards/custom-dashboard.yml') + file_name = 'CONTRIBUTING.md' + + create_file(file_name, '## Contribution guidelines') + visit_blob(file_name) end - context 'valid dashboard file' do - let(:file_content) { File.read(Rails.root.join('config/prometheus/common_metrics.yml')) } + it 'displays an auxiliary viewer' do + aggregate_failures do + expect(page).to have_content("After you've reviewed these contribution guidelines, you'll be all set to contribute to this project.") + end + end + end - it 'displays an auxiliary viewer' do - aggregate_failures do - # shows that dashboard yaml is valid - expect(page).to have_content('Metrics Dashboard YAML definition is valid.') + context 'CHANGELOG.md' do + before do + file_name = 'CHANGELOG.md' - # shows a learn more link - expect(page).to have_link('Learn more') - end + create_file(file_name, '## Changelog for v1.0.0') + visit_blob(file_name) + end + + it 'displays an auxiliary viewer' do + aggregate_failures do + expect(page).to have_content("To find the state of this project's repository at the time of any of these versions, check out the tags.") end end + end - context 'invalid dashboard file' do - let(:file_content) { "dashboard: 'invalid'" } + context 'Cargo.toml' do + before do + file_name = 'Cargo.toml' - it 'displays an auxiliary viewer' do - aggregate_failures do - # shows that dashboard yaml is invalid - expect(page).to have_content('Metrics Dashboard YAML definition is invalid:') - expect(page).to have_content("panel_groups: should be an array of panel_groups objects") + create_file(file_name, ' + [package] + name = "hello_world" # the name of the package + version = "0.1.0" # the current version, obeying semver + authors = ["Alice <a@example.com>", "Bob <b@example.com>"] + ') + visit_blob(file_name) + end - # shows a learn more link - expect(page).to have_link('Learn more') - end + it 'displays an auxiliary viewer' do + aggregate_failures do + expect(page).to have_content("This project manages its dependencies using Cargo.") end end end - context 'with metrics_dashboard_exhaustive_validations feature flag on' do + context 'Cartfile' do before do - stub_feature_flags(metrics_dashboard_exhaustive_validations: true) - visit_blob('.gitlab/dashboards/custom-dashboard.yml') + file_name = 'Cartfile' + + create_file(file_name, ' + gitlab "Alamofire/Alamofire" == 4.9.0 + gitlab "Alamofire/AlamofireImage" ~> 3.4 + ') + visit_blob(file_name) + end + + it 'displays an auxiliary viewer' do + aggregate_failures do + expect(page).to have_content("This project manages its dependencies using Carthage.") + end end + end - context 'valid dashboard file' do - let(:file_content) { File.read(Rails.root.join('config/prometheus/common_metrics.yml')) } + context 'composer.json' do + before do + file_name = 'composer.json' - it 'displays an auxiliary viewer' do - aggregate_failures do - # shows that dashboard yaml is valid - expect(page).to have_content('Metrics Dashboard YAML definition is valid.') + create_file(file_name, ' + { + "license": "MIT" + } + ') + visit_blob(file_name) + end - # shows a learn more link - expect(page).to have_link('Learn more') - end + it 'displays an auxiliary viewer' do + aggregate_failures do + expect(page).to have_content("This project manages its dependencies using Composer.") end end + end - context 'invalid dashboard file' do - let(:file_content) { "dashboard: 'invalid'" } + context 'Gemfile' do + before do + file_name = 'Gemfile' - it 'displays an auxiliary viewer' do - aggregate_failures do - # shows that dashboard yaml is invalid - expect(page).to have_content('Metrics Dashboard YAML definition is invalid:') - expect(page).to have_content("root is missing required keys: panel_groups") + create_file(file_name, ' + source "https://rubygems.org" - # shows a learn more link - expect(page).to have_link('Learn more') - end + # Gems here + ') + visit_blob(file_name) + end + + it 'displays an auxiliary viewer' do + aggregate_failures do + expect(page).to have_content("This project manages its dependencies using Bundler.") end end end - end - context 'LICENSE' do - before do - visit_blob('LICENSE') - end + context 'Godeps.json' do + before do + file_name = 'Godeps.json' - it 'displays an auxiliary viewer' do - aggregate_failures do - # shows license - expect(page).to have_content('This project is licensed under the MIT License.') + create_file(file_name, ' + { + "GoVersion": "go1.6" + } + ') + visit_blob(file_name) + end - # shows a learn more link - expect(page).to have_link('Learn more', href: 'http://choosealicense.com/licenses/mit/') + it 'displays an auxiliary viewer' do + aggregate_failures do + expect(page).to have_content("This project manages its dependencies using godep.") + end end end - end - context '*.gemspec' do - before do - project.add_maintainer(project.creator) + context 'go.mod' do + before do + file_name = 'go.mod' - Files::CreateService.new( - project, - project.creator, - start_branch: 'master', - branch_name: 'master', - commit_message: "Add activerecord.gemspec", - file_path: 'activerecord.gemspec', - file_content: <<-SPEC.strip_heredoc - Gem::Specification.new do |s| - s.platform = Gem::Platform::RUBY - s.name = "activerecord" - end - SPEC - ).execute + create_file(file_name, ' + module example.com/mymodule + + go 1.14 + ') + visit_blob(file_name) + end - visit_blob('activerecord.gemspec') + it 'displays an auxiliary viewer' do + aggregate_failures do + expect(page).to have_content("This project manages its dependencies using Go Modules.") + end + end end - it 'displays an auxiliary viewer' do - aggregate_failures do - # shows names of dependency manager and package - expect(page).to have_content('This project manages its dependencies using RubyGems.') + context 'package.json' do + before do + file_name = 'package.json' - # shows a learn more link - expect(page).to have_link('Learn more', href: 'https://rubygems.org/') + create_file(file_name, ' + { + "name": "my-awesome-package", + "version": "1.0.0" + } + ') + visit_blob(file_name) + end + + it 'displays an auxiliary viewer' do + aggregate_failures do + expect(page).to have_content("This project manages its dependencies using npm.") + end end end - end - context 'CONTRIBUTING.md' do - before do - file_name = 'CONTRIBUTING.md' + context 'podfile' do + before do + file_name = 'podfile' - create_file(file_name, '## Contribution guidelines') - visit_blob(file_name) - end + create_file(file_name, 'platform :ios, "8.0"') + visit_blob(file_name) + end - it 'displays an auxiliary viewer' do - aggregate_failures do - expect(page).to have_content("After you've reviewed these contribution guidelines, you'll be all set to contribute to this project.") + it 'displays an auxiliary viewer' do + aggregate_failures do + expect(page).to have_content("This project manages its dependencies using CocoaPods.") + end end end - end - context 'CHANGELOG.md' do - before do - file_name = 'CHANGELOG.md' + context 'test.podspec' do + before do + file_name = 'test.podspec' - create_file(file_name, '## Changelog for v1.0.0') - visit_blob(file_name) - end + create_file(file_name, ' + Pod::Spec.new do |s| + s.name = "TensorFlowLiteC" + ') + visit_blob(file_name) + end - it 'displays an auxiliary viewer' do - aggregate_failures do - expect(page).to have_content("To find the state of this project's repository at the time of any of these versions, check out the tags.") + it 'displays an auxiliary viewer' do + aggregate_failures do + expect(page).to have_content("This project manages its dependencies using CocoaPods.") + end end end - end - context 'Cargo.toml' do - before do - file_name = 'Cargo.toml' + context 'JSON.podspec.json' do + before do + file_name = 'JSON.podspec.json' - create_file(file_name, ' - [package] - name = "hello_world" # the name of the package - version = "0.1.0" # the current version, obeying semver - authors = ["Alice <a@example.com>", "Bob <b@example.com>"] - ') - visit_blob(file_name) - end + create_file(file_name, ' + { + "name": "JSON" + } + ') + visit_blob(file_name) + end - it 'displays an auxiliary viewer' do - aggregate_failures do - expect(page).to have_content("This project manages its dependencies using Cargo.") + it 'displays an auxiliary viewer' do + aggregate_failures do + expect(page).to have_content("This project manages its dependencies using CocoaPods.") + end end end - end - context 'Cartfile' do - before do - file_name = 'Cartfile' + context 'requirements.txt' do + before do + file_name = 'requirements.txt' - create_file(file_name, ' - gitlab "Alamofire/Alamofire" == 4.9.0 - gitlab "Alamofire/AlamofireImage" ~> 3.4 - ') - visit_blob(file_name) + create_file(file_name, 'Project requirements') + visit_blob(file_name) + end + + it 'displays an auxiliary viewer' do + aggregate_failures do + expect(page).to have_content("This project manages its dependencies using pip.") + end + end end - it 'displays an auxiliary viewer' do - aggregate_failures do - expect(page).to have_content("This project manages its dependencies using Carthage.") + context 'yarn.lock' do + before do + file_name = 'yarn.lock' + + create_file(file_name, ' + # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. + # yarn lockfile v1 + ') + visit_blob(file_name) + end + + it 'displays an auxiliary viewer' do + aggregate_failures do + expect(page).to have_content("This project manages its dependencies using Yarn.") + end end end end - context 'composer.json' do + context 'realtime pipelines' do before do - file_name = 'composer.json' + Files::CreateService.new( + project, + project.creator, + start_branch: 'feature', + branch_name: 'feature', + commit_message: "Add ruby file", + file_path: 'files/ruby/test.rb', + file_content: "# Awesome content" + ).execute - create_file(file_name, ' - { - "license": "MIT" - } - ') - visit_blob(file_name) + create(:ci_pipeline, status: 'running', project: project, ref: 'feature', sha: project.commit('feature').sha) + visit_blob('files/ruby/test.rb', ref: 'feature') end - it 'displays an auxiliary viewer' do - aggregate_failures do - expect(page).to have_content("This project manages its dependencies using Composer.") + it 'shows the realtime pipeline status' do + page.within('.commit-actions') do + expect(page).to have_css('.ci-status-icon') + expect(page).to have_css('.ci-status-icon-running') + expect(page).to have_css('.js-ci-status-icon-running') end end end - context 'Gemfile' do - before do - file_name = 'Gemfile' + context 'for subgroups' do + let(:group) { create(:group) } + let(:subgroup) { create(:group, parent: group) } + let(:project) { create(:project, :public, :repository, group: subgroup) } - create_file(file_name, ' - source "https://rubygems.org" + it 'renders tree table without errors' do + visit_blob('README.md') - # Gems here - ') - visit_blob(file_name) + expect(page).to have_selector('.file-content') + expect(page).not_to have_selector('.flash-alert') end - it 'displays an auxiliary viewer' do - aggregate_failures do - expect(page).to have_content("This project manages its dependencies using Bundler.") - end + it 'displays a GPG badge' do + visit_blob('CONTRIBUTING.md', ref: '33f3729a45c02fc67d00adb1b8bca394b0e761d9') + + expect(page).not_to have_selector '.gpg-status-box.js-loading-gpg-badge' + expect(page).to have_selector '.gpg-status-box.invalid' end end - context 'Godeps.json' do - before do - file_name = 'Godeps.json' + context 'on signed merge commit' do + it 'displays a GPG badge' do + visit_blob('conflicting-file.md', ref: '6101e87e575de14b38b4e1ce180519a813671e10') - create_file(file_name, ' - { - "GoVersion": "go1.6" - } - ') - visit_blob(file_name) - end - - it 'displays an auxiliary viewer' do - aggregate_failures do - expect(page).to have_content("This project manages its dependencies using godep.") - end + expect(page).not_to have_selector '.gpg-status-box.js-loading-gpg-badge' + expect(page).to have_selector '.gpg-status-box.invalid' end end - context 'go.mod' do + context 'when static objects external storage is enabled' do before do - file_name = 'go.mod' + stub_application_setting(static_objects_external_storage_url: 'https://cdn.gitlab.com') + end - create_file(file_name, ' - module example.com/mymodule + context 'public project' do + before do + visit_blob('README.md') + end - go 1.14 - ') - visit_blob(file_name) - end + it 'shows open raw and download buttons with external storage URL prepended to their href' do + path = project_raw_path(project, 'master/README.md') + raw_uri = "https://cdn.gitlab.com#{path}" + download_uri = "https://cdn.gitlab.com#{path}?inline=false" - it 'displays an auxiliary viewer' do - aggregate_failures do - expect(page).to have_content("This project manages its dependencies using Go Modules.") + aggregate_failures do + expect(page).to have_link 'Open raw', href: raw_uri + expect(page).to have_link 'Download', href: download_uri + end end end end + end - context 'package.json' do - before do - file_name = 'package.json' + context 'with refactor_blob_viewer feature flag disabled' do + before do + stub_feature_flags(refactor_blob_viewer: false) + end - create_file(file_name, ' - { - "name": "my-awesome-package", - "version": "1.0.0" - } - ') - visit_blob(file_name) - end + context 'when ref switch' do + # We need to unsre that this test runs with the refactor_blob_viewer feature flag enabled + # This will be addressed in https://gitlab.com/gitlab-org/gitlab/-/issues/351558 - it 'displays an auxiliary viewer' do - aggregate_failures do - expect(page).to have_content("This project manages its dependencies using npm.") + def switch_ref_to(ref_name) + first('.qa-branches-select').click # rubocop:disable QA/SelectorUsage + + page.within '.project-refs-form' do + click_link ref_name + wait_for_requests end end - end - context 'podfile' do - before do - file_name = 'podfile' + context 'when highlighting lines' do + it 'displays single highlighted line number of different ref' do + visit_blob('files/js/application.js', anchor: 'L1') - create_file(file_name, 'platform :ios, "8.0"') - visit_blob(file_name) - end + switch_ref_to('feature') - it 'displays an auxiliary viewer' do - aggregate_failures do - expect(page).to have_content("This project manages its dependencies using CocoaPods.") + page.within '.blob-content' do + expect(find_by_id('LC1')[:class]).to include("hll") + end end - end - end - context 'test.podspec' do - before do - file_name = 'test.podspec' + it 'displays multiple highlighted line numbers of different ref' do + visit_blob('files/js/application.js', anchor: 'L1-3') - create_file(file_name, ' - Pod::Spec.new do |s| - s.name = "TensorFlowLiteC" - ') - visit_blob(file_name) - end + switch_ref_to('feature') - it 'displays an auxiliary viewer' do - aggregate_failures do - expect(page).to have_content("This project manages its dependencies using CocoaPods.") + page.within '.blob-content' do + expect(find_by_id('LC1')[:class]).to include("hll") + expect(find_by_id('LC2')[:class]).to include("hll") + expect(find_by_id('LC3')[:class]).to include("hll") + end end end end - context 'JSON.podspec.json' do - before do - file_name = 'JSON.podspec.json' + context 'visiting with a line number anchor' do + # We need to unsre that this test runs with the refactor_blob_viewer feature flag enabled + # This will be addressed in https://gitlab.com/gitlab-org/gitlab/-/issues/351558 - create_file(file_name, ' - { - "name": "JSON" - } - ') - visit_blob(file_name) + before do + visit_blob('files/markdown/ruby-style-guide.md', anchor: 'L1') end - it 'displays an auxiliary viewer' do + it 'displays the blob using the simple viewer' do aggregate_failures do - expect(page).to have_content("This project manages its dependencies using CocoaPods.") + # hides the rich viewer + expect(page).to have_selector('.blob-viewer[data-type="simple"]') + expect(page).not_to have_selector('.blob-viewer[data-type="rich"]') + + # highlights the line in question + expect(page).to have_selector('#LC1.hll') + + # shows highlighted Markdown code + expect(page).to have_css(".js-syntax-highlight") + expect(page).to have_content("[PEP-8](http://www.python.org/dev/peps/pep-0008/)") + + # shows an enabled copy button + expect(page).to have_selector('.js-copy-blob-source-btn:not(.disabled)') end end end - context 'requirements.txt' do - before do - file_name = 'requirements.txt' + context 'binary file that appears to be text in the first 1024 bytes' do + # We need to unsre that this test runs with the refactor_blob_viewer feature flag enabled + # This will be addressed in https://gitlab.com/gitlab-org/gitlab/-/issues/351559 - create_file(file_name, 'Project requirements') - visit_blob(file_name) + before do + visit_blob('encoding/binary-1.bin', ref: 'binary-encoding') end - - it 'displays an auxiliary viewer' do + it 'displays the blob' do aggregate_failures do - expect(page).to have_content("This project manages its dependencies using pip.") + # shows a download link + expect(page).to have_link('Download (23.8 KB)') + # does not show a viewer switcher + expect(page).not_to have_selector('.js-blob-viewer-switcher') + # The specs below verify an arguably incorrect result, but since we only + # learn that the file is not actually text once the text viewer content + # is loaded asynchronously, there is no straightforward way to get these + # synchronously loaded elements to display correctly. + # + # Clicking the copy button will result in nothing being copied. + # Clicking the raw button will result in the binary file being downloaded, + # as expected. + # shows an enabled copy button, incorrectly + expect(page).to have_selector('.js-copy-blob-source-btn:not(.disabled)') + # shows a raw button, incorrectly + expect(page).to have_link('Open raw') end end end - context 'yarn.lock' do - before do - file_name = 'yarn.lock' + context 'when static objects external storage is enabled' do + # We need to unsre that this test runs with the refactor_blob_viewer feature flag enabled + # This will be addressed in https://gitlab.com/gitlab-org/gitlab/-/issues/351555 - create_file(file_name, ' - # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. - # yarn lockfile v1 - ') - visit_blob(file_name) + before do + stub_application_setting(static_objects_external_storage_url: 'https://cdn.gitlab.com') end - it 'displays an auxiliary viewer' do - aggregate_failures do - expect(page).to have_content("This project manages its dependencies using Yarn.") + context 'private project' do + let_it_be(:project) { create(:project, :repository, :private) } + let_it_be(:user) { create(:user) } + + before do + project.add_developer(user) + + sign_in(user) + visit_blob('README.md') + end + + it 'shows open raw and download buttons with external storage URL prepended and user token appended to their href' do + path = project_raw_path(project, 'master/README.md') + raw_uri = "https://cdn.gitlab.com#{path}?token=#{user.static_object_token}" + download_uri = "https://cdn.gitlab.com#{path}?inline=false&token=#{user.static_object_token}" + + aggregate_failures do + expect(page).to have_link 'Open raw', href: raw_uri + expect(page).to have_link 'Download', href: download_uri + end end end end - context 'when refactor_blob_viewer is disabled' do - before do - stub_feature_flags(refactor_blob_viewer: false) - end + context 'files with auxiliary viewers' do + # This context is the same as the other 'files with auxiliary viewers' in this file, we just ensure that the auxiliary viewers still work this the refactor_blob_viewer disabled + # It should be safe to remove once we rollout the refactored blob viewer describe '.gitlab-ci.yml' do before do @@ -1554,104 +1657,4 @@ RSpec.describe 'File blob', :js do end end end - - context 'realtime pipelines' do - before do - Files::CreateService.new( - project, - project.creator, - start_branch: 'feature', - branch_name: 'feature', - commit_message: "Add ruby file", - file_path: 'files/ruby/test.rb', - file_content: "# Awesome content" - ).execute - - create(:ci_pipeline, status: 'running', project: project, ref: 'feature', sha: project.commit('feature').sha) - visit_blob('files/ruby/test.rb', ref: 'feature') - end - - it 'shows the realtime pipeline status' do - page.within('.commit-actions') do - expect(page).to have_css('.ci-status-icon') - expect(page).to have_css('.ci-status-icon-running') - expect(page).to have_css('.js-ci-status-icon-running') - end - end - end - - context 'for subgroups' do - let(:group) { create(:group) } - let(:subgroup) { create(:group, parent: group) } - let(:project) { create(:project, :public, :repository, group: subgroup) } - - it 'renders tree table without errors' do - visit_blob('README.md') - - expect(page).to have_selector('.file-content') - expect(page).not_to have_selector('.flash-alert') - end - - it 'displays a GPG badge' do - visit_blob('CONTRIBUTING.md', ref: '33f3729a45c02fc67d00adb1b8bca394b0e761d9') - - expect(page).not_to have_selector '.gpg-status-box.js-loading-gpg-badge' - expect(page).to have_selector '.gpg-status-box.invalid' - end - end - - context 'on signed merge commit' do - it 'displays a GPG badge' do - visit_blob('conflicting-file.md', ref: '6101e87e575de14b38b4e1ce180519a813671e10') - - expect(page).not_to have_selector '.gpg-status-box.js-loading-gpg-badge' - expect(page).to have_selector '.gpg-status-box.invalid' - end - end - - context 'when static objects external storage is enabled' do - before do - stub_application_setting(static_objects_external_storage_url: 'https://cdn.gitlab.com') - end - - context 'private project' do - let_it_be(:project) { create(:project, :repository, :private) } - let_it_be(:user) { create(:user) } - - before do - project.add_developer(user) - - sign_in(user) - visit_blob('README.md') - end - - it 'shows open raw and download buttons with external storage URL prepended and user token appended to their href' do - path = project_raw_path(project, 'master/README.md') - raw_uri = "https://cdn.gitlab.com#{path}?token=#{user.static_object_token}" - download_uri = "https://cdn.gitlab.com#{path}?inline=false&token=#{user.static_object_token}" - - aggregate_failures do - expect(page).to have_link 'Open raw', href: raw_uri - expect(page).to have_link 'Download', href: download_uri - end - end - end - - context 'public project' do - before do - visit_blob('README.md') - end - - it 'shows open raw and download buttons with external storage URL prepended to their href' do - path = project_raw_path(project, 'master/README.md') - raw_uri = "https://cdn.gitlab.com#{path}" - download_uri = "https://cdn.gitlab.com#{path}?inline=false" - - aggregate_failures do - expect(page).to have_link 'Open raw', href: raw_uri - expect(page).to have_link 'Download', href: download_uri - end - end - end - end end diff --git a/spec/frontend/__helpers__/matchers/to_match_interpolated_text.js b/spec/frontend/__helpers__/matchers/to_match_interpolated_text.js index 4ce814a01b4..41e69bffd88 100644 --- a/spec/frontend/__helpers__/matchers/to_match_interpolated_text.js +++ b/spec/frontend/__helpers__/matchers/to_match_interpolated_text.js @@ -1,4 +1,5 @@ -export const toMatchInterpolatedText = (received, match) => { +// Custom matchers are object methods and should be traditional functions to be able to access `utils` on `this` +export function toMatchInterpolatedText(received, match) { let clearReceived; let clearMatch; @@ -15,16 +16,14 @@ export const toMatchInterpolatedText = (received, match) => { const pass = clearReceived === clearMatch; const message = pass ? () => ` - \n\n - Expected: ${this.utils.printExpected(clearReceived)} - To not equal: ${this.utils.printReceived(clearMatch)} + Expected to not be: ${this.utils.printExpected(clearMatch)} + Received: ${this.utils.printReceived(clearReceived)} ` : () => ` - \n\n - Expected: ${this.utils.printExpected(clearReceived)} - To equal: ${this.utils.printReceived(clearMatch)} + Expected to be: ${this.utils.printExpected(clearMatch)} + Received: ${this.utils.printReceived(clearReceived)} `; return { actual: received, message, pass }; -}; +} diff --git a/spec/frontend/blob/components/__snapshots__/blob_header_spec.js.snap b/spec/frontend/blob/components/__snapshots__/blob_header_spec.js.snap index 22bec77276b..b3d93906445 100644 --- a/spec/frontend/blob/components/__snapshots__/blob_header_spec.js.snap +++ b/spec/frontend/blob/components/__snapshots__/blob_header_spec.js.snap @@ -13,6 +13,7 @@ exports[`Blob Header Default Actions rendering matches the snapshot 1`] = ` <blob-filepath-stub blob="[object Object]" + showpath="true" /> </div> diff --git a/spec/frontend/environments/environment_pin_spec.js b/spec/frontend/environments/environment_pin_spec.js index a9a58071e12..669c974ea4f 100644 --- a/spec/frontend/environments/environment_pin_spec.js +++ b/spec/frontend/environments/environment_pin_spec.js @@ -1,5 +1,9 @@ +import Vue from 'vue'; +import VueApollo from 'vue-apollo'; import { GlDropdownItem } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; +import cancelAutoStopMutation from '~/environments/graphql/mutations/cancel_auto_stop.mutation.graphql'; +import createMockApollo from 'helpers/mock_apollo_helper'; import PinComponent from '~/environments/components/environment_pin.vue'; import eventHub from '~/environments/event_hub'; @@ -18,28 +22,66 @@ describe('Pin Component', () => { const autoStopUrl = '/root/auto-stop-env-test/-/environments/38/cancel_auto_stop'; - beforeEach(() => { - factory({ - propsData: { - autoStopUrl, - }, + describe('without graphql', () => { + beforeEach(() => { + factory({ + propsData: { + autoStopUrl, + }, + }); }); - }); - afterEach(() => { - wrapper.destroy(); - }); + afterEach(() => { + wrapper.destroy(); + }); - it('should render the component with descriptive text', () => { - expect(wrapper.text()).toBe('Prevent auto-stopping'); + it('should render the component with descriptive text', () => { + expect(wrapper.text()).toBe('Prevent auto-stopping'); + }); + + it('should emit onPinClick when clicked', () => { + const eventHubSpy = jest.spyOn(eventHub, '$emit'); + const item = wrapper.find(GlDropdownItem); + + item.vm.$emit('click'); + + expect(eventHubSpy).toHaveBeenCalledWith('cancelAutoStop', autoStopUrl); + }); }); - it('should emit onPinClick when clicked', () => { - const eventHubSpy = jest.spyOn(eventHub, '$emit'); - const item = wrapper.find(GlDropdownItem); + describe('with graphql', () => { + Vue.use(VueApollo); + let mockApollo; - item.vm.$emit('click'); + beforeEach(() => { + mockApollo = createMockApollo(); + factory({ + propsData: { + autoStopUrl, + graphql: true, + }, + apolloProvider: mockApollo, + }); + }); - expect(eventHubSpy).toHaveBeenCalledWith('cancelAutoStop', autoStopUrl); + afterEach(() => { + wrapper.destroy(); + }); + + it('should render the component with descriptive text', () => { + expect(wrapper.text()).toBe('Prevent auto-stopping'); + }); + + it('should emit onPinClick when clicked', () => { + jest.spyOn(mockApollo.defaultClient, 'mutate'); + const item = wrapper.find(GlDropdownItem); + + item.vm.$emit('click'); + + expect(mockApollo.defaultClient.mutate).toHaveBeenCalledWith({ + mutation: cancelAutoStopMutation, + variables: { autoStopUrl }, + }); + }); }); }); diff --git a/spec/frontend/environments/graphql/resolvers_spec.js b/spec/frontend/environments/graphql/resolvers_spec.js index 6b53dc24f0f..21d7e09bad5 100644 --- a/spec/frontend/environments/graphql/resolvers_spec.js +++ b/spec/frontend/environments/graphql/resolvers_spec.js @@ -173,9 +173,7 @@ describe('~/frontend/environments/graphql/resolvers', () => { it('should post to the auto stop path', async () => { mock.onPost(ENDPOINT).reply(200); - await mockResolvers.Mutation.cancelAutoStop(null, { - environment: { autoStopPath: ENDPOINT }, - }); + await mockResolvers.Mutation.cancelAutoStop(null, { autoStopUrl: ENDPOINT }); expect(mock.history.post).toContainEqual( expect.objectContaining({ url: ENDPOINT, method: 'post' }), diff --git a/spec/frontend/environments/new_environment_item_spec.js b/spec/frontend/environments/new_environment_item_spec.js index de36b701a16..0d5d216ba02 100644 --- a/spec/frontend/environments/new_environment_item_spec.js +++ b/spec/frontend/environments/new_environment_item_spec.js @@ -191,9 +191,9 @@ describe('~/environments/components/new_environment_item.vue', () => { }); it('shows the option to pin the environment if there is an autostop date', () => { - const rollback = wrapper.findByRole('menuitem', { name: __('Prevent auto-stopping') }); + const pin = wrapper.findByRole('menuitem', { name: __('Prevent auto-stopping') }); - expect(rollback.exists()).toBe(true); + expect(pin.exists()).toBe(true); }); it('shows when the environment auto stops', () => { @@ -211,9 +211,9 @@ describe('~/environments/components/new_environment_item.vue', () => { it('does not show the option to pin the environment if there is no autostop date', () => { wrapper = createWrapper({ apolloProvider: createApolloProvider() }); - const rollback = wrapper.findByRole('menuitem', { name: __('Prevent auto-stopping') }); + const pin = wrapper.findByRole('menuitem', { name: __('Prevent auto-stopping') }); - expect(rollback.exists()).toBe(false); + expect(pin.exists()).toBe(false); }); it('does not show when the environment auto stops', () => { @@ -246,9 +246,9 @@ describe('~/environments/components/new_environment_item.vue', () => { it('does not show the option to pin the environment if there is no autostop date', () => { wrapper = createWrapper({ apolloProvider: createApolloProvider() }); - const rollback = wrapper.findByRole('menuitem', { name: __('Prevent auto-stopping') }); + const pin = wrapper.findByRole('menuitem', { name: __('Prevent auto-stopping') }); - expect(rollback.exists()).toBe(false); + expect(pin.exists()).toBe(false); }); it('does not show when the environment auto stops', () => { diff --git a/spec/frontend/repository/components/blob_content_viewer_spec.js b/spec/frontend/repository/components/blob_content_viewer_spec.js index 70808a3c7b1..eb9b443c694 100644 --- a/spec/frontend/repository/components/blob_content_viewer_spec.js +++ b/spec/frontend/repository/components/blob_content_viewer_spec.js @@ -236,7 +236,7 @@ describe('Blob content viewer component', () => { await waitForPromises(); - expect(loadViewer).toHaveBeenCalledWith(viewer); + expect(loadViewer).toHaveBeenCalledWith(viewer, false); expect(wrapper.findComponent(loadViewerReturnValue).exists()).toBe(true); }, ); diff --git a/spec/frontend/repository/components/blob_viewers/lfs_viewer_spec.js b/spec/frontend/repository/components/blob_viewers/lfs_viewer_spec.js new file mode 100644 index 00000000000..59cca23ebfc --- /dev/null +++ b/spec/frontend/repository/components/blob_viewers/lfs_viewer_spec.js @@ -0,0 +1,41 @@ +import { GlLink, GlSprintf } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; +import LfsViewer from '~/repository/components/blob_viewers/lfs_viewer.vue'; + +describe('LFS Viewer', () => { + let wrapper; + + const DEFAULT_PROPS = { + fileName: 'file_name.js', + filePath: '/some/file/path', + }; + + const createComponent = () => { + wrapper = shallowMount(LfsViewer, { + propsData: { ...DEFAULT_PROPS }, + stubs: { GlSprintf }, + }); + }; + + const findLink = () => wrapper.findComponent(GlLink); + + beforeEach(() => createComponent()); + + afterEach(() => wrapper.destroy()); + + it('renders the correct text', () => { + expect(wrapper.text()).toBe( + 'This content could not be displayed because it is stored in LFS. You can download it instead.', + ); + }); + + it('renders download link', () => { + const { filePath, fileName } = DEFAULT_PROPS; + + expect(findLink().attributes()).toMatchObject({ + target: '_blank', + href: filePath, + download: fileName, + }); + }); +}); diff --git a/spec/frontend/repository/mock_data.js b/spec/frontend/repository/mock_data.js index b8ab5cfcef1..2df89965fbe 100644 --- a/spec/frontend/repository/mock_data.js +++ b/spec/frontend/repository/mock_data.js @@ -17,6 +17,7 @@ export const simpleViewerMock = { canCurrentUserPushToBranch: true, archived: false, storedExternally: false, + externalStorageUrl: '', externalStorage: 'lfs', rawPath: 'some_file.js', replacePath: 'some_file.js/replace', diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb index cc443afee6e..1d78778c757 100644 --- a/spec/helpers/projects_helper_spec.rb +++ b/spec/helpers/projects_helper_spec.rb @@ -345,6 +345,14 @@ RSpec.describe ProjectsHelper do expect(link).not_to include(user.name) end end + + context 'when user is nil' do + it 'returns "(deleted)"' do + link = helper.link_to_member(project, nil) + + expect(link).to eq("(deleted)") + end + end end describe 'default_clone_protocol' do diff --git a/spec/lib/gitlab/rack_attack/request_spec.rb b/spec/lib/gitlab/rack_attack/request_spec.rb index ecdcc23e588..37db588ce16 100644 --- a/spec/lib/gitlab/rack_attack/request_spec.rb +++ b/spec/lib/gitlab/rack_attack/request_spec.rb @@ -5,6 +5,20 @@ require 'spec_helper' RSpec.describe Gitlab::RackAttack::Request do using RSpec::Parameterized::TableSyntax + let(:path) { '/' } + 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 } @@ -16,11 +30,172 @@ 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 + '/foo/api' | false + + '/api' | true + '/api/v4/groups/1' | true + end + + with_them do + it { is_expected.to eq(expected) } + end + end + + describe '#api_internal_request?' do + subject { request.api_internal_request? } + + where(:path, :expected) do + '/' | false + '/groups' | false + '/api' | false + '/api/v4/groups/1' | false + '/api/v4/internal' | false + '/foo/api/v4/internal' | false + + '/api/v4/internal/' | true + '/api/v4/internal/foo' | true + '/api/v1/internal/foo' | true + end + + with_them do + it { is_expected.to eq(expected) } + end + end + + describe '#health_check_request?' do + subject { request.health_check_request? } + + where(:path, :expected) do + '/' | false + '/groups' | false + '/foo/-/health' | false + + '/-/health' | true + '/-/liveness' | true + '/-/readiness' | true + '/-/metrics' | true + '/-/health/foo' | true + '/-/liveness/foo' | true + '/-/readiness/foo' | true + '/-/metrics/foo' | true + end + + with_them do + it { is_expected.to eq(expected) } + end + end + + describe '#container_registry_event?' do + subject { request.container_registry_event? } + + where(:path, :expected) do + '/' | false + '/groups' | false + '/api/v4/container_registry_event' | false + '/foo/api/v4/container_registry_event/' | false + + '/api/v4/container_registry_event/' | true + '/api/v4/container_registry_event/foo' | true + '/api/v1/container_registry_event/foo' | true + end + + with_them do + it { is_expected.to eq(expected) } + end + end + + describe '#product_analytics_collector_request?' do + subject { request.product_analytics_collector_request? } + + where(:path, :expected) do + '/' | false + '/groups' | false + '/-/collector' | false + '/-/collector/foo' | false + '/foo/-/collector/i' | false + + '/-/collector/i' | true + '/-/collector/ifoo' | true + '/-/collector/i/foo' | true + end + + with_them do + it { is_expected.to eq(expected) } + end + end + + describe '#should_be_skipped?' do + where( + api_internal_request: [true, false], + health_check_request: [true, false], + container_registry_event: [true, false] + ) + + with_them do + it 'returns true if any condition is true' do + allow(request).to receive(:api_internal_request?).and_return(api_internal_request) + allow(request).to receive(:health_check_request?).and_return(health_check_request) + allow(request).to receive(:container_registry_event?).and_return(container_registry_event) + + expect(request.should_be_skipped?).to be(api_internal_request || health_check_request || container_registry_event) + end + end + end + + describe '#web_request?' do + subject { request.web_request? } + + where(:path, :expected) do + '/' | true + '/groups' | true + '/foo/api' | true + + '/api' | false + '/api/v4/groups/1' | false + end + + with_them do + it { is_expected.to eq(expected) } + end + end + + describe '#protected_path?' do + subject { request.protected_path? } + + before do + stub_application_setting(protected_paths: [ + '/protected', + '/secure' + ]) + end + + where(:path, :expected) do + '/' | false + '/groups' | false + '/foo/protected' | false + '/foo/secure' | false + + '/protected' | true + '/secure' | true + '/secure/' | true + '/secure/foo' | true + end + + with_them do + it { is_expected.to eq(expected) } + end + end + describe '#deprecated_api_request?' do - let(:env) { { 'REQUEST_METHOD' => 'GET', 'rack.input' => StringIO.new, 'PATH_INFO' => path, 'QUERY_STRING' => query } } - let(:request) { ::Rack::Attack::Request.new(env) } + subject { request.send(:deprecated_api_request?) } - subject { !!request.__send__(:deprecated_api_request?) } + let(:env) { { 'QUERY_STRING' => query } } where(:path, :query, :expected) do '/' | '' | false diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb index 725a571e385..3dc38b5bc1c 100644 --- a/spec/lib/gitlab/usage_data_spec.rb +++ b/spec/lib/gitlab/usage_data_spec.rb @@ -12,8 +12,8 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do stub_database_flavor_check('Cloud SQL for PostgreSQL') end - describe '.uncached_data' do - subject { described_class.uncached_data } + describe '.data' do + subject { described_class.data(force_refresh: true) } it 'includes basic top and second level keys' do is_expected.to include(:counts) diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 70dd1cbe240..9da22e15a12 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -3524,7 +3524,7 @@ RSpec.describe MergeRequest, factory_default: :keep do let!(:job) { create(:ci_build, :with_deployment, :start_review_app, pipeline: pipeline, project: project) } it 'returns environments' do - is_expected.to eq(pipeline.environments) + is_expected.to eq(pipeline.environments_in_self_and_descendants.to_a) expect(subject.count).to be(1) end diff --git a/spec/requests/rack_attack_global_spec.rb b/spec/requests/rack_attack_global_spec.rb index 793438808a5..25eae57a134 100644 --- a/spec/requests/rack_attack_global_spec.rb +++ b/spec/requests/rack_attack_global_spec.rb @@ -184,6 +184,7 @@ RSpec.describe 'Rack Attack global throttles', :use_clean_rails_memory_store_cac context 'unauthenticated requests' do let(:protected_path_that_does_not_require_authentication) do + # This is one of the default values for `application_settings.protected_paths` '/users/sign_in' end @@ -227,6 +228,20 @@ RSpec.describe 'Rack Attack global throttles', :use_clean_rails_memory_store_cac expect_rejection { post protected_path_that_does_not_require_authentication, params: post_params } end + it 'allows non-POST requests to protected paths over the rate limit' do + (1 + requests_per_period).times do + get protected_path_that_does_not_require_authentication + expect(response).to have_gitlab_http_status(:ok) + end + end + + it 'allows POST requests to unprotected paths over the rate limit' do + (1 + requests_per_period).times do + post '/api/graphql' + expect(response).to have_gitlab_http_status(:ok) + end + end + it_behaves_like 'tracking when dry-run mode is set' do let(:throttle_name) { 'throttle_unauthenticated_protected_paths' } end diff --git a/spec/rubocop/cop/sidekiq_load_balancing/worker_data_consistency_with_deduplication_spec.rb b/spec/rubocop/cop/sidekiq_load_balancing/worker_data_consistency_with_deduplication_spec.rb deleted file mode 100644 index 6e7212b1002..00000000000 --- a/spec/rubocop/cop/sidekiq_load_balancing/worker_data_consistency_with_deduplication_spec.rb +++ /dev/null @@ -1,166 +0,0 @@ -# frozen_string_literal: true - -require 'fast_spec_helper' -require 'rspec-parameterized' -require_relative '../../../../rubocop/cop/sidekiq_load_balancing/worker_data_consistency_with_deduplication' - -RSpec.describe RuboCop::Cop::SidekiqLoadBalancing::WorkerDataConsistencyWithDeduplication do - using RSpec::Parameterized::TableSyntax - - subject(:cop) { described_class.new } - - before do - allow(cop) - .to receive(:in_worker?) - .and_return(true) - end - - where(:data_consistency) { %i[delayed sticky] } - - with_them do - let(:strategy) { described_class::DEFAULT_STRATEGY } - let(:corrected) do - <<~CORRECTED - class SomeWorker - include ApplicationWorker - - data_consistency :#{data_consistency} - - deduplicate #{strategy}, including_scheduled: true - idempotent! - end - CORRECTED - end - - context 'when deduplication strategy is not explicitly set' do - it 'registers an offense and corrects using default strategy' do - expect_offense(<<~CODE) - class SomeWorker - include ApplicationWorker - - data_consistency :#{data_consistency} - - idempotent! - ^^^^^^^^^^^ Workers that declare either `:sticky` or `:delayed` data consistency [...] - end - CODE - - expect_correction(corrected) - end - - context 'when identation is different' do - let(:corrected) do - <<~CORRECTED - class SomeWorker - include ApplicationWorker - - data_consistency :#{data_consistency} - - deduplicate #{strategy}, including_scheduled: true - idempotent! - end - CORRECTED - end - - it 'registers an offense and corrects with correct identation' do - expect_offense(<<~CODE) - class SomeWorker - include ApplicationWorker - - data_consistency :#{data_consistency} - - idempotent! - ^^^^^^^^^^^ Workers that declare either `:sticky` or `:delayed` data consistency [...] - end - CODE - - expect_correction(corrected) - end - end - end - - context 'when deduplication strategy does not include including_scheduling option' do - let(:strategy) { ':until_executed' } - - it 'registers an offense and corrects' do - expect_offense(<<~CODE) - class SomeWorker - include ApplicationWorker - - data_consistency :#{data_consistency} - - deduplicate :until_executed - ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Workers that declare either `:sticky` or `:delayed` data consistency [...] - idempotent! - end - CODE - - expect_correction(corrected) - end - end - - context 'when deduplication strategy has including_scheduling option disabled' do - let(:strategy) { ':until_executed' } - - it 'registers an offense and corrects' do - expect_offense(<<~CODE) - class SomeWorker - include ApplicationWorker - - data_consistency :#{data_consistency} - - deduplicate :until_executed, including_scheduled: false - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Workers that declare either `:sticky` or `:delayed` data consistency [...] - idempotent! - end - CODE - - expect_correction(corrected) - end - end - - context "when deduplication strategy is :none" do - it 'does not register an offense' do - expect_no_offenses(<<~CODE) - class SomeWorker - include ApplicationWorker - - data_consistency :always - - deduplicate :none - idempotent! - end - CODE - end - end - - context "when deduplication strategy has including_scheduling option enabled" do - it 'does not register an offense' do - expect_no_offenses(<<~CODE) - class SomeWorker - include ApplicationWorker - - data_consistency :always - - deduplicate :until_executing, including_scheduled: true - idempotent! - end - CODE - end - end - end - - context "data_consistency: :always" do - it 'does not register an offense' do - expect_no_offenses(<<~CODE) - class SomeWorker - include ApplicationWorker - - data_consistency :always - - idempotent! - end - CODE - end - end -end diff --git a/spec/serializers/test_case_entity_spec.rb b/spec/serializers/test_case_entity_spec.rb index cdeefd2fec5..03fb88bc6c3 100644 --- a/spec/serializers/test_case_entity_spec.rb +++ b/spec/serializers/test_case_entity_spec.rb @@ -41,6 +41,18 @@ RSpec.describe TestCaseEntity do end end + context 'when no test name is entered' do + let(:test_case) { build(:report_test_case, name: "") } + + it 'contains correct test case details' do + expect(subject[:status]).to eq('success') + expect(subject[:name]).to eq('(No name)') + expect(subject[:classname]).to eq('trace') + expect(subject[:file]).to eq('spec/trace_spec.rb') + expect(subject[:execution_time]).to eq(1.23) + end + end + context 'when attachment is present' do let(:test_case) { build(:report_test_case, :failed_with_attachment, job: job) } diff --git a/spec/services/environments/stop_service_spec.rb b/spec/services/environments/stop_service_spec.rb index acc9869002f..362071c1c26 100644 --- a/spec/services/environments/stop_service_spec.rb +++ b/spec/services/environments/stop_service_spec.rb @@ -185,7 +185,7 @@ RSpec.describe Environments::StopService do end it 'has active environment at first' do - expect(pipeline.environments.first).to be_available + expect(pipeline.environments_in_self_and_descendants.first).to be_available end context 'when user is a developer' do @@ -196,7 +196,7 @@ RSpec.describe Environments::StopService do it 'stops the active environment' do subject - expect(pipeline.environments.first).to be_stopped + expect(pipeline.environments_in_self_and_descendants.first).to be_stopped end end @@ -208,7 +208,7 @@ RSpec.describe Environments::StopService do it 'does not stop the active environment' do subject - expect(pipeline.environments.first).to be_available + expect(pipeline.environments_in_self_and_descendants.first).to be_available end end @@ -232,7 +232,7 @@ RSpec.describe Environments::StopService do it 'does not stop the active environment' do subject - expect(pipeline.environments.first).to be_available + expect(pipeline.environments_in_self_and_descendants.first).to be_available end end end |