diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-11-05 21:12:21 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-11-05 21:12:21 +0300 |
commit | 25307dda309ede41ea2e67f16f6de25d0ec1c40e (patch) | |
tree | 8684e28ace0cb742e45a041710dc833fb0b0eadc | |
parent | 81a37f05815a4c731a2d2c93302ddc554444b637 (diff) |
Add latest changes from gitlab-org/gitlab@master
44 files changed, 778 insertions, 206 deletions
diff --git a/.gitlab/issue_templates/Problem Validation.md b/.gitlab/issue_templates/Problem Validation.md index 5d417c5a26d..3f92510b6af 100644 --- a/.gitlab/issue_templates/Problem Validation.md +++ b/.gitlab/issue_templates/Problem Validation.md @@ -1,3 +1,7 @@ +<!-- This template is used as a starting point for understing and articulating a customer problem. +Learn more about it in the handbook: https://about.gitlab.com/handbook/product-development-flow/#validation-phase-2-problem-validation +--> + ## Problem Statement <!-- What is the problem we hope to validate? Reference how to write a real customer problem statement at https://productcoalition.com/how-to-write-a-good-customer-problem-statement-a815f80189ba for guidance. --> @@ -45,4 +49,8 @@ For example, if the solution will take a product manager, designer, and engineer - [ ] The problem is well described and detailed with necessary requirements for product design to understand the problem - [ ] The problem is well described and detailed with necessary requirements for engineering to understand the problem +## Research Issue + +<!-- Link to the Problem Validation Research issue that will be executed by the UX Researcher. https://gitlab.com/gitlab-org/ux-research/ --> + /label ~"workflow::validation backlog" ~devops:: ~category: ~group:: diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c670421d88..6b8363ed543 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2293,6 +2293,15 @@ entry. - [Remove diffs gradual load feature flag](gitlab-org/gitlab@027d7c4327b5b6205a84281239027273517bf81b) ([merge request](gitlab-org/gitlab!55478)) - [Remove partial index for Hashed Storage migration](gitlab-org/gitlab@3ed017a1023d7b0941a7606b69e6caee8d22f15c) ([merge request](gitlab-org/gitlab!62920)) +## 14.0.12 (2021-11-05) + +### Changed (4 changes) + +- [Geo: Alternate redownload and normal design sync attempts](gitlab-org/gitlab@ed34172e5c7425316480efb732a9429e93e81017) ([merge request](gitlab-org/gitlab!73722)) **GitLab Enterprise Edition** +- [Geo: Alternate redownload and normal SSF sync attempts](gitlab-org/gitlab@c0f2f40b98c4b9fc72c21c474a2224e045263ea2) ([merge request](gitlab-org/gitlab!73722)) **GitLab Enterprise Edition** +- [Geo: Alternate redownload and normal project syncs](gitlab-org/gitlab@6370a7258719d5eb0ad83c54383ecb7f4fd54fc2) ([merge request](gitlab-org/gitlab!73722)) **GitLab Enterprise Edition** +- [Geo: Reduce frequency of redownload attempts](gitlab-org/gitlab@1bcd41f28733b01286a42689857f6530c0805186) ([merge request](gitlab-org/gitlab!73722)) **GitLab Enterprise Edition** + ## 14.0.11 (2021-09-23) ### Fixed (1 change) diff --git a/app/assets/javascripts/clusters/agents/components/show.vue b/app/assets/javascripts/clusters/agents/components/show.vue index 5c672d288c5..afbba9d1f7c 100644 --- a/app/assets/javascripts/clusters/agents/components/show.vue +++ b/app/assets/javascripts/clusters/agents/components/show.vue @@ -128,6 +128,7 @@ export default { </p> <gl-tabs> + <slot name="ee-security-tab"></slot> <gl-tab> <template #title> <span data-testid="cluster-agent-token-count"> diff --git a/app/assets/javascripts/clusters/agents/index.js b/app/assets/javascripts/clusters/agents/index.js index bcb5b271203..426d8d83847 100644 --- a/app/assets/javascripts/clusters/agents/index.js +++ b/app/assets/javascripts/clusters/agents/index.js @@ -1,7 +1,7 @@ import Vue from 'vue'; import VueApollo from 'vue-apollo'; import createDefaultClient from '~/lib/graphql'; -import AgentShowPage from './components/show.vue'; +import AgentShowPage from 'ee_else_ce/clusters/agents/components/show.vue'; Vue.use(VueApollo); diff --git a/app/assets/javascripts/google_cloud/components/app.vue b/app/assets/javascripts/google_cloud/components/app.vue new file mode 100644 index 00000000000..1e5be9df019 --- /dev/null +++ b/app/assets/javascripts/google_cloud/components/app.vue @@ -0,0 +1,50 @@ +<script> +import { GlTab, GlTabs } from '@gitlab/ui'; +import IncubationBanner from './incubation_banner.vue'; +import ServiceAccounts from './service_accounts.vue'; + +export default { + components: { GlTab, GlTabs, IncubationBanner, ServiceAccounts }, + props: { + serviceAccounts: { + type: Array, + required: true, + }, + createServiceAccountUrl: { + type: String, + required: true, + }, + emptyIllustrationUrl: { + type: String, + required: true, + }, + }, + methods: { + feedbackUrl(template) { + return `https://gitlab.com/gitlab-org/incubation-engineering/five-minute-production/meta/-/issues/new?issuable_template=${template}`; + }, + }, +}; +</script> + +<template> + <div> + <incubation-banner + :share-feedback-url="feedbackUrl('general_feedback')" + :report-bug-url="feedbackUrl('report_bug')" + :feature-request-url="feedbackUrl('feature_request')" + /> + <gl-tabs> + <gl-tab :title="__('Configuration')"> + <service-accounts + class="gl-mx-3" + :list="serviceAccounts" + :create-url="createServiceAccountUrl" + :empty-illustration-url="emptyIllustrationUrl" + /> + </gl-tab> + <gl-tab :title="__('Deployments')" disabled /> + <gl-tab :title="__('Services')" disabled /> + </gl-tabs> + </div> +</template> diff --git a/app/assets/javascripts/google_cloud/components/incubation_banner.vue b/app/assets/javascripts/google_cloud/components/incubation_banner.vue new file mode 100644 index 00000000000..652b8c1aecb --- /dev/null +++ b/app/assets/javascripts/google_cloud/components/incubation_banner.vue @@ -0,0 +1,44 @@ +<script> +import { GlAlert, GlLink, GlSprintf } from '@gitlab/ui'; + +export default { + components: { GlAlert, GlLink, GlSprintf }, + props: { + shareFeedbackUrl: { + required: true, + type: String, + }, + reportBugUrl: { + required: true, + type: String, + }, + featureRequestUrl: { + required: true, + type: String, + }, + }, +}; +</script> + +<template> + <gl-alert :dismissible="false" variant="info"> + {{ __('This is an experimental feature developed by GitLab Incubation Engineering.') }} + <gl-sprintf + :message=" + __( + 'We invite you to %{featureLinkStart}request a feature%{featureLinkEnd}, %{bugLinkStart}report a bug%{bugLinkEnd} or %{feedbackLinkStart}share feedback%{feedbackLinkEnd}', + ) + " + > + <template #featureLink="{ content }"> + <gl-link :href="featureRequestUrl">{{ content }}</gl-link> + </template> + <template #bugLink="{ content }"> + <gl-link :href="reportBugUrl">{{ content }}</gl-link> + </template> + <template #feedbackLink="{ content }"> + <gl-link :href="shareFeedbackUrl">{{ content }}</gl-link> + </template> + </gl-sprintf> + </gl-alert> +</template> diff --git a/app/assets/javascripts/google_cloud/components/service_accounts.vue b/app/assets/javascripts/google_cloud/components/service_accounts.vue new file mode 100644 index 00000000000..b70b25a5dc3 --- /dev/null +++ b/app/assets/javascripts/google_cloud/components/service_accounts.vue @@ -0,0 +1,65 @@ +<script> +import { GlButton, GlEmptyState, GlTable } from '@gitlab/ui'; +import { __ } from '~/locale'; + +export default { + components: { GlButton, GlEmptyState, GlTable }, + props: { + list: { + type: Array, + required: true, + }, + createUrl: { + type: String, + required: true, + }, + emptyIllustrationUrl: { + type: String, + required: true, + }, + }, + data() { + return { + tableFields: [ + { key: 'environment', label: __('Environment'), sortable: true }, + { key: 'gcp_project', label: __('Google Cloud Project'), sortable: true }, + { key: 'service_account_exists', label: __('Service Account'), sortable: true }, + { key: 'service_account_key_exists', label: __('Service Account Key'), sortable: true }, + ], + }; + }, +}; +</script> + +<template> + <div> + <gl-empty-state + v-if="list.length === 0" + :title="__('No service accounts')" + :description=" + __('Service Accounts keys authorize GitLab to deploy your Google Cloud project') + " + :primary-button-link="createUrl" + :primary-button-text="__('Create service account')" + :svg-path="emptyIllustrationUrl" + /> + + <div v-else> + <h2 class="gl-font-size-h2">{{ __('Service Accounts') }}</h2> + <p>{{ __('Service Accounts keys authorize GitLab to deploy your Google Cloud project') }}</p> + + <gl-table :items="list" :fields="tableFields"> + <template #cell(service_account_exists)="{ value }"> + {{ value ? '✔' : __('Not found') }} + </template> + <template #cell(service_account_key_exists)="{ value }"> + {{ value ? '✔' : __('Not found') }} + </template> + </gl-table> + + <gl-button :href="createUrl" category="primary" variant="info"> + {{ __('Create service account') }} + </gl-button> + </div> + </div> +</template> diff --git a/app/assets/javascripts/google_cloud/index.js b/app/assets/javascripts/google_cloud/index.js new file mode 100644 index 00000000000..a2838605219 --- /dev/null +++ b/app/assets/javascripts/google_cloud/index.js @@ -0,0 +1,19 @@ +import Vue from 'vue'; +import App from './components/app.vue'; + +const elementRenderer = (element, props = {}) => (createElement) => + createElement(element, { props }); + +export default () => { + const root = document.querySelector('#js-google-cloud'); + + // uncomment this once backend is ready + // const dataset = JSON.parse(root.getAttribute('data')); + const mockDataset = { + createServiceAccountUrl: '#create-url', + serviceAccounts: [], + emptyIllustrationUrl: + 'https://gitlab.com/gitlab-org/gitlab-svgs/-/raw/main/illustrations/pipelines_empty.svg', + }; + return new Vue({ el: root, render: elementRenderer(App, mockDataset) }); +}; diff --git a/app/assets/javascripts/pages/projects/google_cloud/index.js b/app/assets/javascripts/pages/projects/google_cloud/index.js new file mode 100644 index 00000000000..4506ea8efd1 --- /dev/null +++ b/app/assets/javascripts/pages/projects/google_cloud/index.js @@ -0,0 +1,3 @@ +import initGoogleCloud from '~/google_cloud/index'; + +initGoogleCloud(); diff --git a/app/assets/javascripts/pages/projects/project.js b/app/assets/javascripts/pages/projects/project.js index 9e93f709937..a26aeeb6db4 100644 --- a/app/assets/javascripts/pages/projects/project.js +++ b/app/assets/javascripts/pages/projects/project.js @@ -23,14 +23,14 @@ export default class Project { }); } - $('.hide-no-ssh-message').on('click', function (e) { + $('.js-hide-no-ssh-message').on('click', function (e) { Cookies.set('hide_no_ssh_message', 'false'); - $(this).parents('.no-ssh-key-message').remove(); + $(this).parents('.js-no-ssh-key-message').remove(); return e.preventDefault(); }); - $('.hide-no-password-message').on('click', function (e) { + $('.js-hide-no-password-message').on('click', function (e) { Cookies.set('hide_no_password_message', 'false'); - $(this).parents('.no-password-message').remove(); + $(this).parents('.js-no-password-message').remove(); return e.preventDefault(); }); $('.hide-auto-devops-implicitly-enabled-banner').on('click', function (e) { diff --git a/app/assets/javascripts/projects/components/shared/delete_button.vue b/app/assets/javascripts/projects/components/shared/delete_button.vue index 525ea462847..2e46f437ace 100644 --- a/app/assets/javascripts/projects/components/shared/delete_button.vue +++ b/app/assets/javascripts/projects/components/shared/delete_button.vue @@ -1,5 +1,5 @@ <script> -import { GlModal, GlModalDirective, GlFormTextarea, GlButton } from '@gitlab/ui'; +import { GlModal, GlModalDirective, GlFormInput, GlButton } from '@gitlab/ui'; import { uniqueId } from 'lodash'; import csrf from '~/lib/utils/csrf'; import { __ } from '~/locale'; @@ -7,7 +7,7 @@ import { __ } from '~/locale'; export default { components: { GlModal, - GlFormTextarea, + GlFormInput, GlButton, }, directives: { @@ -88,7 +88,12 @@ export default { <p> <code class="gl-white-space-pre-wrap">{{ confirmPhrase }}</code> </p> - <gl-form-textarea id="confirm_name_input" v-model="userInput" name="confirm_name_input" /> + <gl-form-input + id="confirm_name_input" + v-model="userInput" + name="confirm_name_input" + type="text" + /> <slot name="modal-footer"></slot> </div> </gl-modal> diff --git a/app/assets/javascripts/terms/index.js b/app/assets/javascripts/terms/index.js index dc4f1190eb8..9d60fdfb50a 100644 --- a/app/assets/javascripts/terms/index.js +++ b/app/assets/javascripts/terms/index.js @@ -1,7 +1,7 @@ import Vue from 'vue'; +import TermsApp from 'jh_else_ce/terms/components/app.vue'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; -import TermsApp from './components/app.vue'; export const initTermsApp = () => { const el = document.getElementById('js-terms-of-service'); diff --git a/app/controllers/projects/cluster_agents_controller.rb b/app/controllers/projects/cluster_agents_controller.rb index e7fbe93131d..404d3907128 100644 --- a/app/controllers/projects/cluster_agents_controller.rb +++ b/app/controllers/projects/cluster_agents_controller.rb @@ -3,6 +3,10 @@ class Projects::ClusterAgentsController < Projects::ApplicationController before_action :authorize_can_read_cluster_agent! + before_action do + push_frontend_feature_flag(:cluster_vulnerabilities, project, default_enabled: :yaml) + end + feature_category :kubernetes_management def show @@ -17,3 +21,5 @@ class Projects::ClusterAgentsController < Projects::ApplicationController access_denied! end end + +Projects::ClusterAgentsController.prepend_mod_with('Projects::ClusterAgentsController') diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index bee25e2a569..8366b25d2bc 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -196,12 +196,26 @@ module ProjectsHelper cookies["hide_auto_devops_implicitly_enabled_banner_#{project.id}".to_sym].blank? end - def link_to_set_password - if current_user.require_password_creation_for_git? - link_to s_('SetPasswordToCloneLink|set a password'), edit_profile_password_path - else - link_to s_('CreateTokenToCloneLink|create a personal access token'), profile_personal_access_tokens_path - end + def no_password_message + push_pull_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: help_page_path('gitlab-basics/start-using-git', anchor: 'pull-and-push') } + clone_with_https_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: help_page_path('gitlab-basics/start-using-git', anchor: 'clone-with-https') } + set_password_link_start = '<a href="%{url}">'.html_safe % { url: edit_profile_password_path } + set_up_pat_link_start = '<a href="%{url}">'.html_safe % { url: profile_personal_access_tokens_path } + + message = if current_user.require_password_creation_for_git? + _('Your account is authenticated with SSO or SAML. To %{push_pull_link_start}push and pull%{link_end} over %{protocol} with Git using this account, you must %{set_password_link_start}set a password%{link_end} or %{set_up_pat_link_start}set up a Personal Access Token%{link_end} to use instead of a password. For more information, see %{clone_with_https_link_start}Clone with HTTPS%{link_end}.') + else + _('Your account is authenticated with SSO or SAML. To %{push_pull_link_start}push and pull%{link_end} over %{protocol} with Git using this account, you must %{set_up_pat_link_start}set up a Personal Access Token%{link_end} to use instead of a password. For more information, see %{clone_with_https_link_start}Clone with HTTPS%{link_end}.') + end + + html_escape(message) % { + push_pull_link_start: push_pull_link_start, + protocol: gitlab_config.protocol.upcase, + clone_with_https_link_start: clone_with_https_link_start, + set_password_link_start: set_password_link_start, + set_up_pat_link_start: set_up_pat_link_start, + link_end: '</a>'.html_safe + } end # Returns true if any projects are present. @@ -384,12 +398,11 @@ module ProjectsHelper # Returns the confirm phrase the user needs to type in order to delete the project # - # Occasionally a user will delete one project, believing it is a different (similar) one. - # Specifically, a user might delete an original project, believing it is a fork. - # Thus the phrase should be the project full name to include the namespace. + # Thus the phrase should include the namespace to make it very clear to the + # user which project is subject to deletion. # Relevant issue: https://gitlab.com/gitlab-org/gitlab/-/issues/343591 def delete_confirm_phrase(project) - s_('DeleteProject|Delete %{name}') % { name: project.full_name } + project.path_with_namespace end private diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 43427e2ebc7..d75f7984e2c 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -48,7 +48,7 @@ class CommitStatus < Ci::ApplicationRecord scope :ordered, -> { order(:name) } scope :ordered_by_stage, -> { order(stage_idx: :asc) } scope :latest_ordered, -> { latest.ordered.includes(project: :namespace) } - scope :retried_ordered, -> { retried.ordered.includes(project: :namespace) } + scope :retried_ordered, -> { retried.order(name: :asc, id: :desc).includes(project: :namespace) } scope :ordered_by_pipeline, -> { order(pipeline_id: :asc) } scope :before_stage, -> (index) { where('stage_idx < ?', index) } scope :for_stage, -> (index) { where(stage_idx: index) } diff --git a/app/models/merge_request_diff_commit.rb b/app/models/merge_request_diff_commit.rb index f776ac4256c..b1cae0d1e49 100644 --- a/app/models/merge_request_diff_commit.rb +++ b/app/models/merge_request_diff_commit.rb @@ -7,6 +7,7 @@ class MergeRequestDiffCommit < ApplicationRecord include ShaAttribute include CachedCommit include IgnorableColumns + include FromUnion ignore_column %i[author_name author_email committer_name committer_email], remove_with: '14.6', diff --git a/app/models/user.rb b/app/models/user.rb index 886c30bd28e..2ca7909ebcb 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -473,7 +473,11 @@ class User < ApplicationRecord end def active_for_authentication? - super && can?(:log_in) + return false unless super + + check_ldap_if_ldap_blocked! + + can?(:log_in) end # The messages for these keys are defined in `devise.en.yml` @@ -2167,6 +2171,13 @@ class User < ApplicationRecord def ci_job_token_scope_cache_key "users:#{id}:ci:job_token_scope" end + + # An `ldap_blocked` user will be unblocked if LDAP indicates they are allowed. + def check_ldap_if_ldap_blocked! + return unless ::Gitlab::Auth::Ldap::Config.enabled? && ldap_blocked? + + ::Gitlab::Auth::Ldap::Access.allowed?(self) + end end User.prepend_mod_with('User') diff --git a/app/views/shared/_no_password.html.haml b/app/views/shared/_no_password.html.haml index 9c1e5a49b44..d1e1a8a819d 100644 --- a/app/views/shared/_no_password.html.haml +++ b/app/views/shared/_no_password.html.haml @@ -1,12 +1,10 @@ - if show_no_password_message? - .no-password-message.gl-alert.gl-alert-warning - = sprite_icon('warning', size: 16, css_class: 'gl-icon gl-alert-icon gl-alert-icon-no-title') - %button.js-close.gl-alert-dismiss{ type: 'button', 'aria-label': _('Dismiss') } - = sprite_icon('close', size: 16, css_class: 'gl-icon') + = render 'shared/global_alert', + variant: :warning, + alert_class: 'js-no-password-message', + close_button_class: 'js-hide-no-password-message' do .gl-alert-body - - translation_params = { protocol: gitlab_config.protocol.upcase, set_password_link: link_to_set_password } - - set_password_message = _("You won't be able to pull or push repositories via %{protocol} until you %{set_password_link} on your account") % translation_params - = set_password_message.html_safe + = no_password_message .gl-alert-actions - = link_to _('Remind later'), '#', class: 'hide-no-password-message btn gl-alert-action btn-info btn-md gl-button' - = link_to _("Don't show again"), profile_path(user: {hide_no_password: true}), method: :put, role: 'button', class: 'btn gl-alert-action btn-md btn-default gl-button btn-default-secondary' + = link_to _('Remind later'), '#', class: 'js-hide-no-password-message gl-alert-action btn btn-confirm btn-md gl-button' + = link_to _("Don't show again"), profile_path(user: { hide_no_password: true }), method: :put, role: 'button', class: 'gl-alert-action btn btn-default btn-md gl-button' diff --git a/app/views/shared/_no_ssh.html.haml b/app/views/shared/_no_ssh.html.haml index 2c6ceb58654..20dc1b41970 100644 --- a/app/views/shared/_no_ssh.html.haml +++ b/app/views/shared/_no_ssh.html.haml @@ -1,10 +1,10 @@ - if show_no_ssh_key_message? - %div{ class: 'no-ssh-key-message gl-alert gl-alert-warning', role: 'alert' } - = sprite_icon('warning', css_class: 'gl-icon s16 gl-alert-icon gl-alert-icon-no-title') - %button{ class: 'gl-alert-dismiss hide-no-ssh-message', type: 'button', 'aria-label': _('Dismiss') } - = sprite_icon('close', css_class: 'gl-icon s16') + = render 'shared/global_alert', + variant: :warning, + alert_class: 'js-no-ssh-message', + close_button_class: 'js-hide-no-ssh-message' do .gl-alert-body - = s_("MissingSSHKeyWarningLink|You won't be able to pull or push repositories via SSH until you add an SSH key to your profile") + = s_("MissingSSHKeyWarningLink|You can't push or pull repositories using SSH until you add an SSH key to your profile.") .gl-alert-actions - = link_to s_('MissingSSHKeyWarningLink|Add SSH key'), profile_keys_path, class: "btn gl-alert-action btn-warning btn-md gl-button" - = link_to s_("MissingSSHKeyWarningLink|Don't show again"), profile_path(user: {hide_no_ssh_key: true}), method: :put, role: 'button', class: 'btn gl-alert-action btn-md btn-warning gl-button btn-warning-secondary' + = link_to s_('MissingSSHKeyWarningLink|Add SSH key'), profile_keys_path, class: "gl-alert-action btn btn-confirm btn-md gl-button" + = link_to s_("MissingSSHKeyWarningLink|Don't show again"), profile_path(user: { hide_no_ssh_key: true }), method: :put, role: 'button', class: 'gl-alert-action btn btn-default btn-md gl-button' diff --git a/config/feature_flags/development/cluster_vulnerabilities.yml b/config/feature_flags/development/cluster_vulnerabilities.yml new file mode 100644 index 00000000000..919cdc1d009 --- /dev/null +++ b/config/feature_flags/development/cluster_vulnerabilities.yml @@ -0,0 +1,8 @@ +--- +name: cluster_vulnerabilities +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/73321 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/343917 +milestone: '14.5' +type: development +group: group::container security +default_enabled: false diff --git a/config/feature_flags/development/terms_of_service_vue.yml b/config/feature_flags/development/terms_of_service_vue.yml index d80af2ab716..01bf3613127 100644 --- a/config/feature_flags/development/terms_of_service_vue.yml +++ b/config/feature_flags/development/terms_of_service_vue.yml @@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/343046 milestone: '14.5' type: development group: group::access -default_enabled: false +default_enabled: true diff --git a/doc/administration/feature_flags.md b/doc/administration/feature_flags.md index f2067e7a2d1..afbf0759452 100644 --- a/doc/administration/feature_flags.md +++ b/doc/administration/feature_flags.md @@ -42,11 +42,15 @@ GitLab to an earlier version, the feature flag status may change. Features that are disabled by default may change or be removed without notice in a future version of GitLab. -Data corruption, stability degradation, or performance degradation might occur if +Data corruption, stability degradation, performance degradation, or security issues might occur if you enable a feature that's disabled by default. Problems caused by using a default disabled feature aren't covered by GitLab support, unless you were directed by GitLab to enable the feature. +Security issues found in features that are disabled by default are patched in regular releases +and do not follow our regular [maintenance policy](../policy/maintenance.md#security-releases) +with regards to backporting the fix. + ## Risks when disabling released features In most cases, the feature flag code is removed in a future version of GitLab. diff --git a/doc/administration/sidekiq.md b/doc/administration/sidekiq.md index 4aee88ed9cb..8f1119f6868 100644 --- a/doc/administration/sidekiq.md +++ b/doc/administration/sidekiq.md @@ -74,6 +74,20 @@ you want using steps 1 and 2 from the GitLab downloads page. postgresql['trust_auth_cidr_addresses'] = %w(127.0.0.1/32 10.10.1.30/32 10.10.1.31/32 10.10.1.32/32 10.10.1.33/32 10.10.1.38/32) ``` +1. If you run multiple Sidekiq nodes with a shared file storage, such as NFS, you must + specify the UIDs and GIDs to ensure they match between servers. Specifying the UIDs + and GIDs prevents permissions issues in the file system. This advice is similar to the + [advice for Geo setups](geo/replication/multiple_servers.md#step-4-configure-the-frontend-application-nodes-on-the-geo-secondary-site): + + ```ruby + user['uid'] = 9000 + user['gid'] = 9000 + web_server['uid'] = 9001 + web_server['gid'] = 9001 + registry['uid'] = 9002 + registry['gid'] = 9002 + ``` + 1. Disable other services: ```ruby diff --git a/doc/user/project/badges.md b/doc/user/project/badges.md index d54edc7e6d3..9ca11d43864 100644 --- a/doc/user/project/badges.md +++ b/doc/user/project/badges.md @@ -56,11 +56,18 @@ To add this badge to a project: ## Group badges -Badges can be added to a group and are visible on every project's -overview page that's under that group. In this case, they cannot be edited or -deleted on the project level. If you need to have individual badges for each -project, consider adding them on the [project level](#project-badges) or use -[placeholders](#placeholders). +By adding a badge to a group, you add and enforce a project-level badge +for all projects in the group. The group badge is visible on the **Overview** +page of any project that belongs to the group. + +NOTE: +While these badges appear as project-level badges in the codebase, they +cannot be edited or deleted at the project level. + +If you need individual badges for each project, either: + +- Add the badge at the [project level](#project-badges). +- Use [placeholders](#placeholders). To add a new badge to a group: diff --git a/doc/user/project/wiki/group.md b/doc/user/project/wiki/group.md index 6e364a688d3..731fed2595a 100644 --- a/doc/user/project/wiki/group.md +++ b/doc/user/project/wiki/group.md @@ -44,7 +44,22 @@ Users with the [Owner role](../../permissions.md) in a group can [import and export group wikis](../../group/settings/import_export.md) when importing or exporting a group. -Content created in a group wiki is not deleted when an account is downgraded or a GitLab trial ends. +Content created in a group wiki is not deleted when an account is downgraded or a +GitLab trial ends. The group wiki data is exported whenever the group owner of +the wiki is exported. + +To access the group wiki data from the export file if the feature is no longer +available, you have to: + +1. Extract the [export file tarball](../../group/settings/import_export.md) with + this command, replacing `FILENAME` with your file's name: + `tar -xvzf FILENAME.tar.gz` +1. Browse to the `repositories` directory. This directory contains a + [Git bundle](https://git-scm.com/docs/git-bundle) with the extension `.wiki.bundle`. +1. Clone the Git bundle into a new repository, replacing `FILENAME` with + your bundle's name: `git clone FILENAME.wiki.bundle` + +All files in the wiki are available in this Git repository. ## Related topics diff --git a/lib/bulk_imports/groups/graphql/get_milestones_query.rb b/lib/bulk_imports/groups/graphql/get_milestones_query.rb deleted file mode 100644 index 5dd5b31cf0e..00000000000 --- a/lib/bulk_imports/groups/graphql/get_milestones_query.rb +++ /dev/null @@ -1,56 +0,0 @@ -# frozen_string_literal: true - -module BulkImports - module Groups - module Graphql - module GetMilestonesQuery - extend self - - def to_s - <<-'GRAPHQL' - query ($full_path: ID!, $cursor: String, $per_page: Int) { - group(fullPath: $full_path) { - milestones(first: $per_page, after: $cursor, includeDescendants: false) { - page_info: pageInfo { - next_page: endCursor - has_next_page: hasNextPage - } - nodes { - iid - title - description - state - start_date: startDate - due_date: dueDate - created_at: createdAt - updated_at: updatedAt - } - } - } - } - GRAPHQL - end - - def variables(context) - { - full_path: context.entity.source_full_path, - cursor: context.tracker.next_page, - per_page: ::BulkImports::Tracker::DEFAULT_PAGE_SIZE - } - end - - def base_path - %w[data group milestones] - end - - def data_path - base_path << 'nodes' - end - - def page_info_path - base_path << 'page_info' - end - end - end - end -end diff --git a/lib/gitlab/background_migration/fix_merge_request_diff_commit_users.rb b/lib/gitlab/background_migration/fix_merge_request_diff_commit_users.rb index 1c04b5b025e..ea3e56cb14a 100644 --- a/lib/gitlab/background_migration/fix_merge_request_diff_commit_users.rb +++ b/lib/gitlab/background_migration/fix_merge_request_diff_commit_users.rb @@ -10,7 +10,10 @@ module Gitlab # this process needs Git/Gitaly access, and duplicating all that code is far # too much, this migration relies on global models such as Project, # MergeRequest, etc. + # rubocop: disable Metrics/ClassLength class FixMergeRequestDiffCommitUsers + BATCH_SIZE = 100 + def initialize @commits = {} @users = {} @@ -33,24 +36,47 @@ module Gitlab # Loading everything using one big query may result in timeouts (e.g. # for projects the size of gitlab-org/gitlab). So instead we query # data on a per merge request basis. - project.merge_requests.each_batch do |mrs| - ::MergeRequestDiffCommit - .select([ - :merge_request_diff_id, - :relative_order, - :sha, - :committer_id, - :commit_author_id - ]) - .joins(merge_request_diff: :merge_request) - .where(merge_requests: { id: mrs.select(:id) }) - .where('commit_author_id IS NULL OR committer_id IS NULL') - .each do |commit| + project.merge_requests.each_batch(column: :iid) do |mrs| + mrs.ids.each do |mr_id| + each_row_to_check(mr_id) do |commit| update_commit(project, commit) end + end end end + def each_row_to_check(merge_request_id, &block) + columns = %w[merge_request_diff_id relative_order].map do |col| + Pagination::Keyset::ColumnOrderDefinition.new( + attribute_name: col, + order_expression: MergeRequestDiffCommit.arel_table[col.to_sym].asc, + nullable: :not_nullable, + distinct: false + ) + end + + order = Pagination::Keyset::Order.build(columns) + scope = MergeRequestDiffCommit + .joins(:merge_request_diff) + .where(merge_request_diffs: { merge_request_id: merge_request_id }) + .where('commit_author_id IS NULL OR committer_id IS NULL') + .order(order) + + Pagination::Keyset::Iterator + .new(scope: scope, use_union_optimization: true) + .each_batch(of: BATCH_SIZE) do |rows| + rows + .select([ + :merge_request_diff_id, + :relative_order, + :sha, + :committer_id, + :commit_author_id + ]) + .each(&block) + end + end + # rubocop: disable Metrics/AbcSize def update_commit(project, row) commit = find_commit(project, row.sha) @@ -125,5 +151,6 @@ module Gitlab MergeRequestDiffCommit.arel_table end end + # rubocop: enable Metrics/ClassLength end end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index b932dc5e249..60e93714371 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -2914,6 +2914,9 @@ msgstr "" msgid "Advanced export options" msgstr "" +msgid "AdvancedSearch|Reindex required" +msgstr "" + msgid "After a successful password update you will be redirected to login screen." msgstr "" @@ -7400,6 +7403,9 @@ msgstr "" msgid "ClusterAgents|Registration token" msgstr "" +msgid "ClusterAgents|Security" +msgstr "" + msgid "ClusterAgents|Select an Agent" msgstr "" @@ -9742,6 +9748,9 @@ msgstr "" msgid "Create requirement" msgstr "" +msgid "Create service account" +msgstr "" + msgid "Create snippet" msgstr "" @@ -9775,9 +9784,6 @@ msgstr "" msgid "CreateTag|Tag" msgstr "" -msgid "CreateTokenToCloneLink|create a personal access token" -msgstr "" - msgid "CreateValueStreamForm|%{name} (default)" msgstr "" @@ -11107,9 +11113,6 @@ msgstr "" msgid "Delete variable" msgstr "" -msgid "DeleteProject|Delete %{name}" -msgstr "" - msgid "DeleteProject|Failed to remove project repository. Please try again or contact administrator." msgstr "" @@ -16034,6 +16037,9 @@ msgstr "" msgid "Google Cloud" msgstr "" +msgid "Google Cloud Project" +msgstr "" + msgid "Google authentication is not %{link_start}properly configured%{link_end}. Ask your GitLab administrator if you want to use this service." msgstr "" @@ -18647,6 +18653,9 @@ msgstr "" msgid "Interval Pattern" msgstr "" +msgid "Introduced in GitLab 13.1, before using %{reindexing_link_start}zero-downtime reindexing%{link_end} and %{migrations_link_start}Advanced Search migrations%{link_end}, you need to %{recreate_link_start}recreate your index%{link_end}." +msgstr "" + msgid "Introducing Your DevOps Reports" msgstr "" @@ -22387,6 +22396,9 @@ msgstr "" msgid "MissingSSHKeyWarningLink|Don't show again" msgstr "" +msgid "MissingSSHKeyWarningLink|You can't push or pull repositories using SSH until you add an SSH key to your profile." +msgstr "" + msgid "MissingSSHKeyWarningLink|You won't be able to pull or push repositories via SSH until you add an SSH key to your profile" msgstr "" @@ -23366,6 +23378,9 @@ msgstr "" msgid "No schedules" msgstr "" +msgid "No service accounts" +msgstr "" + msgid "No severity matches the provided parameter" msgstr "" @@ -23461,6 +23476,9 @@ msgstr "" msgid "Not confidential" msgstr "" +msgid "Not found" +msgstr "" + msgid "Not found." msgstr "" @@ -31282,6 +31300,18 @@ msgstr "" msgid "Service" msgstr "" +msgid "Service Account" +msgstr "" + +msgid "Service Account Key" +msgstr "" + +msgid "Service Accounts" +msgstr "" + +msgid "Service Accounts keys authorize GitLab to deploy your Google Cloud project" +msgstr "" + msgid "Service Desk" msgstr "" @@ -31336,6 +31366,9 @@ msgstr "" msgid "ServicePing|Turn on service ping to review instance-level analytics." msgstr "" +msgid "Services" +msgstr "" + msgid "Session ID" msgstr "" @@ -31474,9 +31507,6 @@ msgstr "" msgid "Set what should be replicated by this secondary site." msgstr "" -msgid "SetPasswordToCloneLink|set a password" -msgstr "" - msgid "SetStatusModal|Add status emoji" msgstr "" @@ -35110,6 +35140,9 @@ msgstr "" msgid "This is a self-managed instance of GitLab." msgstr "" +msgid "This is an experimental feature developed by GitLab Incubation Engineering." +msgstr "" + msgid "This is the highest peak of users on your installation since the license started." msgstr "" @@ -37021,6 +37054,9 @@ msgstr "" msgid "UsageQuota|Learn more about usage quotas" msgstr "" +msgid "UsageQuota|No CI minutes usage data available." +msgstr "" + msgid "UsageQuota|Packages" msgstr "" @@ -38356,6 +38392,9 @@ msgstr "" msgid "We heard back from your device. You have been authenticated." msgstr "" +msgid "We invite you to %{featureLinkStart}request a feature%{featureLinkEnd}, %{bugLinkStart}report a bug%{bugLinkEnd} or %{feedbackLinkStart}share feedback%{feedbackLinkEnd}" +msgstr "" + msgid "We recommend cloud-based mobile authenticator apps such as Authy, Duo Mobile, and LastPass. They can restore access if you lose your hardware device." msgstr "" @@ -39552,9 +39591,6 @@ msgstr "" msgid "You won't be able to create new projects because you have reached your project limit." msgstr "" -msgid "You won't be able to pull or push repositories via %{protocol} until you %{set_password_link} on your account" -msgstr "" - msgid "You'll be charged for %{true_up_link_start}users over license%{link_end} on a quarterly or annual basis, depending on the terms of your agreement." msgstr "" @@ -39705,6 +39741,12 @@ msgstr "" msgid "Your account has been deactivated. You will not be able to: " msgstr "" +msgid "Your account is authenticated with SSO or SAML. To %{push_pull_link_start}push and pull%{link_end} over %{protocol} with Git using this account, you must %{set_password_link_start}set a password%{link_end} or %{set_up_pat_link_start}set up a Personal Access Token%{link_end} to use instead of a password. For more information, see %{clone_with_https_link_start}Clone with HTTPS%{link_end}." +msgstr "" + +msgid "Your account is authenticated with SSO or SAML. To %{push_pull_link_start}push and pull%{link_end} over %{protocol} with Git using this account, you must %{set_up_pat_link_start}set up a Personal Access Token%{link_end} to use instead of a password. For more information, see %{clone_with_https_link_start}Clone with HTTPS%{link_end}." +msgstr "" + msgid "Your account is locked." msgstr "" diff --git a/package.json b/package.json index 9bf46e477c2..88141c019af 100644 --- a/package.json +++ b/package.json @@ -55,9 +55,9 @@ "@babel/preset-env": "^7.10.1", "@gitlab/at.js": "1.5.7", "@gitlab/favicon-overlay": "2.0.0", - "@gitlab/svgs": "1.219.0", + "@gitlab/svgs": "1.220.0", "@gitlab/tributejs": "1.0.0", - "@gitlab/ui": "32.31.0", + "@gitlab/ui": "32.33.0", "@gitlab/visual-review-tools": "1.6.1", "@rails/actioncable": "6.1.4-1", "@rails/ujs": "6.1.4-1", diff --git a/spec/features/projects/show/no_password_spec.rb b/spec/features/projects/show/no_password_spec.rb index d18ff75b324..ed06f4e14d3 100644 --- a/spec/features/projects/show/no_password_spec.rb +++ b/spec/features/projects/show/no_password_spec.rb @@ -3,6 +3,9 @@ require 'spec_helper' RSpec.describe 'No Password Alert' do + let_it_be(:message_password_auth_enabled) { 'Your account is authenticated with SSO or SAML. To push and pull over HTTP with Git using this account, you must set a password or set up a Personal Access Token to use instead of a password. For more information, see Clone with HTTPS.' } + let_it_be(:message_password_auth_disabled) { 'Your account is authenticated with SSO or SAML. To push and pull over HTTP with Git using this account, you must set up a Personal Access Token to use instead of a password. For more information, see Clone with HTTPS.' } + let(:project) { create(:project, :repository, namespace: user.namespace) } context 'with internal auth enabled' do @@ -15,7 +18,7 @@ RSpec.describe 'No Password Alert' do let(:user) { create(:user) } it 'shows no alert' do - expect(page).not_to have_content "You won't be able to pull or push repositories via HTTP until you set a password on your account" + expect(page).not_to have_content message_password_auth_enabled end end @@ -23,7 +26,7 @@ RSpec.describe 'No Password Alert' do let(:user) { create(:user, password_automatically_set: true) } it 'shows a password alert' do - expect(page).to have_content "You won't be able to pull or push repositories via HTTP until you set a password on your account" + expect(page).to have_content message_password_auth_enabled end end end @@ -41,7 +44,7 @@ RSpec.describe 'No Password Alert' do gitlab_sign_in_via('saml', user, 'my-uid') visit project_path(project) - expect(page).to have_content "You won't be able to pull or push repositories via HTTP until you create a personal access token on your account" + expect(page).to have_content message_password_auth_disabled end end @@ -51,7 +54,7 @@ RSpec.describe 'No Password Alert' do gitlab_sign_in_via('saml', user, 'my-uid') visit project_path(project) - expect(page).not_to have_content "You won't be able to pull or push repositories via HTTP until you create a personal access token on your account" + expect(page).not_to have_content message_password_auth_disabled end end end diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb index fec2873e4fd..c4619b5498e 100644 --- a/spec/features/projects_spec.rb +++ b/spec/features/projects_spec.rb @@ -257,7 +257,7 @@ RSpec.describe 'Project' do end it 'deletes a project', :sidekiq_inline do - expect { remove_with_confirm('Delete project', "Delete #{project.full_name}", 'Yes, delete project') }.to change { Project.count }.by(-1) + expect { remove_with_confirm('Delete project', project.path_with_namespace, 'Yes, delete project') }.to change { Project.count }.by(-1) expect(page).to have_content "Project '#{project.full_name}' is in the process of being deleted." expect(Project.all.count).to be_zero expect(project.issues).to be_empty diff --git a/spec/frontend/clusters/agents/components/show_spec.js b/spec/frontend/clusters/agents/components/show_spec.js index fd04ff8b3e7..c502e7d813e 100644 --- a/spec/frontend/clusters/agents/components/show_spec.js +++ b/spec/frontend/clusters/agents/components/show_spec.js @@ -1,6 +1,8 @@ import { GlAlert, GlKeysetPagination, GlLoadingIcon, GlSprintf, GlTab } from '@gitlab/ui'; import { createLocalVue, shallowMount } from '@vue/test-utils'; +import { nextTick } from 'vue'; import VueApollo from 'vue-apollo'; +import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import ClusterAgentShow from '~/clusters/agents/components/show.vue'; import TokenTable from '~/clusters/agents/components/token_table.vue'; import getAgentQuery from '~/clusters/agents/graphql/queries/get_cluster_agent.query.graphql'; @@ -40,28 +42,34 @@ describe('ClusterAgentShow', () => { queryResponse || jest.fn().mockResolvedValue({ data: { project: { clusterAgent } } }); const apolloProvider = createMockApollo([[getAgentQuery, agentQueryResponse]]); - wrapper = shallowMount(ClusterAgentShow, { - localVue, - apolloProvider, - propsData, - stubs: { GlSprintf, TimeAgoTooltip, GlTab }, - }); + wrapper = extendedWrapper( + shallowMount(ClusterAgentShow, { + localVue, + apolloProvider, + propsData, + stubs: { GlSprintf, TimeAgoTooltip, GlTab }, + }), + ); }; - const createWrapperWithoutApollo = ({ clusterAgent, loading = false }) => { + const createWrapperWithoutApollo = ({ clusterAgent, loading = false, slots = {} }) => { const $apollo = { queries: { clusterAgent: { loading } } }; - wrapper = shallowMount(ClusterAgentShow, { - propsData, - mocks: { $apollo, clusterAgent }, - stubs: { GlTab }, - }); + wrapper = extendedWrapper( + shallowMount(ClusterAgentShow, { + propsData, + mocks: { $apollo, clusterAgent }, + slots, + stubs: { GlTab }, + }), + ); }; - const findCreatedText = () => wrapper.find('[data-testid="cluster-agent-create-info"]').text(); - const findLoadingIcon = () => wrapper.find(GlLoadingIcon); - const findPaginationButtons = () => wrapper.find(GlKeysetPagination); - const findTokenCount = () => wrapper.find('[data-testid="cluster-agent-token-count"]').text(); + const findCreatedText = () => wrapper.findByTestId('cluster-agent-create-info').text(); + const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); + const findPaginationButtons = () => wrapper.findComponent(GlKeysetPagination); + const findTokenCount = () => wrapper.findByTestId('cluster-agent-token-count').text(); + const findEESecurityTabSlot = () => wrapper.findByTestId('ee-security-tab'); afterEach(() => { wrapper.destroy(); @@ -87,7 +95,7 @@ describe('ClusterAgentShow', () => { }); it('renders token table', () => { - expect(wrapper.find(TokenTable).exists()).toBe(true); + expect(wrapper.findComponent(TokenTable).exists()).toBe(true); }); it('should not render pagination buttons when there are no additional pages', () => { @@ -188,8 +196,27 @@ describe('ClusterAgentShow', () => { }); it('displays an alert message', () => { - expect(wrapper.find(GlAlert).exists()).toBe(true); + expect(wrapper.findComponent(GlAlert).exists()).toBe(true); expect(wrapper.text()).toContain(ClusterAgentShow.i18n.loadingError); }); }); + + describe('ee-security-tab slot', () => { + it('does not display when a slot is not passed in', async () => { + createWrapperWithoutApollo({ clusterAgent: defaultClusterAgent }); + await nextTick(); + expect(findEESecurityTabSlot().exists()).toBe(false); + }); + + it('does display when a slot is passed in', async () => { + createWrapperWithoutApollo({ + clusterAgent: defaultClusterAgent, + slots: { + 'ee-security-tab': `<gl-tab data-testid="ee-security-tab">Security Tab!</gl-tab>`, + }, + }); + await nextTick(); + expect(findEESecurityTabSlot().exists()).toBe(true); + }); + }); }); diff --git a/spec/frontend/google_cloud/components/app_spec.js b/spec/frontend/google_cloud/components/app_spec.js new file mode 100644 index 00000000000..bb86eb5c22e --- /dev/null +++ b/spec/frontend/google_cloud/components/app_spec.js @@ -0,0 +1,66 @@ +import { shallowMount } from '@vue/test-utils'; +import { GlTab, GlTabs } from '@gitlab/ui'; +import App from '~/google_cloud/components/app.vue'; +import IncubationBanner from '~/google_cloud/components/incubation_banner.vue'; +import ServiceAccounts from '~/google_cloud/components/service_accounts.vue'; + +describe('google_cloud App component', () => { + let wrapper; + + const findIncubationBanner = () => wrapper.findComponent(IncubationBanner); + const findTabs = () => wrapper.findComponent(GlTabs); + const findTabItems = () => findTabs().findAllComponents(GlTab); + const findConfigurationTab = () => findTabItems().at(0); + const findDeploymentTab = () => findTabItems().at(1); + const findServicesTab = () => findTabItems().at(2); + const findServiceAccounts = () => findConfigurationTab().findComponent(ServiceAccounts); + + beforeEach(() => { + const propsData = { + serviceAccounts: [{}, {}], + createServiceAccountUrl: '#url-create-service-account', + emptyIllustrationUrl: '#url-empty-illustration', + }; + wrapper = shallowMount(App, { propsData }); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('should contain incubation banner', () => { + expect(findIncubationBanner().exists()).toBe(true); + }); + + describe('google_cloud App tabs', () => { + it('should contain tabs', () => { + expect(findTabs().exists()).toBe(true); + }); + + it('should contain three tab items', () => { + expect(findTabItems().length).toBe(3); + }); + + describe('configuration tab', () => { + it('should exist', () => { + expect(findConfigurationTab().exists()).toBe(true); + }); + + it('should contain service accounts component', () => { + expect(findServiceAccounts().exists()).toBe(true); + }); + }); + + describe('deployments tab', () => { + it('should exist', () => { + expect(findDeploymentTab().exists()).toBe(true); + }); + }); + + describe('services tab', () => { + it('should exist', () => { + expect(findServicesTab().exists()).toBe(true); + }); + }); + }); +}); diff --git a/spec/frontend/google_cloud/components/incubation_banner_spec.js b/spec/frontend/google_cloud/components/incubation_banner_spec.js new file mode 100644 index 00000000000..89517be4ef1 --- /dev/null +++ b/spec/frontend/google_cloud/components/incubation_banner_spec.js @@ -0,0 +1,60 @@ +import { mount } from '@vue/test-utils'; +import { GlAlert, GlLink } from '@gitlab/ui'; +import IncubationBanner from '~/google_cloud/components/incubation_banner.vue'; + +describe('IncubationBanner component', () => { + let wrapper; + + const findAlert = () => wrapper.findComponent(GlAlert); + const findLinks = () => wrapper.findAllComponents(GlLink); + const findFeatureRequestLink = () => findLinks().at(0); + const findReportBugLink = () => findLinks().at(1); + const findShareFeedbackLink = () => findLinks().at(2); + + beforeEach(() => { + const propsData = { + shareFeedbackUrl: 'url_general_feedback', + reportBugUrl: 'url_report_bug', + featureRequestUrl: 'url_feature_request', + }; + wrapper = mount(IncubationBanner, { propsData }); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('contains alert', () => { + expect(findAlert().exists()).toBe(true); + }); + + it('contains relevant text', () => { + expect(findAlert().text()).toContain( + 'This is an experimental feature developed by GitLab Incubation Engineering.', + ); + }); + + describe('has relevant gl-links', () => { + it('three in total', () => { + expect(findLinks().length).toBe(3); + }); + + it('contains feature request link', () => { + const link = findFeatureRequestLink(); + expect(link.text()).toBe('request a feature'); + expect(link.attributes('href')).toBe('url_feature_request'); + }); + + it('contains report bug link', () => { + const link = findReportBugLink(); + expect(link.text()).toBe('report a bug'); + expect(link.attributes('href')).toBe('url_report_bug'); + }); + + it('contains share feedback link', () => { + const link = findShareFeedbackLink(); + expect(link.text()).toBe('share feedback'); + expect(link.attributes('href')).toBe('url_general_feedback'); + }); + }); +}); diff --git a/spec/frontend/google_cloud/components/service_accounts_spec.js b/spec/frontend/google_cloud/components/service_accounts_spec.js new file mode 100644 index 00000000000..3d097078f03 --- /dev/null +++ b/spec/frontend/google_cloud/components/service_accounts_spec.js @@ -0,0 +1,79 @@ +import { mount } from '@vue/test-utils'; +import { GlButton, GlEmptyState, GlTable } from '@gitlab/ui'; +import ServiceAccounts from '~/google_cloud/components/service_accounts.vue'; + +describe('ServiceAccounts component', () => { + describe('when the project does not have any service accounts', () => { + let wrapper; + + const findEmptyState = () => wrapper.findComponent(GlEmptyState); + const findButtonInEmptyState = () => findEmptyState().findComponent(GlButton); + + beforeEach(() => { + const propsData = { + list: [], + createUrl: '#create-url', + emptyIllustrationUrl: '#empty-illustration-url', + }; + wrapper = mount(ServiceAccounts, { propsData }); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('shows the empty state component', () => { + expect(findEmptyState().exists()).toBe(true); + }); + it('shows the link to create new service accounts', () => { + const button = findButtonInEmptyState(); + expect(button.exists()).toBe(true); + expect(button.text()).toBe('Create service account'); + expect(button.attributes('href')).toBe('#create-url'); + }); + }); + + describe('when three service accounts are passed via props', () => { + let wrapper; + + const findTitle = () => wrapper.find('h2'); + const findDescription = () => wrapper.find('p'); + const findTable = () => wrapper.findComponent(GlTable); + const findRows = () => findTable().findAll('tr'); + const findButton = () => wrapper.findComponent(GlButton); + + beforeEach(() => { + const propsData = { + list: [{}, {}, {}], + createUrl: '#create-url', + emptyIllustrationUrl: '#empty-illustration-url', + }; + wrapper = mount(ServiceAccounts, { propsData }); + }); + + it('shows the title', () => { + expect(findTitle().text()).toBe('Service Accounts'); + }); + + it('shows the description', () => { + expect(findDescription().text()).toBe( + 'Service Accounts keys authorize GitLab to deploy your Google Cloud project', + ); + }); + + it('shows the table', () => { + expect(findTable().exists()).toBe(true); + }); + + it('table must have three rows + header row', () => { + expect(findRows().length).toBe(4); + }); + + it('shows the link to create new service accounts', () => { + const button = findButton(); + expect(button.exists()).toBe(true); + expect(button.text()).toBe('Create service account'); + expect(button.attributes('href')).toBe('#create-url'); + }); + }); +}); diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/version_row_spec.js.snap b/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/version_row_spec.js.snap index 8f69f943112..c95538546c1 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/version_row_spec.js.snap +++ b/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/version_row_spec.js.snap @@ -27,6 +27,7 @@ exports[`VersionRow renders 1`] = ` > <span class="gl-truncate" + data-testid="truncate-end-container" title="@gitlab-org/package-15" > <span diff --git a/spec/frontend/projects/components/__snapshots__/project_delete_button_spec.js.snap b/spec/frontend/projects/components/__snapshots__/project_delete_button_spec.js.snap index 492a40458e2..c255fcce321 100644 --- a/spec/frontend/projects/components/__snapshots__/project_delete_button_spec.js.snap +++ b/spec/frontend/projects/components/__snapshots__/project_delete_button_spec.js.snap @@ -71,10 +71,10 @@ exports[`Project remove modal initialized matches the snapshot 1`] = ` </code> </p> - <gl-form-textarea-stub + <gl-form-input-stub id="confirm_name_input" name="confirm_name_input" - noresize="true" + type="text" /> </div> diff --git a/spec/frontend/projects/components/shared/__snapshots__/delete_button_spec.js.snap b/spec/frontend/projects/components/shared/__snapshots__/delete_button_spec.js.snap index a34507e9199..dd54db7dc0a 100644 --- a/spec/frontend/projects/components/shared/__snapshots__/delete_button_spec.js.snap +++ b/spec/frontend/projects/components/shared/__snapshots__/delete_button_spec.js.snap @@ -51,10 +51,10 @@ exports[`Project remove modal intialized matches the snapshot 1`] = ` </code> </p> - <gl-form-textarea-stub + <gl-form-input-stub id="confirm_name_input" name="confirm_name_input" - noresize="true" + type="text" /> </div> diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb index 661d9ac2821..5d2af567549 100644 --- a/spec/helpers/projects_helper_spec.rb +++ b/spec/helpers/projects_helper_spec.rb @@ -268,7 +268,7 @@ RSpec.describe ProjectsHelper do end end - describe '#link_to_set_password' do + describe '#no_password_message' do let(:user) { create(:user, password_automatically_set: true) } before do @@ -276,18 +276,18 @@ RSpec.describe ProjectsHelper do end context 'password authentication is enabled for Git' do - it 'returns link to set a password' do + it 'returns message prompting user to set password or set up a PAT' do stub_application_setting(password_authentication_enabled_for_git?: true) - expect(helper.link_to_set_password).to match %r{<a href="#{edit_profile_password_path}">set a password</a>} + expect(helper.no_password_message).to eq('Your account is authenticated with SSO or SAML. To <a href="/help/gitlab-basics/start-using-git#pull-and-push" target="_blank" rel="noopener noreferrer">push and pull</a> over HTTP with Git using this account, you must <a href="/-/profile/password/edit">set a password</a> or <a href="/-/profile/personal_access_tokens">set up a Personal Access Token</a> to use instead of a password. For more information, see <a href="/help/gitlab-basics/start-using-git#clone-with-https" target="_blank" rel="noopener noreferrer">Clone with HTTPS</a>.') end end context 'password authentication is disabled for Git' do - it 'returns link to create a personal access token' do + it 'returns message prompting user to set up a PAT' do stub_application_setting(password_authentication_enabled_for_git?: false) - expect(helper.link_to_set_password).to match %r{<a href="#{profile_personal_access_tokens_path}">create a personal access token</a>} + expect(helper.no_password_message).to eq('Your account is authenticated with SSO or SAML. To <a href="/help/gitlab-basics/start-using-git#pull-and-push" target="_blank" rel="noopener noreferrer">push and pull</a> over HTTP with Git using this account, you must <a href="/-/profile/personal_access_tokens">set up a Personal Access Token</a> to use instead of a password. For more information, see <a href="/help/gitlab-basics/start-using-git#clone-with-https" target="_blank" rel="noopener noreferrer">Clone with HTTPS</a>.') end end end @@ -987,8 +987,8 @@ RSpec.describe ProjectsHelper do describe "#delete_confirm_phrase" do subject { helper.delete_confirm_phrase(project) } - it 'includes the project full name' do - expect(subject).to eq("Delete #{project.full_name}") + it 'includes the project path with namespace' do + expect(subject).to eq(project.path_with_namespace) end end end diff --git a/spec/lib/bulk_imports/groups/graphql/get_milestones_query_spec.rb b/spec/lib/bulk_imports/groups/graphql/get_milestones_query_spec.rb deleted file mode 100644 index 7a0f964c5f3..00000000000 --- a/spec/lib/bulk_imports/groups/graphql/get_milestones_query_spec.rb +++ /dev/null @@ -1,35 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe BulkImports::Groups::Graphql::GetMilestonesQuery do - it 'has a valid query' do - tracker = create(:bulk_import_tracker) - context = BulkImports::Pipeline::Context.new(tracker) - - query = GraphQL::Query.new( - GitlabSchema, - described_class.to_s, - variables: described_class.variables(context) - ) - result = GitlabSchema.static_validator.validate(query) - - expect(result[:errors]).to be_empty - end - - describe '#data_path' do - it 'returns data path' do - expected = %w[data group milestones nodes] - - expect(described_class.data_path).to eq(expected) - end - end - - describe '#page_info_path' do - it 'returns pagination information path' do - expected = %w[data group milestones page_info] - - expect(described_class.page_info_path).to eq(expected) - end - end -end diff --git a/spec/lib/gitlab/background_migration/fix_merge_request_diff_commit_users_spec.rb b/spec/lib/gitlab/background_migration/fix_merge_request_diff_commit_users_spec.rb index ffed0b517f9..c343ee438b8 100644 --- a/spec/lib/gitlab/background_migration/fix_merge_request_diff_commit_users_spec.rb +++ b/spec/lib/gitlab/background_migration/fix_merge_request_diff_commit_users_spec.rb @@ -49,6 +49,36 @@ RSpec.describe Gitlab::BackgroundMigration::FixMergeRequestDiffCommitUsers do end end + describe '#process' do + it 'processes the merge requests of the project' do + project = create(:project, :repository) + commit = project.commit + mr = create( + :merge_request_with_diffs, + source_project: project, + target_project: project + ) + + diff = mr.merge_request_diffs.first + + create( + :merge_request_diff_commit, + merge_request_diff: diff, + sha: commit.sha, + relative_order: 9000 + ) + + migration.process(project) + + updated = diff + .merge_request_diff_commits + .find_by(sha: commit.sha, relative_order: 9000) + + expect(updated.commit_author_id).not_to be_nil + expect(updated.committer_id).not_to be_nil + end + end + describe '#update_commit' do let(:project) { create(:project, :repository) } let(:mr) do diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb index 20afddd8470..59d14574c02 100644 --- a/spec/models/commit_status_spec.rb +++ b/spec/models/commit_status_spec.rb @@ -379,6 +379,22 @@ RSpec.describe CommitStatus do end end + describe '.retried_ordered' do + subject { described_class.retried_ordered.to_a } + + let!(:statuses) do + [create_status(name: 'aa', ref: 'bb', status: 'running', retried: true), + create_status(name: 'cc', ref: 'cc', status: 'pending', retried: true), + create_status(name: 'aa', ref: 'cc', status: 'success', retried: true), + create_status(name: 'cc', ref: 'bb', status: 'success'), + create_status(name: 'aa', ref: 'bb', status: 'success')] + end + + it 'returns retried statuses in order' do + is_expected.to eq(statuses.values_at(2, 0, 1)) + end + end + describe '.running_or_pending' do subject { described_class.running_or_pending.order(:id) } diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index bd357858dcf..2921d94c343 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -6,6 +6,7 @@ RSpec.describe User do include ProjectForksHelper include TermsHelper include ExclusiveLeaseHelpers + include LdapHelpers it_behaves_like 'having unique enum values' @@ -5808,7 +5809,7 @@ RSpec.describe User do end describe '#active_for_authentication?' do - subject { user.active_for_authentication? } + subject(:active_for_authentication?) { user.active_for_authentication? } let(:user) { create(:user) } @@ -5818,6 +5819,14 @@ RSpec.describe User do end it { is_expected.to be false } + + it 'does not check if LDAP is allowed' do + stub_ldap_setting(enabled: true) + + expect(Gitlab::Auth::Ldap::Access).not_to receive(:allowed?) + + active_for_authentication? + end end context 'when user is a ghost user' do @@ -5828,6 +5837,28 @@ RSpec.describe User do it { is_expected.to be false } end + context 'when user is ldap_blocked' do + before do + user.ldap_block + end + + it 'rechecks if LDAP is allowed when LDAP is enabled' do + stub_ldap_setting(enabled: true) + + expect(Gitlab::Auth::Ldap::Access).to receive(:allowed?) + + active_for_authentication? + end + + it 'does not check if LDAP is allowed when LDAP is not enabled' do + stub_ldap_setting(enabled: false) + + expect(Gitlab::Auth::Ldap::Access).not_to receive(:allowed?) + + active_for_authentication? + end + end + context 'based on user type' do using RSpec::Parameterized::TableSyntax diff --git a/yarn.lock b/yarn.lock index 54b1473a65b..82be75259dc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -904,20 +904,20 @@ stylelint-declaration-strict-value "1.7.7" stylelint-scss "3.18.0" -"@gitlab/svgs@1.219.0": - version "1.219.0" - resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.219.0.tgz#82735876b98bd3a46f42b4a424b45086ed48e7ac" - integrity sha512-UOy0+6A6PTbjNHLFBc70ATYztsiQfWHPORgPGnzugYJz2F7ewMr4p6R8d9avFqMNtVB5mIHSnbrsr0pp0XVMGA== +"@gitlab/svgs@1.220.0": + version "1.220.0" + resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.220.0.tgz#188bdefe86cdbf8be1faa7a92dbac31c728066c7" + integrity sha512-9QRXQG6IrQoviU86g2Y4l19yE81UyEg/iMoGetMfUdQ64NW6unLN7uNbUaO1ws1J0p7uG0dKwR6ohD7tEUPLFA== "@gitlab/tributejs@1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@gitlab/tributejs/-/tributejs-1.0.0.tgz#672befa222aeffc83e7d799b0500a7a4418e59b8" integrity sha512-nmKw1+hB6MHvlmPz63yPwVs1qQkycHwsKgxpEbzmky16Y6mL4EJMk3w1b8QlOAF/AIAzjCERPhe/R4MJiohbZw== -"@gitlab/ui@32.31.0": - version "32.31.0" - resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-32.31.0.tgz#e379f79f0797c98d67e121739add8dec8281a5d4" - integrity sha512-a/03Jgh3TJx0W1lJjsYZiAKbRQHGvomrGhzDvBpxKve2FXrYdo4G6gbwlIKJGiooB5YmZ5OIWhgnhQ8FSy15Aw== +"@gitlab/ui@32.33.0": + version "32.33.0" + resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-32.33.0.tgz#387c0c0fc515a44b8d115a1da1903e84233fbaaf" + integrity sha512-wmyfRMEQ4ZQLCR4FS7fkCY1FCNX6amPyZYYzCZTV52NMtKlgaxczB7YkY1ufdtg5ctmI2NcQNkRGbdW3Et0Riw== dependencies: "@babel/standalone" "^7.0.0" bootstrap-vue "2.20.1" |