diff options
42 files changed, 517 insertions, 243 deletions
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 5b4911bd23f..078498e0d3f 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -7bb80f713fccac70cd4f17211f1f5435b5014c7a +a7b614916005688cb1c290f323adaf9dd37d0b27 diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/app.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/app.vue index d49c1be5202..7dd090a7b47 100644 --- a/app/assets/javascripts/packages_and_registries/package_registry/components/details/app.vue +++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/app.vue @@ -74,7 +74,6 @@ export default { 'canDelete', 'svgPath', 'npmPath', - 'npmHelpPath', 'projectListUrl', 'groupListUrl', ], diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/composer_installation.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/composer_installation.vue index cc629ae394c..4ef37a3cbbe 100644 --- a/app/assets/javascripts/packages_and_registries/package_registry/components/details/composer_installation.vue +++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/composer_installation.vue @@ -6,6 +6,7 @@ import { TRACKING_ACTION_COPY_COMPOSER_REGISTRY_INCLUDE_COMMAND, TRACKING_ACTION_COPY_COMPOSER_PACKAGE_INCLUDE_COMMAND, TRACKING_LABEL_CODE_INSTRUCTION, + COMPOSER_HELP_PATH, } from '~/packages_and_registries/package_registry/constants'; import CodeInstruction from '~/vue_shared/components/registry/code_instruction.vue'; @@ -17,7 +18,7 @@ export default { GlLink, GlSprintf, }, - inject: ['composerHelpPath', 'composerConfigRepositoryName', 'composerPath', 'groupListUrl'], + inject: ['composerConfigRepositoryName', 'composerPath', 'groupListUrl'], props: { packageEntity: { type: Object, @@ -51,6 +52,9 @@ export default { TRACKING_ACTION_COPY_COMPOSER_PACKAGE_INCLUDE_COMMAND, TRACKING_LABEL_CODE_INSTRUCTION, }, + links: { + COMPOSER_HELP_PATH, + }, installOptions: [{ value: 'composer', label: s__('PackageRegistry|Show Composer commands') }], }; </script> @@ -79,7 +83,7 @@ export default { <span data-testid="help-text"> <gl-sprintf :message="$options.i18n.infoLine"> <template #link="{ content }"> - <gl-link :href="composerHelpPath" target="_blank">{{ content }}</gl-link> + <gl-link :href="$options.links.COMPOSER_HELP_PATH" target="_blank">{{ content }}</gl-link> </template> </gl-sprintf> </span> diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/conan_installation.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/conan_installation.vue index 99e27c9d44a..72e6a26e15f 100644 --- a/app/assets/javascripts/packages_and_registries/package_registry/components/details/conan_installation.vue +++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/conan_installation.vue @@ -6,6 +6,7 @@ import { TRACKING_ACTION_COPY_CONAN_COMMAND, TRACKING_ACTION_COPY_CONAN_SETUP_COMMAND, TRACKING_LABEL_CODE_INSTRUCTION, + CONAN_HELP_PATH, } from '~/packages_and_registries/package_registry/constants'; import CodeInstruction from '~/vue_shared/components/registry/code_instruction.vue'; @@ -17,7 +18,7 @@ export default { GlLink, GlSprintf, }, - inject: ['conanHelpPath', 'conanPath'], + inject: ['conanPath'], props: { packageEntity: { type: Object, @@ -44,7 +45,7 @@ export default { TRACKING_ACTION_COPY_CONAN_SETUP_COMMAND, TRACKING_LABEL_CODE_INSTRUCTION, }, - + links: { CONAN_HELP_PATH }, installOptions: [{ value: 'conan', label: s__('PackageRegistry|Show Conan commands') }], }; </script> @@ -72,7 +73,7 @@ export default { /> <gl-sprintf :message="$options.i18n.helpText"> <template #link="{ content }"> - <gl-link :href="conanHelpPath" target="_blank">{{ content }}</gl-link> + <gl-link :href="$options.links.CONAN_HELP_PATH" target="_blank">{{ content }}</gl-link> </template> </gl-sprintf> </div> diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/maven_installation.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/maven_installation.vue index 2070f0bbca0..9757838ccfb 100644 --- a/app/assets/javascripts/packages_and_registries/package_registry/components/details/maven_installation.vue +++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/maven_installation.vue @@ -12,6 +12,7 @@ import { TRACKING_ACTION_COPY_KOTLIN_ADD_TO_SOURCE_COMMAND, TRACKING_LABEL_CODE_INSTRUCTION, TRACKING_LABEL_MAVEN_INSTALLATION, + MAVEN_HELP_PATH, } from '~/packages_and_registries/package_registry/constants'; import CodeInstruction from '~/vue_shared/components/registry/code_instruction.vue'; @@ -23,7 +24,7 @@ export default { GlLink, GlSprintf, }, - inject: ['mavenHelpPath', 'mavenPath'], + inject: ['mavenPath'], props: { packageEntity: { type: Object, @@ -126,7 +127,7 @@ export default { TRACKING_LABEL_CODE_INSTRUCTION, TRACKING_LABEL_MAVEN_INSTALLATION, }, - + links: { MAVEN_HELP_PATH }, installOptions: [ { value: 'maven', label: s__('PackageRegistry|Maven XML') }, { value: 'groovy', label: s__('PackageRegistry|Gradle Groovy DSL') }, @@ -185,7 +186,7 @@ export default { /> <gl-sprintf :message="$options.i18n.helpText"> <template #link="{ content }"> - <gl-link :href="mavenHelpPath" target="_blank">{{ content }}</gl-link> + <gl-link :href="$options.links.MAVEN_HELP_PATH" target="_blank">{{ content }}</gl-link> </template> </gl-sprintf> </template> diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/npm_installation.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/npm_installation.vue index 2448324549e..7af9a2ade56 100644 --- a/app/assets/javascripts/packages_and_registries/package_registry/components/details/npm_installation.vue +++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/npm_installation.vue @@ -13,6 +13,7 @@ import { YARN_PACKAGE_MANAGER, PROJECT_PACKAGE_ENDPOINT_TYPE, INSTANCE_PACKAGE_ENDPOINT_TYPE, + NPM_HELP_PATH, } from '~/packages_and_registries/package_registry/constants'; import CodeInstruction from '~/vue_shared/components/registry/code_instruction.vue'; @@ -25,7 +26,7 @@ export default { GlSprintf, GlFormRadioGroup, }, - inject: ['npmHelpPath', 'npmPath', 'npmProjectPath'], + inject: ['npmPath', 'npmProjectPath'], props: { packageEntity: { type: Object, @@ -89,6 +90,7 @@ export default { 'PackageRegistry|You may also need to setup authentication using an auth token. %{linkStart}See the documentation%{linkEnd} to find out more.', ), }, + links: { NPM_HELP_PATH }, installOptions: [ { value: NPM_PACKAGE_MANAGER, label: s__('PackageRegistry|Show NPM commands') }, { value: YARN_PACKAGE_MANAGER, label: s__('PackageRegistry|Show Yarn commands') }, @@ -150,7 +152,7 @@ export default { <gl-sprintf :message="$options.i18n.helpText"> <template #link="{ content }"> - <gl-link :href="npmHelpPath" target="_blank">{{ content }}</gl-link> + <gl-link :href="$options.links.NPM_HELP_PATH" target="_blank">{{ content }}</gl-link> </template> </gl-sprintf> </div> diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/nuget_installation.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/nuget_installation.vue index 2e9991b7be5..ca48da70bb4 100644 --- a/app/assets/javascripts/packages_and_registries/package_registry/components/details/nuget_installation.vue +++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/nuget_installation.vue @@ -6,6 +6,7 @@ import { TRACKING_ACTION_COPY_NUGET_INSTALL_COMMAND, TRACKING_ACTION_COPY_NUGET_SETUP_COMMAND, TRACKING_LABEL_CODE_INSTRUCTION, + NUGET_HELP_PATH, } from '~/packages_and_registries/package_registry/constants'; import CodeInstruction from '~/vue_shared/components/registry/code_instruction.vue'; @@ -17,7 +18,7 @@ export default { GlLink, GlSprintf, }, - inject: ['nugetHelpPath', 'nugetPath'], + inject: ['nugetPath'], props: { packageEntity: { type: Object, @@ -42,6 +43,7 @@ export default { 'PackageRegistry|For more information on the NuGet registry, %{linkStart}see the documentation%{linkEnd}.', ), }, + links: { NUGET_HELP_PATH }, installOptions: [{ value: 'nuget', label: s__('PackageRegistry|Show Nuget commands') }], }; </script> @@ -68,7 +70,7 @@ export default { /> <gl-sprintf :message="$options.i18n.helpText"> <template #link="{ content }"> - <gl-link :href="nugetHelpPath" target="_blank">{{ content }}</gl-link> + <gl-link :href="$options.links.NUGET_HELP_PATH" target="_blank">{{ content }}</gl-link> </template> </gl-sprintf> </div> diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/pypi_installation.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/pypi_installation.vue index 669adab9df6..1c4ef21a2cf 100644 --- a/app/assets/javascripts/packages_and_registries/package_registry/components/details/pypi_installation.vue +++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/pypi_installation.vue @@ -7,6 +7,7 @@ import { TRACKING_ACTION_COPY_PIP_INSTALL_COMMAND, TRACKING_ACTION_COPY_PYPI_SETUP_COMMAND, TRACKING_LABEL_CODE_INSTRUCTION, + PYPI_HELP_PATH, } from '~/packages_and_registries/package_registry/constants'; import CodeInstruction from '~/vue_shared/components/registry/code_instruction.vue'; @@ -18,7 +19,7 @@ export default { GlLink, GlSprintf, }, - inject: ['pypiHelpPath', 'pypiPath', 'pypiSetupPath'], + inject: ['pypiPath', 'pypiSetupPath'], props: { packageEntity: { type: Object, @@ -50,6 +51,7 @@ password = <your personal access token>`; 'PackageRegistry|For more information on the PyPi registry, %{linkStart}see the documentation%{linkEnd}.', ), }, + links: { PYPI_HELP_PATH }, installOptions: [{ value: 'pypi', label: s__('PackageRegistry|Show PyPi commands') }], }; </script> @@ -86,7 +88,7 @@ password = <your personal access token>`; /> <gl-sprintf :message="$options.i18n.helpText"> <template #link="{ content }"> - <gl-link :href="pypiHelpPath" target="_blank">{{ content }}</gl-link> + <gl-link :href="$options.links.PYPI_HELP_PATH" target="_blank">{{ content }}</gl-link> </template> </gl-sprintf> </div> diff --git a/app/assets/javascripts/packages_and_registries/package_registry/constants.js b/app/assets/javascripts/packages_and_registries/package_registry/constants.js index ab6541e4264..af4e586231c 100644 --- a/app/assets/javascripts/packages_and_registries/package_registry/constants.js +++ b/app/assets/javascripts/packages_and_registries/package_registry/constants.js @@ -142,3 +142,9 @@ export const PACKAGE_TYPES = [ export const EMPTY_LIST_HELP_URL = helpPagePath('user/packages/package_registry/index'); export const PACKAGE_HELP_URL = helpPagePath('user/packages/index'); +export const NPM_HELP_PATH = helpPagePath('user/packages/npm_registry/index'); +export const MAVEN_HELP_PATH = helpPagePath('user/packages/maven_repository/index'); +export const CONAN_HELP_PATH = helpPagePath('user/packages/conan_repository/index'); +export const NUGET_HELP_PATH = helpPagePath('user/packages/nuget_repository/index'); +export const PYPI_HELP_PATH = helpPagePath('user/packages/pypi_repository/index'); +export const COMPOSER_HELP_PATH = helpPagePath('user/packages/composer_repository/index'); diff --git a/app/assets/javascripts/pipelines/components/jobs/jobs_app.vue b/app/assets/javascripts/pipelines/components/jobs/jobs_app.vue index ffac8206b58..e11073aee33 100644 --- a/app/assets/javascripts/pipelines/components/jobs/jobs_app.vue +++ b/app/assets/javascripts/pipelines/components/jobs/jobs_app.vue @@ -112,7 +112,7 @@ export default { </gl-skeleton-loader> </div> - <jobs-table v-else :jobs="jobs" :table-fields="$options.fields" /> + <jobs-table v-else :jobs="jobs" :table-fields="$options.fields" data-testid="jobs-tab-table" /> <gl-intersection-observer v-if="jobsPageInfo.hasNextPage" @appear="fetchMoreJobs"> <gl-loading-icon v-if="$apollo.loading" size="md" /> diff --git a/app/helpers/packages_helper.rb b/app/helpers/packages_helper.rb index 66f80e7eeb8..9e2b27de5b3 100644 --- a/app/helpers/packages_helper.rb +++ b/app/helpers/packages_helper.rb @@ -61,18 +61,12 @@ module PackagesHelper svg_path: image_path('illustrations/no-packages.svg'), npm_path: package_registry_instance_url(:npm), npm_project_path: package_registry_project_url(project.id, :npm), - npm_help_path: help_page_path('user/packages/npm_registry/index'), maven_path: package_registry_project_url(project.id, :maven), - maven_help_path: help_page_path('user/packages/maven_repository/index'), conan_path: package_registry_project_url(project.id, :conan), - conan_help_path: help_page_path('user/packages/conan_repository/index'), nuget_path: nuget_package_registry_url(project.id), - nuget_help_path: help_page_path('user/packages/nuget_repository/index'), pypi_path: pypi_registry_url(project.id), pypi_setup_path: package_registry_project_url(project.id, :pypi), - pypi_help_path: help_page_path('user/packages/pypi_repository/index'), composer_path: composer_registry_url(project&.group&.id), - composer_help_path: help_page_path('user/packages/composer_repository/index'), project_name: project.name, project_list_url: project_packages_path(project), group_list_url: project.group ? group_packages_path(project.group) : '', diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index d5345bd5e02..ef5e72342fb 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -9,6 +9,7 @@ class ApplicationSetting < ApplicationRecord include Sanitizable ignore_columns %i[elasticsearch_shards elasticsearch_replicas], remove_with: '14.4', remove_after: '2021-09-22' + ignore_columns %i[static_objects_external_storage_auth_token], remove_with: '14.9', remove_after: '2022-03-22' INSTANCE_REVIEW_MIN_USERS = 50 GRAFANA_URL_ERROR_MESSAGE = 'Please check your Grafana URL setting in ' \ @@ -21,7 +22,7 @@ class ApplicationSetting < ApplicationRecord add_authentication_token_field :runners_registration_token, encrypted: -> { Feature.enabled?(:application_settings_tokens_optional_encryption) ? :optional : :required } add_authentication_token_field :health_check_access_token - add_authentication_token_field :static_objects_external_storage_auth_token, encrypted: :optional + add_authentication_token_field :static_objects_external_storage_auth_token, encrypted: :required belongs_to :self_monitoring_project, class_name: "Project", foreign_key: 'instance_administration_project_id' belongs_to :push_rule diff --git a/app/models/members/project_namespace_member.rb b/app/models/members/project_namespace_member.rb new file mode 100644 index 00000000000..0e0c52ee3ca --- /dev/null +++ b/app/models/members/project_namespace_member.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +# TODO: https://gitlab.com/groups/gitlab-org/-/epics/7054 +# This file is a part of the Consolidate Group and Project member management epic, +# and will be developed further as we progress through that epic. +class ProjectNamespaceMember < ProjectMember # rubocop:disable Gitlab/NamespacedClass +end diff --git a/app/views/admin/application_settings/appearances/_form.html.haml b/app/views/admin/application_settings/appearances/_form.html.haml index 3bd16e4c344..0f7f0109a54 100644 --- a/app/views/admin/application_settings/appearances/_form.html.haml +++ b/app/views/admin/application_settings/appearances/_form.html.haml @@ -20,7 +20,7 @@ %hr = f.hidden_field :header_logo_cache = f.file_field :header_logo, class: "", accept: 'image/*' - .hint + .form-text.text-muted = _('Maximum file size is 1MB. Pages are optimized for a 28px tall header logo') %hr .row @@ -39,7 +39,7 @@ %hr = f.hidden_field :favicon_cache = f.file_field :favicon, class: '', accept: 'image/*' - .hint + .form-text.text-muted = _("Maximum file size is 1 MB. Image size must be 32 x 32 pixels. Allowed image formats are %{favicon_extension_whitelist}.") % { favicon_extension_whitelist: favicon_extension_whitelist } %br = _("Images with incorrect dimensions are not resized automatically, and may result in unexpected behavior.") @@ -58,7 +58,7 @@ .form-group = f.label :description, class: 'col-form-label label-bold' = f.text_area :description, class: "form-control gl-form-input", rows: 10 - .hint + .form-text.text-muted = parsed_with_gfm .form-group = f.label :logo, class: 'col-form-label label-bold pt-0' @@ -71,7 +71,7 @@ %hr = f.hidden_field :logo_cache = f.file_field :logo, class: "", accept: 'image/*' - .hint + .form-text.text-muted = _('Maximum file size is 1MB. Pages are optimized for a 640x360 px logo.') %hr @@ -84,7 +84,7 @@ = f.label :new_project_guidelines, class: 'col-form-label label-bold' %p = f.text_area :new_project_guidelines, class: "form-control gl-form-input", rows: 10 - .hint + .form-text.text-muted = parsed_with_gfm %hr @@ -97,7 +97,7 @@ = f.label :profile_image_guidelines, class: 'col-form-label label-bold' %p = f.text_area :profile_image_guidelines, class: "form-control gl-form-input", rows: 10 - .hint + .form-text.text-muted = parsed_with_gfm .gl-mt-3.gl-mb-3 diff --git a/app/views/admin/application_settings/appearances/_system_header_footer_form.html.haml b/app/views/admin/application_settings/appearances/_system_header_footer_form.html.haml index 4571d34a497..1ce79e61ac6 100644 --- a/app/views/admin/application_settings/appearances/_system_header_footer_form.html.haml +++ b/app/views/admin/application_settings/appearances/_system_header_footer_form.html.haml @@ -19,7 +19,7 @@ = form.label :email_header_and_footer_enabled, class: 'label-bold' do = _('Enable header and footer in emails') - .hint + .form-text.text-muted = _('Add header and footer to emails. Please note that color settings will only be applied within the application interface') .form-group.js-toggle-colors-container diff --git a/app/views/projects/pages/_list.html.haml b/app/views/projects/pages/_list.html.haml index 40352e79175..4e9c77564da 100644 --- a/app/views/projects/pages/_list.html.haml +++ b/app/views/projects/pages/_list.html.haml @@ -15,11 +15,9 @@ = external_link(domain.url, domain.url) - if domain.certificate %div - %span.badge.badge-gray - = s_('GitLabPages|Certificate: %{subject}') % { subject: domain.pages_domain.subject } + = gl_badge_tag(s_('GitLabPages|Certificate: %{subject}') % { subject: domain.pages_domain.subject }) - if domain.expired? - %span.badge.badge-danger - = s_('GitLabPages|Expired') + = gl_badge_tag s_('GitLabPages|Expired'), variant: :danger %div = link_to s_('GitLabPages|Edit'), project_pages_domain_path(@project, domain), class: "btn gl-button btn-sm btn-grouped btn-confirm btn-inverted" = link_to s_('GitLabPages|Remove'), project_pages_domain_path(@project, domain), data: { confirm: s_('GitLabPages|Are you sure?')}, method: :delete, class: "btn gl-button btn-danger btn-sm btn-grouped" diff --git a/doc/topics/autodevops/customize.md b/doc/topics/autodevops/customize.md index 925f657c099..88f25e4a453 100644 --- a/doc/topics/autodevops/customize.md +++ b/doc/topics/autodevops/customize.md @@ -131,7 +131,7 @@ You can extend and manage your Auto DevOps configuration with GitLab APIs: ## Forward CI/CD variables to the build environment -> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/25514) in GitLab 12.3, but available in versions 11.9 and above. +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/25514) in GitLab 12.3, but available in GitLab 12.0 and later. CI/CD variables can be forwarded into the build environment using the `AUTO_DEVOPS_BUILD_IMAGE_FORWARDED_CI_VARIABLES` CI/CD variable. @@ -408,14 +408,14 @@ applications. | `AUTO_DEVOPS_BUILD_IMAGE_FORWARDED_CI_VARIABLES` | A [comma-separated list of CI/CD variable names](#forward-cicd-variables-to-the-build-environment) to be forwarded to the build environment (the buildpack builder or `docker build`). | | `AUTO_DEVOPS_CHART` | Helm Chart used to deploy your apps. Defaults to the one [provided by GitLab](https://gitlab.com/gitlab-org/cluster-integration/auto-deploy-image/-/tree/master/assets/auto-deploy-app). | | `AUTO_DEVOPS_CHART_REPOSITORY` | Helm Chart repository used to search for charts. Defaults to `https://charts.gitlab.io`. | -| `AUTO_DEVOPS_CHART_REPOSITORY_NAME` | From GitLab 11.11, used to set the name of the Helm repository. Defaults to `gitlab`. | -| `AUTO_DEVOPS_CHART_REPOSITORY_USERNAME` | From GitLab 11.11, used to set a username to connect to the Helm repository. Defaults to no credentials. Also set `AUTO_DEVOPS_CHART_REPOSITORY_PASSWORD`. | -| `AUTO_DEVOPS_CHART_REPOSITORY_PASSWORD` | From GitLab 11.11, used to set a password to connect to the Helm repository. Defaults to no credentials. Also set `AUTO_DEVOPS_CHART_REPOSITORY_USERNAME`. | +| `AUTO_DEVOPS_CHART_REPOSITORY_NAME` | Used to set the name of the Helm repository. Defaults to `gitlab`. | +| `AUTO_DEVOPS_CHART_REPOSITORY_USERNAME` | Used to set a username to connect to the Helm repository. Defaults to no credentials. Also set `AUTO_DEVOPS_CHART_REPOSITORY_PASSWORD`. | +| `AUTO_DEVOPS_CHART_REPOSITORY_PASSWORD` | Used to set a password to connect to the Helm repository. Defaults to no credentials. Also set `AUTO_DEVOPS_CHART_REPOSITORY_USERNAME`. | | `AUTO_DEVOPS_CHART_REPOSITORY_PASS_CREDENTIALS` | From GitLab 14.2, set to a non-empty value to enable forwarding of the Helm repository credentials to the chart server when the chart artifacts are on a different host than repository. | | `AUTO_DEVOPS_DEPLOY_DEBUG` | From GitLab 13.1, if this variable is present, Helm outputs debug logs. | | `AUTO_DEVOPS_ALLOW_TO_FORCE_DEPLOY_V<N>` | From [auto-deploy-image](https://gitlab.com/gitlab-org/cluster-integration/auto-deploy-image) v1.0.0, if this variable is present, a new major version of chart is forcibly deployed. For more information, see [Ignore warnings and continue deploying](upgrading_auto_deploy_dependencies.md#ignore-warnings-and-continue-deploying). | | `BUILDPACK_URL` | Buildpack's full URL. [Must point to a URL supported by Pack or Herokuish](#custom-buildpacks). | -| `CANARY_ENABLED` | From GitLab 11.0, used to define a [deploy policy for canary environments](#deploy-policy-for-canary-environments). | +| `CANARY_ENABLED` | Used to define a [deploy policy for canary environments](#deploy-policy-for-canary-environments). | | `CANARY_PRODUCTION_REPLICAS` | Number of canary replicas to deploy for [Canary Deployments](../../user/project/canary_deployments.md) in the production environment. Takes precedence over `CANARY_REPLICAS`. Defaults to 1. | | `CANARY_REPLICAS` | Number of canary replicas to deploy for [Canary Deployments](../../user/project/canary_deployments.md). Defaults to 1. | | `CI_APPLICATION_REPOSITORY` | The repository of container image being built or deployed, `$CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG`. For more details, read [Custom container image](#custom-container-image). | @@ -424,18 +424,18 @@ applications. | `DOCKERFILE_PATH` | From GitLab 13.2, allows overriding the [default Dockerfile path for the build stage](#custom-dockerfile) | | `HELM_RELEASE_NAME` | From GitLab 12.1, allows the `helm` release name to be overridden. Can be used to assign unique release names when deploying multiple projects to a single namespace. | | `HELM_UPGRADE_VALUES_FILE` | From GitLab 12.6, allows the `helm upgrade` values file to be overridden. Defaults to `.gitlab/auto-deploy-values.yaml`. | -| `HELM_UPGRADE_EXTRA_ARGS` | From GitLab 11.11, allows extra options in `helm upgrade` commands when deploying the application. Note that using quotes doesn't prevent word splitting. | -| `INCREMENTAL_ROLLOUT_MODE` | From GitLab 11.4, if present, can be used to enable an [incremental rollout](#incremental-rollout-to-production) of your application for the production environment. Set to `manual` for manual deployment jobs or `timed` for automatic rollout deployments with a 5 minute delay each one. | -| `K8S_SECRET_*` | From GitLab 11.7, any variable prefixed with [`K8S_SECRET_`](#application-secret-variables) is made available by Auto DevOps as environment variables to the deployed application. | +| `HELM_UPGRADE_EXTRA_ARGS` | Allows extra options in `helm upgrade` commands when deploying the application. Note that using quotes doesn't prevent word splitting. | +| `INCREMENTAL_ROLLOUT_MODE` | If present, can be used to enable an [incremental rollout](#incremental-rollout-to-production) of your application for the production environment. Set to `manual` for manual deployment jobs or `timed` for automatic rollout deployments with a 5 minute delay each one. | +| `K8S_SECRET_*` | Any variable prefixed with [`K8S_SECRET_`](#application-secret-variables) is made available by Auto DevOps as environment variables to the deployed application. | | `KUBE_CONTEXT` | From GitLab 14.5, can be used to select which context to use from `KUBECONFIG`. When `KUBE_CONTEXT` is blank, the default context in `KUBECONFIG` (if any) will be used. A context must be selected when using the [CI/CD tunnel](../../user/clusters/agent/ci_cd_tunnel.md). | -| `KUBE_INGRESS_BASE_DOMAIN` | From GitLab 11.8, can be used to set a domain per cluster. See [cluster domains](../../user/project/clusters/gitlab_managed_clusters.md#base-domain) for more information. | +| `KUBE_INGRESS_BASE_DOMAIN` | Can be used to set a domain per cluster. See [cluster domains](../../user/project/clusters/gitlab_managed_clusters.md#base-domain) for more information. | | `KUBE_NAMESPACE` | The namespace used for deployments. When using certificate-based clusters, [this value should not be overwritten directly](../../user/project/clusters/deploy_to_cluster.md#custom-namespace). | | `KUBECONFIG` | The kubeconfig to use for deployments. User-provided values take priority over GitLab-provided values. | | `PRODUCTION_REPLICAS` | Number of replicas to deploy in the production environment. Takes precedence over `REPLICAS` and defaults to 1. For zero downtime upgrades, set to 2 or greater. | | `REPLICAS` | Number of replicas to deploy. Defaults to 1. | -| `ROLLOUT_RESOURCE_TYPE` | From GitLab 11.9, allows specification of the resource type being deployed when using a custom Helm chart. Default value is `deployment`. | +| `ROLLOUT_RESOURCE_TYPE` | Allows specification of the resource type being deployed when using a custom Helm chart. Default value is `deployment`. | | `ROLLOUT_STATUS_DISABLED` | From GitLab 12.0, used to disable rollout status check because it does not support all resource types, for example, `cronjob`. | -| `STAGING_ENABLED` | From GitLab 10.8, used to define a [deploy policy for staging and production environments](#deploy-policy-for-staging-and-production-environments). | +| `STAGING_ENABLED` | Used to define a [deploy policy for staging and production environments](#deploy-policy-for-staging-and-production-environments). | NOTE: After you set up your replica variables using a @@ -453,8 +453,8 @@ The following table lists CI/CD variables related to the database. | **CI/CD Variable** | **Description** | |-----------------------------------------|------------------------------------| -| `DB_INITIALIZE` | From GitLab 11.4, used to specify the command to run to initialize the application's PostgreSQL database. Runs inside the application pod. | -| `DB_MIGRATE` | From GitLab 11.4, used to specify the command to run to migrate the application's PostgreSQL database. Runs inside the application pod. | +| `DB_INITIALIZE` | Used to specify the command to run to initialize the application's PostgreSQL database. Runs inside the application pod. | +| `DB_MIGRATE` | Used to specify the command to run to migrate the application's PostgreSQL database. Runs inside the application pod. | | `POSTGRES_ENABLED` | Whether PostgreSQL is enabled. Defaults to `true`. Set to `false` to disable the automatic deployment of PostgreSQL. | | `POSTGRES_USER` | The PostgreSQL user. Defaults to `user`. Set it to use a custom username. | | `POSTGRES_PASSWORD` | The PostgreSQL password. Defaults to `testing-password`. Set it to use a custom password. | @@ -478,12 +478,11 @@ The following table lists variables used to disable jobs. | `bundler-audit-dependency_scanning` | `DEPENDENCY_SCANNING_DISABLED` | | If the variable is present, the job isn't created. | | `canary` | `CANARY_ENABLED` | | This manual job is created if the variable is present. | | `code_intelligence` | `CODE_INTELLIGENCE_DISABLED` | From GitLab 13.6 | If the variable is present, the job isn't created. | -| `codequality` | `CODE_QUALITY_DISABLED` | Until GitLab 11.0 | If the variable is present, the job isn't created. | -| `code_quality` | `CODE_QUALITY_DISABLED` | [From GitLab 11.0](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/5773) | If the variable is present, the job isn't created. | -| `container_scanning` | `CONTAINER_SCANNING_DISABLED` | From GitLab 11.0 | If the variable is present, the job isn't created. | -| `dast` | `DAST_DISABLED` | From GitLab 11.0 | If the variable is present, the job isn't created. | +| `code_quality` | `CODE_QUALITY_DISABLED` | | If the variable is present, the job isn't created. | +| `container_scanning` | `CONTAINER_SCANNING_DISABLED` | | If the variable is present, the job isn't created. | +| `dast` | `DAST_DISABLED` | | If the variable is present, the job isn't created. | | `dast_environment_deploy` | `DAST_DISABLED_FOR_DEFAULT_BRANCH` or `DAST_DISABLED` | [From GitLab 12.4](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/17789) | If either variable is present, the job isn't created. | -| `dependency_scanning` | `DEPENDENCY_SCANNING_DISABLED` | From GitLab 11.0 | If the variable is present, the job isn't created. | +| `dependency_scanning` | `DEPENDENCY_SCANNING_DISABLED` | | If the variable is present, the job isn't created. | | `eslint-sast` | `SAST_DISABLED` | | If the variable is present, the job isn't created. | | `flawfinder-sast` | `SAST_DISABLED` | | If the variable is present, the job isn't created. | | `gemnasium-dependency_scanning` | `DEPENDENCY_SCANNING_DISABLED` | | If the variable is present, the job isn't created. | @@ -491,34 +490,32 @@ The following table lists variables used to disable jobs. | `gemnasium-python-dependency_scanning` | `DEPENDENCY_SCANNING_DISABLED` | | If the variable is present, the job isn't created. | | `gosec-sast` | `SAST_DISABLED` | | If the variable is present, the job isn't created. | | `kubesec-sast` | `SAST_DISABLED` | | If the variable is present, the job isn't created. | -| `license_management` | `LICENSE_MANAGEMENT_DISABLED` | GitLab 11.0 to 12.7 | If the variable is present, the job isn't created. Job deprecated [from GitLab 12.8](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/22773) | +| `license_management` | `LICENSE_MANAGEMENT_DISABLED` | GitLab 12.7 and earlier | If the variable is present, the job isn't created. Job deprecated [from GitLab 12.8](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/22773) | | `license_scanning` | `LICENSE_MANAGEMENT_DISABLED` | [From GitLab 12.8](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/22773) | If the variable is present, the job isn't created. | | `load_performance` | `LOAD_PERFORMANCE_DISABLED` | From GitLab 13.2 | If the variable is present, the job isn't created. | | `nodejs-scan-sast` | `SAST_DISABLED` | | If the variable is present, the job isn't created. | -| `performance` | `PERFORMANCE_DISABLED` | GitLab 11.0 to GitLab 13.12 | Browser performance. If the variable is present, the job isn't created. Replaced by `browser_performance`. | +| `performance` | `PERFORMANCE_DISABLED` | GitLab 13.12 and earlier | Browser performance. If the variable is present, the job isn't created. Replaced by `browser_performance`. | | `browser_performance` | `BROWSER_PERFORMANCE_DISABLED` | From GitLab 14.0 | Browser performance. If the variable is present, the job isn't created. Replaces `performance`. | | `phpcs-security-audit-sast` | `SAST_DISABLED` | | If the variable is present, the job isn't created. | | `pmd-apex-sast` | `SAST_DISABLED` | | If the variable is present, the job isn't created. | | `retire-js-dependency_scanning` | `DEPENDENCY_SCANNING_DISABLED` | | If the variable is present, the job isn't created. | -| `review` | `REVIEW_DISABLED` | From GitLab 11.0 | If the variable is present, the job isn't created. | -| `review:stop` | `REVIEW_DISABLED` | From GitLab 11.0 | Manual job. If the variable is present, the job isn't created. | -| `sast` | `SAST_DISABLED` | From GitLab 11.0 | If the variable is present, the job isn't created. | -| `sast:container` | `CONTAINER_SCANNING_DISABLED` | From GitLab 11.0 | If the variable is present, the job isn't created. | +| `review` | `REVIEW_DISABLED` | | If the variable is present, the job isn't created. | +| `review:stop` | `REVIEW_DISABLED` | | Manual job. If the variable is present, the job isn't created. | +| `sast` | `SAST_DISABLED` | | If the variable is present, the job isn't created. | +| `sast:container` | `CONTAINER_SCANNING_DISABLED` | | If the variable is present, the job isn't created. | | `secret_detection` | `SECRET_DETECTION_DISABLED` | From GitLab 13.1 | If the variable is present, the job isn't created. | | `secret_detection_default_branch` | `SECRET_DETECTION_DISABLED` | [From GitLab 13.2](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/22773) | If the variable is present, the job isn't created. | | `security-code-scan-sast` | `SAST_DISABLED` | | If the variable is present, the job isn't created. | -| `secrets-sast` | `SAST_DISABLED` | From GitLab 11.0 | If the variable is present, the job isn't created. | +| `secrets-sast` | `SAST_DISABLED` | | If the variable is present, the job isn't created. | | `sobelaw-sast` | `SAST_DISABLED` | | If the variable is present, the job isn't created. | | `stop_dast_environment` | `DAST_DISABLED_FOR_DEFAULT_BRANCH` or `DAST_DISABLED` | [From GitLab 12.4](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/17789) | If either variable is present, the job isn't created. | | `spotbugs-sast` | `SAST_DISABLED` | | If the variable is present, the job isn't created. | -| `test` | `TEST_DISABLED` | From GitLab 11.0 | If the variable is present, the job isn't created. | +| `test` | `TEST_DISABLED` | | If the variable is present, the job isn't created. | | `staging` | `STAGING_ENABLED` | | The job is created if the variable is present. | | `stop_review` | `REVIEW_DISABLED` | | If the variable is present, the job isn't created. | ### Application secret variables -> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/49056) in GitLab 11.7. - Some applications need to define secret variables that are accessible by the deployed application. Auto DevOps detects CI/CD variables starting with `K8S_SECRET_`, and makes these prefixed variables available to the deployed application as environment variables. @@ -623,8 +620,6 @@ service: ### Deploy policy for staging and production environments -> [Introduced](https://gitlab.com/gitlab-org/gitlab-ci-yml/-/merge_requests/160) in GitLab 10.8. - NOTE: You can also set this inside your [project's settings](requirements.md#auto-devops-deployment-strategy). @@ -640,8 +635,6 @@ you when you're ready to manually deploy to production. ### Deploy policy for canary environments **(PREMIUM)** -> [Introduced](https://gitlab.com/gitlab-org/gitlab-ci-yml/-/merge_requests/171) in GitLab 11.0. - You can use a [canary environment](../../user/project/canary_deployments.md) before deploying any changes to production. @@ -652,8 +645,6 @@ If you define `CANARY_ENABLED` with a non-empty value, then two manual jobs are ### Incremental rollout to production **(PREMIUM)** -> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/5415) in GitLab 10.8. - NOTE: You can also set this inside your [project's settings](requirements.md#auto-devops-deployment-strategy). @@ -703,14 +694,10 @@ With `INCREMENTAL_ROLLOUT_MODE` set to `manual` and with `STAGING_ENABLED` ![Rollout and staging enabled](img/rollout_staging_enabled.png) WARNING: -Before GitLab 11.4, the presence of the `INCREMENTAL_ROLLOUT_ENABLED` CI/CD variable -enabled this feature. This configuration is deprecated, and is scheduled to be -removed in the future. +This configuration is deprecated, and is scheduled to be removed in the future. ### Timed incremental rollout to production **(PREMIUM)** -> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/7545) in GitLab 11.4. - NOTE: You can also set this inside your [project's settings](requirements.md#auto-devops-deployment-strategy). diff --git a/doc/user/admin_area/settings/account_and_limit_settings.md b/doc/user/admin_area/settings/account_and_limit_settings.md index e07a961375b..f748f575419 100644 --- a/doc/user/admin_area/settings/account_and_limit_settings.md +++ b/doc/user/admin_area/settings/account_and_limit_settings.md @@ -201,11 +201,7 @@ To set a limit on how long these sessions are valid: > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/1007) in GitLab 14.6 [with a flag](../../../administration/feature_flags.md) named `ff_limit_ssh_key_lifetime`. Disabled by default. > - [Enabled on self-managed](https://gitlab.com/gitlab-org/gitlab/-/issues/346753) in GitLab 14.6. - -FLAG: -On self-managed GitLab, by default this feature is available. To hide the feature, -ask an administrator to [disable the feature flag](../../../administration/feature_flags.md) named `ff_limit_ssh_key_lifetime`. -On GitLab.com, this feature is not available. +> - [Generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/1007) in GitLab 14.7. [Feature flag ff_limit_ssh_key_lifetime](https://gitlab.com/gitlab-org/gitlab/-/issues/347408) removed. Users can optionally specify a lifetime for [SSH keys](../../../ssh/index.md). diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb index 8e139ae0709..b07b9c79858 100644 --- a/lib/gitlab/regex.rb +++ b/lib/gitlab/regex.rb @@ -79,7 +79,11 @@ module Gitlab def nuget_version_regex @nuget_version_regex ||= / - \A#{_semver_major_minor_patch_regex}(\.\d*)?#{_semver_prerelease_build_regex}\z + \A#{_semver_major_regex} + \.#{_semver_minor_regex} + (\.#{_semver_patch_regex})? + (\.\d*)? + #{_semver_prerelease_build_regex}\z /x.freeze end @@ -167,9 +171,25 @@ module Gitlab # regexes rather than being used alone. def _semver_major_minor_patch_regex @_semver_major_minor_patch_regex ||= / + #{_semver_major_regex}\.#{_semver_minor_regex}\.#{_semver_patch_regex} + /x.freeze + end + + def _semver_major_regex + @_semver_major_regex ||= / (?<major>0|[1-9]\d*) - \.(?<minor>0|[1-9]\d*) - \.(?<patch>0|[1-9]\d*) + /x.freeze + end + + def _semver_minor_regex + @_semver_minor_regex ||= / + (?<minor>0|[1-9]\d*) + /x.freeze + end + + def _semver_patch_regex + @_semver_patch_regex ||= / + (?<patch>0|[1-9]\d*) /x.freeze end diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb index 4378e88f7c1..e600a99e3b6 100644 --- a/spec/features/commits_spec.rb +++ b/spec/features/commits_spec.rb @@ -30,10 +30,10 @@ RSpec.describe 'Commits' do project.add_reporter(user) end - describe 'Commit builds with jobs_tab_feature flag off' do + describe 'Commit builds with jobs_tab_vue feature flag off' do before do stub_feature_flags(jobs_tab_vue: false) - visit pipeline_path(pipeline) + visit builds_project_pipeline_path(project, pipeline) end it { expect(page).to have_content pipeline.sha[0..7] } @@ -45,6 +45,23 @@ RSpec.describe 'Commits' do end end end + + describe 'Commit builds with jobs_tab_vue feature flag on', :js do + before do + visit builds_project_pipeline_path(project, pipeline) + + wait_for_requests + end + + it { expect(page).to have_content pipeline.sha[0..7] } + + it 'contains generic commit status build' do + page.within('[data-testid="jobs-tab-table"]') do + expect(page).to have_content "##{status.id}" # build id + expect(page).to have_content 'generic' # build name + end + end + end end context 'commit status is Ci Build' do @@ -103,6 +120,18 @@ RSpec.describe 'Commits' do end end + context 'Download artifacts with jobs_tab_vue feature flag on', :js do + before do + create(:ci_job_artifact, :archive, file: artifacts_file, job: build) + end + + it do + visit builds_project_pipeline_path(project, pipeline) + wait_for_requests + expect(page).to have_link('Download artifacts', href: download_project_job_artifacts_path(project, build, file_type: :archive)) + end + end + describe 'Cancel all builds' do it 'cancels commit', :js, :sidekiq_might_not_need_inline do visit pipeline_path(pipeline) @@ -141,6 +170,27 @@ RSpec.describe 'Commits' do end end + context "when logged as reporter and with jobs_tab_vue feature flag on", :js do + before do + project.add_reporter(user) + create(:ci_job_artifact, :archive, file: artifacts_file, job: build) + visit builds_project_pipeline_path(project, pipeline) + wait_for_requests + end + + it 'renders header' do + expect(page).to have_content pipeline.sha[0..7] + expect(page).to have_content pipeline.git_commit_message.gsub!(/\s+/, ' ') + expect(page).to have_content pipeline.user.name + expect(page).not_to have_link('Cancel running') + expect(page).not_to have_link('Retry') + end + + it do + expect(page).to have_link('Download artifacts') + end + end + context 'when accessing internal project with disallowed access', :js, quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/299575' do before do project.update!( diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb index 6ddc8e43762..5176a7ec5a1 100644 --- a/spec/features/projects/pipelines/pipeline_spec.rb +++ b/spec/features/projects/pipelines/pipeline_spec.rb @@ -1020,6 +1020,103 @@ RSpec.describe 'Pipeline', :js do end end + describe 'GET /:project/-/pipelines/:id/builds with jobs_tab_vue feature flag turned on' do + include_context 'pipeline builds' + + let_it_be(:project) { create(:project, :repository) } + + let(:pipeline) { create(:ci_pipeline, project: project, ref: 'master', sha: project.commit.id) } + + before do + visit builds_project_pipeline_path(project, pipeline) + end + + it 'shows a list of jobs' do + expect(page).to have_content('Test') + expect(page).to have_content(build_passed.id) + expect(page).to have_content('Deploy') + expect(page).to have_content(build_failed.id) + expect(page).to have_content(build_running.id) + expect(page).to have_content(build_external.id) + expect(page).to have_content('Retry') + expect(page).to have_content('Cancel running') + expect(page).to have_button('Play') + end + + it 'shows jobs tab pane as active' do + expect(page).to have_css('#js-tab-builds.active') + end + + context 'page tabs' do + it 'shows Pipeline, Jobs and DAG tabs with link' do + expect(page).to have_link('Pipeline') + expect(page).to have_link('Jobs') + expect(page).to have_link('Needs') + end + + it 'shows counter in Jobs tab' do + expect(page.find('.js-builds-counter').text).to eq(pipeline.total_size.to_s) + end + + it 'shows Jobs tab as active' do + expect(page).to have_css('li.js-builds-tab-link .active') + end + end + + context 'retrying jobs' do + it { expect(page).not_to have_content('retried') } + + context 'when retrying' do + before do + find('[data-testid="retry"]', match: :first).click + end + + it 'does not show a "Retry" button', :sidekiq_might_not_need_inline do + expect(page).not_to have_content('Retry') + end + end + end + + context 'canceling jobs' do + it { expect(page).not_to have_selector('.ci-canceled') } + + context 'when canceling' do + before do + click_on 'Cancel running' + end + + it 'does not show a "Cancel running" button', :sidekiq_might_not_need_inline do + expect(page).not_to have_content('Cancel running') + end + end + end + + context 'playing manual job' do + before do + within '[data-testid="jobs-tab-table"]' do + click_button('Play') + + wait_for_requests + end + end + + it { expect(build_manual.reload).to be_pending } + end + + context 'when user unschedules a delayed job' do + before do + within '[data-testid="jobs-tab-table"]' do + click_button('Unschedule') + end + end + + it 'unschedules the delayed job and shows play button as a manual job' do + expect(page).to have_button('Play') + expect(page).not_to have_button('Unschedule') + end + end + end + describe 'GET /:project/-/pipelines/:id/failures' do let(:pipeline) { create(:ci_pipeline, project: project, ref: 'master', sha: '1234') } let(:pipeline_failures_page) { failures_project_pipeline_path(project, pipeline) } diff --git a/spec/frontend/__helpers__/matchers.js b/spec/frontend/__helpers__/matchers.js deleted file mode 100644 index 945abdafe9a..00000000000 --- a/spec/frontend/__helpers__/matchers.js +++ /dev/null @@ -1,68 +0,0 @@ -export default { - toHaveSpriteIcon: (element, iconName) => { - if (!iconName) { - throw new Error('toHaveSpriteIcon is missing iconName argument!'); - } - - if (!(element instanceof HTMLElement)) { - throw new Error(`${element} is not a DOM element!`); - } - - const iconReferences = [].slice.apply(element.querySelectorAll('svg use')); - const matchingIcon = iconReferences.find( - (reference) => reference.parentNode.getAttribute('data-testid') === `${iconName}-icon`, - ); - - const pass = Boolean(matchingIcon); - - let message; - if (pass) { - message = `${element.outerHTML} contains the sprite icon "${iconName}"!`; - } else { - message = `${element.outerHTML} does not contain the sprite icon "${iconName}"!`; - - const existingIcons = iconReferences.map((reference) => { - const iconUrl = reference.getAttribute('href'); - return `"${iconUrl.replace(/^.+#/, '')}"`; - }); - if (existingIcons.length > 0) { - message += ` (only found ${existingIcons.join(',')})`; - } - } - - return { - pass, - message: () => message, - }; - }, - toMatchInterpolatedText(received, match) { - let clearReceived; - let clearMatch; - - try { - clearReceived = received.replace(/\s\s+/gm, ' ').replace(/\s\./gm, '.').trim(); - } catch (e) { - return { actual: received, message: 'The received value is not a string', pass: false }; - } - try { - clearMatch = match.replace(/%{\w+}/gm, '').trim(); - } catch (e) { - return { message: 'The comparator value is not a string', pass: false }; - } - const pass = clearReceived === clearMatch; - const message = pass - ? () => ` - \n\n - Expected: ${this.utils.printExpected(clearReceived)} - To not equal: ${this.utils.printReceived(clearMatch)} - ` - : () => - ` - \n\n - Expected: ${this.utils.printExpected(clearReceived)} - To equal: ${this.utils.printReceived(clearMatch)} - `; - - return { actual: received, message, pass }; - }, -}; diff --git a/spec/frontend/__helpers__/matchers/index.js b/spec/frontend/__helpers__/matchers/index.js new file mode 100644 index 00000000000..4fcde89d421 --- /dev/null +++ b/spec/frontend/__helpers__/matchers/index.js @@ -0,0 +1,2 @@ +export * from './to_have_sprite_icon'; +export * from './to_match_interpolated_text'; diff --git a/spec/frontend/__helpers__/matchers/to_have_sprite_icon.js b/spec/frontend/__helpers__/matchers/to_have_sprite_icon.js new file mode 100644 index 00000000000..bce9d93bea8 --- /dev/null +++ b/spec/frontend/__helpers__/matchers/to_have_sprite_icon.js @@ -0,0 +1,36 @@ +export const toHaveSpriteIcon = (element, iconName) => { + if (!iconName) { + throw new Error('toHaveSpriteIcon is missing iconName argument!'); + } + + if (!(element instanceof HTMLElement)) { + throw new Error(`${element} is not a DOM element!`); + } + + const iconReferences = [].slice.apply(element.querySelectorAll('svg use')); + const matchingIcon = iconReferences.find( + (reference) => reference.parentNode.getAttribute('data-testid') === `${iconName}-icon`, + ); + + const pass = Boolean(matchingIcon); + + let message; + if (pass) { + message = `${element.outerHTML} contains the sprite icon "${iconName}"!`; + } else { + message = `${element.outerHTML} does not contain the sprite icon "${iconName}"!`; + + const existingIcons = iconReferences.map((reference) => { + const iconUrl = reference.getAttribute('href'); + return `"${iconUrl.replace(/^.+#/, '')}"`; + }); + if (existingIcons.length > 0) { + message += ` (only found ${existingIcons.join(',')})`; + } + } + + return { + pass, + message: () => message, + }; +}; diff --git a/spec/frontend/__helpers__/matchers/to_match_interpolated_text.js b/spec/frontend/__helpers__/matchers/to_match_interpolated_text.js new file mode 100644 index 00000000000..4ce814a01b4 --- /dev/null +++ b/spec/frontend/__helpers__/matchers/to_match_interpolated_text.js @@ -0,0 +1,30 @@ +export const toMatchInterpolatedText = (received, match) => { + let clearReceived; + let clearMatch; + + try { + clearReceived = received.replace(/\s\s+/gm, ' ').replace(/\s\./gm, '.').trim(); + } catch (e) { + return { actual: received, message: 'The received value is not a string', pass: false }; + } + try { + clearMatch = match.replace(/%{\w+}/gm, '').trim(); + } catch (e) { + return { message: 'The comparator value is not a string', pass: false }; + } + const pass = clearReceived === clearMatch; + const message = pass + ? () => ` + \n\n + Expected: ${this.utils.printExpected(clearReceived)} + To not equal: ${this.utils.printReceived(clearMatch)} + ` + : () => + ` + \n\n + Expected: ${this.utils.printExpected(clearReceived)} + To equal: ${this.utils.printReceived(clearMatch)} + `; + + return { actual: received, message, pass }; +}; diff --git a/spec/frontend/__helpers__/matchers/to_match_interpolated_text_spec.js b/spec/frontend/__helpers__/matchers/to_match_interpolated_text_spec.js new file mode 100644 index 00000000000..f6fd00011fe --- /dev/null +++ b/spec/frontend/__helpers__/matchers/to_match_interpolated_text_spec.js @@ -0,0 +1,46 @@ +describe('custom matcher toMatchInterpolatedText', () => { + describe('malformed input', () => { + it.each([null, 1, Symbol, Array, Object])( + 'fails graciously if the expected value is %s', + (expected) => { + expect(expected).not.toMatchInterpolatedText('null'); + }, + ); + }); + describe('malformed matcher', () => { + it.each([null, 1, Symbol, Array, Object])( + 'fails graciously if the matcher is %s', + (matcher) => { + expect('null').not.toMatchInterpolatedText(matcher); + }, + ); + }); + + describe('positive assertion', () => { + it.each` + htmlString | templateString + ${'foo'} | ${'foo'} + ${'foo'} | ${'foo%{foo}'} + ${'foo '} | ${'foo'} + ${'foo '} | ${'foo%{foo}'} + ${'foo . '} | ${'foo%{foo}.'} + ${'foo bar . '} | ${'foo%{foo} bar.'} + ${'foo\n\nbar . '} | ${'foo%{foo} bar.'} + ${'foo bar . .'} | ${'foo%{fooStart} bar.%{fooEnd}.'} + `('$htmlString equals $templateString', ({ htmlString, templateString }) => { + expect(htmlString).toMatchInterpolatedText(templateString); + }); + }); + + describe('negative assertion', () => { + it.each` + htmlString | templateString + ${'foo'} | ${'bar'} + ${'foo'} | ${'bar%{foo}'} + ${'foo'} | ${'@{lol}foo%{foo}'} + ${' fo o '} | ${'foo'} + `('$htmlString does not equal $templateString', ({ htmlString, templateString }) => { + expect(htmlString).not.toMatchInterpolatedText(templateString); + }); + }); +}); diff --git a/spec/frontend/__helpers__/matchers_spec.js b/spec/frontend/__helpers__/matchers_spec.js deleted file mode 100644 index dfd6f754c72..00000000000 --- a/spec/frontend/__helpers__/matchers_spec.js +++ /dev/null @@ -1,48 +0,0 @@ -describe('Custom jest matchers', () => { - describe('toMatchInterpolatedText', () => { - describe('malformed input', () => { - it.each([null, 1, Symbol, Array, Object])( - 'fails graciously if the expected value is %s', - (expected) => { - expect(expected).not.toMatchInterpolatedText('null'); - }, - ); - }); - describe('malformed matcher', () => { - it.each([null, 1, Symbol, Array, Object])( - 'fails graciously if the matcher is %s', - (matcher) => { - expect('null').not.toMatchInterpolatedText(matcher); - }, - ); - }); - - describe('positive assertion', () => { - it.each` - htmlString | templateString - ${'foo'} | ${'foo'} - ${'foo'} | ${'foo%{foo}'} - ${'foo '} | ${'foo'} - ${'foo '} | ${'foo%{foo}'} - ${'foo . '} | ${'foo%{foo}.'} - ${'foo bar . '} | ${'foo%{foo} bar.'} - ${'foo\n\nbar . '} | ${'foo%{foo} bar.'} - ${'foo bar . .'} | ${'foo%{fooStart} bar.%{fooEnd}.'} - `('$htmlString equals $templateString', ({ htmlString, templateString }) => { - expect(htmlString).toMatchInterpolatedText(templateString); - }); - }); - - describe('negative assertion', () => { - it.each` - htmlString | templateString - ${'foo'} | ${'bar'} - ${'foo'} | ${'bar%{foo}'} - ${'foo'} | ${'@{lol}foo%{foo}'} - ${' fo o '} | ${'foo'} - `('$htmlString does not equal $templateString', ({ htmlString, templateString }) => { - expect(htmlString).not.toMatchInterpolatedText(templateString); - }); - }); - }); -}); diff --git a/spec/frontend/__helpers__/shared_test_setup.js b/spec/frontend/__helpers__/shared_test_setup.js index 03389e16b65..7b5df18ee0f 100644 --- a/spec/frontend/__helpers__/shared_test_setup.js +++ b/spec/frontend/__helpers__/shared_test_setup.js @@ -8,7 +8,7 @@ import setWindowLocation from './set_window_location_helper'; import { setGlobalDateToFakeDate } from './fake_date'; import { loadHTMLFixture, setHTMLFixture } from './fixtures'; import { TEST_HOST } from './test_constants'; -import customMatchers from './matchers'; +import * as customMatchers from './matchers'; import './dom_shims'; import './jquery'; diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/conan_installation_spec.js.snap b/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/conan_installation_spec.js.snap index e9f80d5f512..8c260c0acfb 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/conan_installation_spec.js.snap +++ b/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/conan_installation_spec.js.snap @@ -28,9 +28,13 @@ exports[`ConanInstallation renders all the messages 1`] = ` trackingaction="copy_conan_setup_command" trackinglabel="code_instruction" /> - - <gl-sprintf-stub - message="For more information on the Conan registry, %{linkStart}see the documentation%{linkEnd}." - /> + For more information on the Conan registry, + <gl-link-stub + href="/help/user/packages/conan_repository/index" + target="_blank" + > + see the documentation + </gl-link-stub> + . </div> `; diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/maven_installation_spec.js.snap b/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/maven_installation_spec.js.snap index 4865b8205ab..1626d464298 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/maven_installation_spec.js.snap +++ b/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/maven_installation_spec.js.snap @@ -64,9 +64,15 @@ exports[`MavenInstallation maven renders all the messages 1`] = ` /> <p> - <gl-sprintf-stub - message="Copy and paste this inside your %{codeStart}pom.xml%{codeEnd} %{codeStart}dependencies%{codeEnd} block." - /> + Copy and paste this inside your + <code> + pom.xml + </code> + + <code> + dependencies + </code> + block. </p> <code-instruction-stub @@ -97,9 +103,11 @@ exports[`MavenInstallation maven renders all the messages 1`] = ` </h3> <p> - <gl-sprintf-stub - message="If you haven't already done so, you will need to add the below to your %{codeStart}pom.xml%{codeEnd} file." - /> + If you haven't already done so, you will need to add the below to your + <code> + pom.xml + </code> + file. </p> <code-instruction-stub @@ -127,9 +135,13 @@ exports[`MavenInstallation maven renders all the messages 1`] = ` trackingaction="copy_maven_setup_xml" trackinglabel="code_instruction" /> - - <gl-sprintf-stub - message="For more information on the Maven registry, %{linkStart}see the documentation%{linkEnd}." - /> + For more information on the Maven registry, + <gl-link-stub + href="/help/user/packages/maven_repository/index" + target="_blank" + > + see the documentation + </gl-link-stub> + . </div> `; diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/npm_installation_spec.js.snap b/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/npm_installation_spec.js.snap index d5649e39561..16b573bb4a0 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/npm_installation_spec.js.snap +++ b/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/npm_installation_spec.js.snap @@ -37,9 +37,13 @@ exports[`NpmInstallation renders all the messages 1`] = ` trackingaction="copy_npm_setup_command" trackinglabel="code_instruction" /> - - <gl-sprintf-stub - message="You may also need to setup authentication using an auth token. %{linkStart}See the documentation%{linkEnd} to find out more." - /> + You may also need to setup authentication using an auth token. + <gl-link-stub + href="/help/user/packages/npm_registry/index" + target="_blank" + > + See the documentation + </gl-link-stub> + to find out more. </div> `; diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/nuget_installation_spec.js.snap b/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/nuget_installation_spec.js.snap index 29ddd7b77ed..b1f05d7cc6d 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/nuget_installation_spec.js.snap +++ b/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/nuget_installation_spec.js.snap @@ -28,9 +28,13 @@ exports[`NugetInstallation renders all the messages 1`] = ` trackingaction="copy_nuget_setup_command" trackinglabel="code_instruction" /> - - <gl-sprintf-stub - message="For more information on the NuGet registry, %{linkStart}see the documentation%{linkEnd}." - /> + For more information on the NuGet registry, + <gl-link-stub + href="/help/user/packages/nuget_repository/index" + target="_blank" + > + see the documentation + </gl-link-stub> + . </div> `; diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/pypi_installation_spec.js.snap b/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/pypi_installation_spec.js.snap index 158bbbc3463..3d3e3021026 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/pypi_installation_spec.js.snap +++ b/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/pypi_installation_spec.js.snap @@ -23,9 +23,11 @@ exports[`PypiInstallation renders all the messages 1`] = ` </h3> <p> - <gl-sprintf-stub - message="If you haven't already done so, you will need to add the below to your %{codeStart}.pypirc%{codeEnd} file." - /> + If you haven't already done so, you will need to add the below to your + <code> + .pypirc + </code> + file. </p> <code-instruction-stub @@ -40,9 +42,13 @@ password = <your personal access token>" trackingaction="copy_pypi_setup_command" trackinglabel="code_instruction" /> - - <gl-sprintf-stub - message="For more information on the PyPi registry, %{linkStart}see the documentation%{linkEnd}." - /> + For more information on the PyPi registry, + <gl-link-stub + href="/help/user/packages/pypi_repository/index" + target="_blank" + > + see the documentation + </gl-link-stub> + . </div> `; diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/composer_installation_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/composer_installation_spec.js index aedf20e873a..9906da6c0bc 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/details/composer_installation_spec.js +++ b/spec/frontend/packages_and_registries/package_registry/components/details/composer_installation_spec.js @@ -7,6 +7,7 @@ import { TRACKING_ACTION_COPY_COMPOSER_REGISTRY_INCLUDE_COMMAND, TRACKING_ACTION_COPY_COMPOSER_PACKAGE_INCLUDE_COMMAND, PACKAGE_TYPE_COMPOSER, + COMPOSER_HELP_PATH, } from '~/packages_and_registries/package_registry/constants'; const packageEntity = { ...packageData(), packageType: PACKAGE_TYPE_COMPOSER }; @@ -24,7 +25,6 @@ describe('ComposerInstallation', () => { function createComponent(groupListUrl = 'groupListUrl') { wrapper = shallowMountExtended(ComposerInstallation, { provide: { - composerHelpPath: 'composerHelpPath', composerConfigRepositoryName: 'composerConfigRepositoryName', composerPath: 'composerPath', groupListUrl, @@ -96,7 +96,7 @@ describe('ComposerInstallation', () => { 'For more information on Composer packages in GitLab, see the documentation.', ); expect(findHelpLink().attributes()).toMatchObject({ - href: 'composerHelpPath', + href: COMPOSER_HELP_PATH, target: '_blank', }); }); diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/conan_installation_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/conan_installation_spec.js index 6b642cc21b7..c5a2fb6606b 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/details/conan_installation_spec.js +++ b/spec/frontend/packages_and_registries/package_registry/components/details/conan_installation_spec.js @@ -1,8 +1,12 @@ +import { GlLink, GlSprintf } from '@gitlab/ui'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import { packageData } from 'jest/packages_and_registries/package_registry/mock_data'; import ConanInstallation from '~/packages_and_registries/package_registry/components/details/conan_installation.vue'; import InstallationTitle from '~/packages_and_registries/package_registry/components/details/installation_title.vue'; -import { PACKAGE_TYPE_CONAN } from '~/packages_and_registries/package_registry/constants'; +import { + PACKAGE_TYPE_CONAN, + CONAN_HELP_PATH, +} from '~/packages_and_registries/package_registry/constants'; import CodeInstructions from '~/vue_shared/components/registry/code_instruction.vue'; const packageEntity = { ...packageData(), packageType: PACKAGE_TYPE_CONAN }; @@ -12,16 +16,19 @@ describe('ConanInstallation', () => { const findCodeInstructions = () => wrapper.findAllComponents(CodeInstructions); const findInstallationTitle = () => wrapper.findComponent(InstallationTitle); + const findSetupDocsLink = () => wrapper.findComponent(GlLink); function createComponent() { wrapper = shallowMountExtended(ConanInstallation, { provide: { - conanHelpPath: 'conanHelpPath', conanPath: 'conanPath', }, propsData: { packageEntity, }, + stubs: { + GlSprintf, + }, }); } @@ -61,5 +68,12 @@ describe('ConanInstallation', () => { 'conan remote add gitlab conanPath', ); }); + + it('has a link to the docs', () => { + expect(findSetupDocsLink().attributes()).toMatchObject({ + href: CONAN_HELP_PATH, + target: '_blank', + }); + }); }); }); diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/maven_installation_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/maven_installation_spec.js index eed7e903833..dd74d106e83 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/details/maven_installation_spec.js +++ b/spec/frontend/packages_and_registries/package_registry/components/details/maven_installation_spec.js @@ -1,3 +1,4 @@ +import { GlLink, GlSprintf } from '@gitlab/ui'; import { nextTick } from 'vue'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; @@ -16,6 +17,7 @@ import { TRACKING_ACTION_COPY_KOTLIN_INSTALL_COMMAND, TRACKING_ACTION_COPY_KOTLIN_ADD_TO_SOURCE_COMMAND, PACKAGE_TYPE_MAVEN, + MAVEN_HELP_PATH, } from '~/packages_and_registries/package_registry/constants'; import CodeInstructions from '~/vue_shared/components/registry/code_instruction.vue'; @@ -28,7 +30,6 @@ describe('MavenInstallation', () => { metadata: mavenMetadata(), }; - const mavenHelpPath = 'mavenHelpPath'; const mavenPath = 'mavenPath'; const xmlCodeBlock = `<dependency> @@ -64,11 +65,11 @@ describe('MavenInstallation', () => { const findCodeInstructions = () => wrapper.findAllComponents(CodeInstructions); const findInstallationTitle = () => wrapper.findComponent(InstallationTitle); + const findSetupDocsLink = () => wrapper.findComponent(GlLink); function createComponent({ data = {} } = {}) { wrapper = shallowMountExtended(MavenInstallation, { provide: { - mavenHelpPath, mavenPath, }, propsData: { @@ -77,6 +78,9 @@ describe('MavenInstallation', () => { data() { return data; }, + stubs: { + GlSprintf, + }, }); } @@ -148,6 +152,13 @@ describe('MavenInstallation', () => { trackingAction: TRACKING_ACTION_COPY_MAVEN_SETUP, }); }); + + it('has a setup link', () => { + expect(findSetupDocsLink().attributes()).toMatchObject({ + href: MAVEN_HELP_PATH, + target: '_blank', + }); + }); }); }); diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/npm_installation_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/npm_installation_spec.js index b89410ede13..98204160110 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/details/npm_installation_spec.js +++ b/spec/frontend/packages_and_registries/package_registry/components/details/npm_installation_spec.js @@ -1,4 +1,4 @@ -import { GlFormRadioGroup } from '@gitlab/ui'; +import { GlLink, GlSprintf, GlFormRadioGroup } from '@gitlab/ui'; import { nextTick } from 'vue'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; @@ -15,6 +15,7 @@ import { YARN_PACKAGE_MANAGER, PROJECT_PACKAGE_ENDPOINT_TYPE, INSTANCE_PACKAGE_ENDPOINT_TYPE, + NPM_HELP_PATH, } from '~/packages_and_registries/package_registry/constants'; import CodeInstructions from '~/vue_shared/components/registry/code_instruction.vue'; @@ -29,11 +30,11 @@ describe('NpmInstallation', () => { const findCodeInstructions = () => wrapper.findAllComponents(CodeInstructions); const findInstallationTitle = () => wrapper.findComponent(InstallationTitle); const findEndPointTypeSector = () => wrapper.findComponent(GlFormRadioGroup); + const findSetupDocsLink = () => wrapper.findComponent(GlLink); function createComponent({ data = {} } = {}) { wrapper = shallowMountExtended(NpmInstallation, { provide: { - npmHelpPath: 'npmHelpPath', npmPath: 'npmPath', npmProjectPath: 'npmProjectPath', }, @@ -43,6 +44,7 @@ describe('NpmInstallation', () => { data() { return data; }, + stubs: { GlSprintf }, }); } @@ -58,6 +60,13 @@ describe('NpmInstallation', () => { expect(wrapper.element).toMatchSnapshot(); }); + it('has a setup link', () => { + expect(findSetupDocsLink().attributes()).toMatchObject({ + href: NPM_HELP_PATH, + target: '_blank', + }); + }); + describe('endpoint type selector', () => { it('has the endpoint type selector', () => { expect(findEndPointTypeSector().exists()).toBe(true); diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/nuget_installation_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/nuget_installation_spec.js index c48a3f07299..204ed702a05 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/details/nuget_installation_spec.js +++ b/spec/frontend/packages_and_registries/package_registry/components/details/nuget_installation_spec.js @@ -1,3 +1,4 @@ +import { GlLink, GlSprintf } from '@gitlab/ui'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import { packageData } from 'jest/packages_and_registries/package_registry/mock_data'; import InstallationTitle from '~/packages_and_registries/package_registry/components/details/installation_title.vue'; @@ -6,6 +7,7 @@ import { TRACKING_ACTION_COPY_NUGET_INSTALL_COMMAND, TRACKING_ACTION_COPY_NUGET_SETUP_COMMAND, PACKAGE_TYPE_NUGET, + NUGET_HELP_PATH, } from '~/packages_and_registries/package_registry/constants'; import CodeInstructions from '~/vue_shared/components/registry/code_instruction.vue'; @@ -20,16 +22,17 @@ describe('NugetInstallation', () => { const findCodeInstructions = () => wrapper.findAllComponents(CodeInstructions); const findInstallationTitle = () => wrapper.findComponent(InstallationTitle); + const findSetupDocsLink = () => wrapper.findComponent(GlLink); function createComponent() { wrapper = shallowMountExtended(NugetInstallation, { provide: { - nugetHelpPath: 'nugetHelpPath', nugetPath: 'nugetPath', }, propsData: { packageEntity, }, + stubs: { GlSprintf }, }); } @@ -71,5 +74,12 @@ describe('NugetInstallation', () => { trackingAction: TRACKING_ACTION_COPY_NUGET_SETUP_COMMAND, }); }); + + it('it has docs link', () => { + expect(findSetupDocsLink().attributes()).toMatchObject({ + href: NUGET_HELP_PATH, + target: '_blank', + }); + }); }); }); diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/pypi_installation_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/pypi_installation_spec.js index 410c1b65348..5714a567e14 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/details/pypi_installation_spec.js +++ b/spec/frontend/packages_and_registries/package_registry/components/details/pypi_installation_spec.js @@ -1,3 +1,4 @@ +import { GlLink, GlSprintf } from '@gitlab/ui'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import { packageData } from 'jest/packages_and_registries/package_registry/mock_data'; import InstallationTitle from '~/packages_and_registries/package_registry/components/details/installation_title.vue'; @@ -6,6 +7,7 @@ import { PACKAGE_TYPE_PYPI, TRACKING_ACTION_COPY_PIP_INSTALL_COMMAND, TRACKING_ACTION_COPY_PYPI_SETUP_COMMAND, + PYPI_HELP_PATH, } from '~/packages_and_registries/package_registry/constants'; const packageEntity = { ...packageData(), packageType: PACKAGE_TYPE_PYPI }; @@ -23,17 +25,20 @@ password = <your personal access token>`; const setupInstruction = () => wrapper.findByTestId('pypi-setup-content'); const findInstallationTitle = () => wrapper.findComponent(InstallationTitle); + const findSetupDocsLink = () => wrapper.findComponent(GlLink); function createComponent() { wrapper = shallowMountExtended(PypiInstallation, { provide: { - pypiHelpPath: 'pypiHelpPath', pypiPath: 'pypiPath', pypiSetupPath: 'pypiSetupPath', }, propsData: { packageEntity, }, + stubs: { + GlSprintf, + }, }); } @@ -76,5 +81,12 @@ password = <your personal access token>`; trackingAction: TRACKING_ACTION_COPY_PYPI_SETUP_COMMAND, }); }); + + it('has a link to the docs', () => { + expect(findSetupDocsLink().attributes()).toMatchObject({ + href: PYPI_HELP_PATH, + target: '_blank', + }); + }); }); }); diff --git a/spec/lib/gitlab/regex_spec.rb b/spec/lib/gitlab/regex_spec.rb index 83f85cc73d0..8d67350f0f3 100644 --- a/spec/lib/gitlab/regex_spec.rb +++ b/spec/lib/gitlab/regex_spec.rb @@ -433,6 +433,7 @@ RSpec.describe Gitlab::Regex do describe '.nuget_version_regex' do subject { described_class.nuget_version_regex } + it { is_expected.to match('1.2') } it { is_expected.to match('1.2.3') } it { is_expected.to match('1.2.3.4') } it { is_expected.to match('1.2.3.4-stable.1') } @@ -440,7 +441,6 @@ RSpec.describe Gitlab::Regex do it { is_expected.to match('1.2.3-alpha.3') } it { is_expected.to match('1.0.7+r3456') } it { is_expected.not_to match('1') } - it { is_expected.not_to match('1.2') } it { is_expected.not_to match('1./2.3') } it { is_expected.not_to match('../../../../../1.2.3') } it { is_expected.not_to match('%2e%2e%2f1.2.3') } diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb index afea20b31b9..81728432a30 100644 --- a/spec/models/application_setting_spec.rb +++ b/spec/models/application_setting_spec.rb @@ -1244,7 +1244,7 @@ RSpec.describe ApplicationSetting do end end - describe '#static_objects_external_storage_auth_token=' do + describe '#static_objects_external_storage_auth_token=', :aggregate_failures do subject { setting.static_objects_external_storage_auth_token = token } let(:token) { 'Test' } @@ -1268,5 +1268,20 @@ RSpec.describe ApplicationSetting do expect(setting.static_objects_external_storage_auth_token).to be_nil end end + + context 'with plaintext token only' do + let(:token) { '' } + + it 'ignores the plaintext token' do + subject + + ApplicationSetting.update_all(static_objects_external_storage_auth_token: 'Test') + + setting.reload + expect(setting[:static_objects_external_storage_auth_token]).to be_nil + expect(setting[:static_objects_external_storage_auth_token_encrypted]).to be_nil + expect(setting.static_objects_external_storage_auth_token).to be_nil + end + end end end diff --git a/spec/models/packages/package_spec.rb b/spec/models/packages/package_spec.rb index 923ec173368..122340f7bec 100644 --- a/spec/models/packages/package_spec.rb +++ b/spec/models/packages/package_spec.rb @@ -413,9 +413,17 @@ RSpec.describe Packages::Package, type: :model do it_behaves_like 'validating version to be SemVer compliant for', :terraform_module_package context 'nuget package' do - it_behaves_like 'validating version to be SemVer compliant for', :nuget_package + subject { build_stubbed(:nuget_package) } + it { is_expected.to allow_value('1.2').for(:version) } + it { is_expected.to allow_value('1.2.3').for(:version) } it { is_expected.to allow_value('1.2.3.4').for(:version) } + it { is_expected.to allow_value('1.2.3-beta').for(:version) } + it { is_expected.to allow_value('1.2.3-alpha.3').for(:version) } + it { is_expected.not_to allow_value('1').for(:version) } + it { is_expected.not_to allow_value('1./2.3').for(:version) } + it { is_expected.not_to allow_value('../../../../../1.2.3').for(:version) } + it { is_expected.not_to allow_value('%2e%2e%2f1.2.3').for(:version) } end end |