diff options
74 files changed, 689 insertions, 167 deletions
diff --git a/app/assets/javascripts/clusters_list/components/clusters.vue b/app/assets/javascripts/clusters_list/components/clusters.vue index 7b53020fc49..c734da80f48 100644 --- a/app/assets/javascripts/clusters_list/components/clusters.vue +++ b/app/assets/javascripts/clusters_list/components/clusters.vue @@ -10,6 +10,7 @@ import { GlTable, } from '@gitlab/ui'; import AncestorNotice from './ancestor_notice.vue'; +import NodeErrorHelpText from './node_error_help_text.vue'; import tooltip from '~/vue_shared/directives/tooltip'; import { CLUSTER_TYPES, STATUSES } from '../constants'; import { __, sprintf } from '~/locale'; @@ -26,6 +27,7 @@ export default { GlSkeletonLoading, GlSprintf, GlTable, + NodeErrorHelpText, }, directives: { tooltip, @@ -231,9 +233,12 @@ export default { <gl-skeleton-loading v-else-if="loadingNodes" :lines="1" :class="contentAlignClasses" /> - <small v-else class="gl-font-sm gl-font-style-italic gl-text-gray-200">{{ - __('Unknown') - }}</small> + <NodeErrorHelpText + v-else-if="item.kubernetes_errors" + :class="contentAlignClasses" + :error-type="item.kubernetes_errors.connection_error" + :popover-id="`nodeSizeError${item.id}`" + /> </template> <template #cell(total_cpu)="{ item }"> @@ -250,6 +255,13 @@ export default { </span> <gl-skeleton-loading v-else-if="loadingNodes" :lines="1" :class="contentAlignClasses" /> + + <NodeErrorHelpText + v-else-if="item.kubernetes_errors" + :class="contentAlignClasses" + :error-type="item.kubernetes_errors.node_connection_error" + :popover-id="`nodeCpuError${item.id}`" + /> </template> <template #cell(total_memory)="{ item }"> @@ -266,6 +278,13 @@ export default { </span> <gl-skeleton-loading v-else-if="loadingNodes" :lines="1" :class="contentAlignClasses" /> + + <NodeErrorHelpText + v-else-if="item.kubernetes_errors" + :class="contentAlignClasses" + :error-type="item.kubernetes_errors.metrics_connection_error" + :popover-id="`nodeMemoryError${item.id}`" + /> </template> <template #cell(cluster_type)="{value}"> diff --git a/app/assets/javascripts/clusters_list/components/node_error_help_text.vue b/app/assets/javascripts/clusters_list/components/node_error_help_text.vue new file mode 100644 index 00000000000..1a396694bc8 --- /dev/null +++ b/app/assets/javascripts/clusters_list/components/node_error_help_text.vue @@ -0,0 +1,53 @@ +<script> +import { GlIcon, GlPopover } from '@gitlab/ui'; +import { CLUSTER_ERRORS } from '../constants'; + +export default { + components: { + GlIcon, + GlPopover, + }, + props: { + errorType: { + type: String, + required: false, + default: '', + }, + popoverId: { + type: String, + required: true, + }, + }, + computed: { + errorContent() { + return CLUSTER_ERRORS[this.errorType] || CLUSTER_ERRORS.default; + }, + }, +}; +</script> + +<template> + <div :id="popoverId"> + <span class="gl-font-style-italic"> + {{ errorContent.tableText }} + </span> + + <gl-icon name="status_warning" :size="24" class="gl-p-2" /> + + <gl-popover :container="popoverId" :target="popoverId" placement="top" triggers="hover focus"> + <template #title> + <span class="gl-display-block gl-text-left">{{ errorContent.title }}</span> + </template> + + <p class="gl-text-left">{{ errorContent.description }}</p> + + <p class="gl-text-left">{{ s__('ClusterIntegration|Troubleshooting tips:') }}</p> + + <ul class="gl-text-left"> + <li v-for="tip in errorContent.troubleshootingTips" :key="tip"> + {{ tip }} + </li> + </ul> + </gl-popover> + </div> +</template> diff --git a/app/assets/javascripts/clusters_list/constants.js b/app/assets/javascripts/clusters_list/constants.js index 3e8ef3151a6..f39678b73dc 100644 --- a/app/assets/javascripts/clusters_list/constants.js +++ b/app/assets/javascripts/clusters_list/constants.js @@ -1,4 +1,45 @@ -import { __ } from '~/locale'; +import { __, s__ } from '~/locale'; + +export const CLUSTER_ERRORS = { + default: { + tableText: s__('ClusterIntegration|Unknown Error'), + title: s__('ClusterIntegration|Unknown Error'), + description: s__( + 'ClusterIntegration|An unknown error occurred while attempting to connect to Kubernetes.', + ), + troubleshootingTips: [ + s__('ClusterIntegration|Check your cluster status'), + s__('ClusterIntegration|Make sure your API endpoint is correct'), + s__( + 'ClusterIntegration|Node calculations use the Kubernetes Metrics API. Make sure your cluster has metrics installed', + ), + ], + }, + authentication_error: { + tableText: s__('ClusterIntegration|Unable to Authenticate'), + title: s__('ClusterIntegration|Authentication Error'), + description: s__('ClusterIntegration|GitLab failed to authenticate.'), + troubleshootingTips: [ + s__('ClusterIntegration|Check your token'), + s__('ClusterIntegration|Check your CA certificate'), + ], + }, + connection_error: { + tableText: s__('ClusterIntegration|Unable to Connect'), + title: s__('ClusterIntegration|Connection Error'), + description: s__('ClusterIntegration|GitLab failed to connect to the cluster.'), + troubleshootingTips: [ + s__('ClusterIntegration|Check your cluster status'), + s__('ClusterIntegration|Make sure your API endpoint is correct'), + ], + }, + http_error: { + tableText: s__('ClusterIntegration|Unable to Connect'), + title: s__('ClusterIntegration|HTTP Error'), + description: s__('ClusterIntegration|There was an HTTP error when connecting to your cluster.'), + troubleshootingTips: [s__('ClusterIntegration|Check your cluster status')], + }, +}; export const CLUSTER_TYPES = { project_type: __('Project'), diff --git a/app/assets/javascripts/diffs/components/diff_file_header.vue b/app/assets/javascripts/diffs/components/diff_file_header.vue index fded391cc84..aab69172204 100644 --- a/app/assets/javascripts/diffs/components/diff_file_header.vue +++ b/app/assets/javascripts/diffs/components/diff_file_header.vue @@ -69,6 +69,11 @@ export default { default: false, }, }, + data() { + return { + hasDropdownOpen: false, + }; + }, computed: { ...mapGetters('diffs', ['diffHasExpandedDiscussions', 'diffHasDiscussions']), diffContentIDSelector() { @@ -179,6 +184,9 @@ export default { } } }, + setDropdownOpen(val) { + this.hasDropdownOpen = val; + }, }, }; </script> @@ -187,6 +195,7 @@ export default { <div ref="header" class="js-file-title file-title file-title-flex-parent" + :class="{ 'gl-z-dropdown-menu!': hasDropdownOpen }" @click.self="handleToggleFile" > <div class="file-header-content"> @@ -273,11 +282,14 @@ export default { v-if="!diffFile.deleted_file" :can-current-user-fork="canCurrentUserFork" :edit-path="diffFile.edit_path" + :ide-edit-path="diffFile.ide_edit_path" :can-modify-blob="diffFile.can_modify_blob" data-track-event="click_toggle_edit_button" data-track-label="diff_toggle_edit_button" data-track-property="diff_toggle_edit" @showForkMessage="showForkMessage" + @open="setDropdownOpen(true)" + @close="setDropdownOpen(false)" /> </template> diff --git a/app/assets/javascripts/diffs/components/edit_button.vue b/app/assets/javascripts/diffs/components/edit_button.vue index ff1af5569dc..a0d66ae2994 100644 --- a/app/assets/javascripts/diffs/components/edit_button.vue +++ b/app/assets/javascripts/diffs/components/edit_button.vue @@ -1,10 +1,17 @@ <script> -import { GlTooltipDirective, GlDeprecatedButton, GlIcon } from '@gitlab/ui'; +import { uniqueId } from 'lodash'; +import { + GlTooltipDirective, + GlIcon, + GlDeprecatedDropdown as GlDropdown, + GlDeprecatedDropdownItem as GlDropdownItem, +} from '@gitlab/ui'; import { __ } from '~/locale'; export default { components: { - GlDeprecatedButton, + GlDropdown, + GlDropdownItem, GlIcon, }, directives: { @@ -16,6 +23,11 @@ export default { required: false, default: '', }, + ideEditPath: { + type: String, + required: false, + default: '', + }, canCurrentUserFork: { type: Boolean, required: true, @@ -26,39 +38,62 @@ export default { default: false, }, }, + data() { + return { tooltipId: uniqueId('edit_button_tooltip_') }; + }, computed: { tooltipTitle() { if (this.isDisabled) { return __("Can't edit as source branch was deleted"); } - return __('Edit file'); + return __('Edit file in...'); }, isDisabled() { return !this.editPath; }, }, methods: { - handleEditClick(evt) { + handleShow(evt) { + // We must hide the tooltip because it is redundant and doesn't close itself + // when dropdown opens because we are still "focused". + this.$root.$emit('bv::hide::tooltip', this.tooltipId); + if (this.canCurrentUserFork && !this.canModifyBlob) { evt.preventDefault(); this.$emit('showForkMessage'); + } else { + this.$emit('open'); } }, + handleHide() { + this.$emit('close'); + }, }, }; </script> <template> - <span v-gl-tooltip.top :title="tooltipTitle"> - <gl-deprecated-button - :href="editPath" + <div v-gl-tooltip.top="{ title: tooltipTitle, id: tooltipId }" class="gl-display-flex"> + <gl-dropdown + toggle-class="rounded-0" :disabled="isDisabled" :class="{ 'cursor-not-allowed': isDisabled }" - class="rounded-0 js-edit-blob" - @click.native="handleEditClick" + right + data-testid="edit_file" + @show="handleShow" + @hide="handleHide" > - <gl-icon name="pencil" /> - </gl-deprecated-button> - </span> + <template #button-content> + <span class="gl-dropdown-toggle-text"><gl-icon name="pencil"/></span> + <gl-icon class="gl-dropdown-caret" name="chevron-down" aria-hidden="true" /> + </template> + <gl-dropdown-item v-if="editPath" :href="editPath">{{ + __('Edit in single-file editor') + }}</gl-dropdown-item> + <gl-dropdown-item v-if="ideEditPath" :href="ideEditPath">{{ + __('Edit in Web IDE') + }}</gl-dropdown-item> + </gl-dropdown> + </div> </template> diff --git a/app/assets/javascripts/releases/components/app_edit_new.vue b/app/assets/javascripts/releases/components/app_edit_new.vue index e1edf3d689d..e72599f1f7f 100644 --- a/app/assets/javascripts/releases/components/app_edit_new.vue +++ b/app/assets/javascripts/releases/components/app_edit_new.vue @@ -150,7 +150,7 @@ export default { /> </div> </gl-form-group> - <gl-form-group> + <gl-form-group data-testid="release-notes"> <label for="release-notes">{{ __('Release notes') }}</label> <div class="bordered-box pr-3 pl-3"> <markdown-field @@ -158,6 +158,7 @@ export default { :markdown-preview-path="markdownPreviewPath" :markdown-docs-path="markdownDocsPath" :add-spacing-classes="false" + :textarea-value="releaseNotes" class="gl-mt-3 gl-mb-3" > <template #textarea> diff --git a/app/assets/javascripts/snippets/components/snippet_description_edit.vue b/app/assets/javascripts/snippets/components/snippet_description_edit.vue index 737845d09b8..5e6caf27bdd 100644 --- a/app/assets/javascripts/snippets/components/snippet_description_edit.vue +++ b/app/assets/javascripts/snippets/components/snippet_description_edit.vue @@ -49,6 +49,7 @@ export default { :add-spacing-classes="false" :markdown-preview-path="markdownPreviewPath" :markdown-docs-path="markdownDocsPath" + :textarea-value="value" > <template #textarea> <textarea diff --git a/app/assets/javascripts/vue_shared/components/markdown/field.vue b/app/assets/javascripts/vue_shared/components/markdown/field.vue index a48c279d0e3..5a465b7cdd8 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/field.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/field.vue @@ -84,7 +84,37 @@ export default { required: false, default: false, }, - // This prop is used as a fallback in case if textarea.elm is undefined + + /** + * This prop is used as a fallback if the value of the textarea can't be + * retreived using `this.$slots.textarea[0]?.elm?`. + * + * This happens when the `textarea` slot is defined like this: + * + * ```html + * <markdown-field> + * <template #textarea> + * <textarea></textarea> + * </template> + * </markdown-field> + * ``` + * + * ... as opposed to this: + * + * ```html + * <markdown-field> + * <textarea slot="textarea"> + * </markdown-field> + * ``` + * + * When using `<template #textarea>` as shown above in example #1, + * it's important to **always** provide a value to this prop. + * If `textareaValue` isn't provided, this component will not + * show a preview when the "Preview" tab is clicked - it + * will always show "Nothing to preview." + * + * For more info, see https://github.com/vuejs/vue/issues/10450. + */ textareaValue: { type: String, required: false, diff --git a/app/assets/stylesheets/_page_specific_files.scss b/app/assets/stylesheets/_page_specific_files.scss index f706b615e7e..965769d28f7 100644 --- a/app/assets/stylesheets/_page_specific_files.scss +++ b/app/assets/stylesheets/_page_specific_files.scss @@ -57,9 +57,7 @@ @import './pages/sherlock'; @import './pages/status'; @import './pages/storage_quota'; -@import './pages/tags'; @import './pages/tree'; @import './pages/trials'; -@import './pages/ui_dev_kit'; @import './pages/users'; @import './pages/wiki'; diff --git a/app/assets/stylesheets/pages/tags.scss b/app/assets/stylesheets/pages/tags.scss deleted file mode 100644 index a6d30522ff7..00000000000 --- a/app/assets/stylesheets/pages/tags.scss +++ /dev/null @@ -1,3 +0,0 @@ -.tag-release-link { - color: $blue-600 !important; -} diff --git a/app/assets/stylesheets/pages/ui_dev_kit.scss b/app/assets/stylesheets/pages/ui_dev_kit.scss deleted file mode 100644 index 288da4da5c3..00000000000 --- a/app/assets/stylesheets/pages/ui_dev_kit.scss +++ /dev/null @@ -1,17 +0,0 @@ -.gitlab-ui-dev-kit { - > h2 { - margin: 35px 0 20px; - font-weight: $gl-font-weight-bold; - } - - .example { - padding: 15px; - border: 1px dashed $gray-100; - margin-bottom: 15px; - - &::before { - content: 'Example'; - color: $ui-dev-kit-example-color; - } - } -} diff --git a/app/assets/stylesheets/utilities.scss b/app/assets/stylesheets/utilities.scss index 9c666331c4f..9277269c730 100644 --- a/app/assets/stylesheets/utilities.scss +++ b/app/assets/stylesheets/utilities.scss @@ -156,3 +156,8 @@ display: none; } } + +// This utility is used to force the z-index to match that of dropdown menu's +.gl-z-dropdown-menu\! { + z-index: 300 !important; +} diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb index d71e6b4c004..7df6bef7914 100644 --- a/app/helpers/gitlab_routing_helper.rb +++ b/app/helpers/gitlab_routing_helper.rb @@ -343,6 +343,18 @@ module GitlabRoutingHelper Gitlab::UrlBuilder.wiki_page_url(wiki, page, only_path: true, **options) end + def gitlab_ide_merge_request_path(merge_request) + target_project = merge_request.target_project + source_project = merge_request.source_project + params = {} + + if target_project != source_project + params = { target_project: target_project.full_path } + end + + ide_merge_request_path(source_project.namespace, source_project, merge_request, params) + end + private def snippet_query_params(snippet, *args) diff --git a/app/serializers/cluster_entity.rb b/app/serializers/cluster_entity.rb index eea0acdc11b..9872bbf80b5 100644 --- a/app/serializers/cluster_entity.rb +++ b/app/serializers/cluster_entity.rb @@ -6,6 +6,7 @@ class ClusterEntity < Grape::Entity expose :cluster_type expose :enabled expose :environment_scope + expose :id expose :name expose :nodes expose :provider_type diff --git a/app/serializers/cluster_serializer.rb b/app/serializers/cluster_serializer.rb index 700a46040e3..f71591612a6 100644 --- a/app/serializers/cluster_serializer.rb +++ b/app/serializers/cluster_serializer.rb @@ -12,6 +12,7 @@ class ClusterSerializer < BaseSerializer :environment_scope, :gitlab_managed_apps_logs_path, :enable_advanced_logs_querying, + :id, :kubernetes_errors, :name, :nodes, diff --git a/app/serializers/diff_file_base_entity.rb b/app/serializers/diff_file_base_entity.rb index 9f27191c3c8..596f5d686da 100644 --- a/app/serializers/diff_file_base_entity.rb +++ b/app/serializers/diff_file_base_entity.rb @@ -34,7 +34,7 @@ class DiffFileBaseEntity < Grape::Entity expose :edit_path, if: -> (_, options) { options[:merge_request] } do |diff_file| merge_request = options[:merge_request] - next unless merge_request.merged? || merge_request.source_branch_exists? + next unless has_edit_path?(merge_request) target_project, target_branch = edit_project_branch_options(merge_request) @@ -43,6 +43,14 @@ class DiffFileBaseEntity < Grape::Entity project_edit_blob_path(target_project, tree_join(target_branch, diff_file.new_path), options) end + expose :ide_edit_path, if: -> (_, options) { options[:merge_request] } do |diff_file| + merge_request = options[:merge_request] + + next unless has_edit_path?(merge_request) + + gitlab_ide_merge_request_path(merge_request) + end + expose :old_path_html do |diff_file| old_path, _ = mark_inline_diffs(diff_file.old_path, diff_file.new_path) old_path @@ -125,4 +133,8 @@ class DiffFileBaseEntity < Grape::Entity [merge_request.target_project, merge_request.target_branch] end end + + def has_edit_path?(merge_request) + merge_request.merged? || merge_request.source_branch_exists? + end end diff --git a/app/services/audit_event_service.rb b/app/services/audit_event_service.rb index d7630dbdac9..9802f48ae7e 100644 --- a/app/services/audit_event_service.rb +++ b/app/services/audit_event_service.rb @@ -53,7 +53,6 @@ class AuditEventService private - attr_accessor :authentication_event attr_reader :ip_address def build_author(author) @@ -99,11 +98,11 @@ class AuditEventService end def mark_as_authentication_event! - self.authentication_event = true + @authentication_event = true end def authentication_event? - authentication_event + @authentication_event end def log_security_event_to_database diff --git a/app/views/notify/_failed_builds.html.haml b/app/views/notify/_failed_builds.html.haml index cde0ac21d6d..11cbd700258 100644 --- a/app/views/notify/_failed_builds.html.haml +++ b/app/views/notify/_failed_builds.html.haml @@ -6,7 +6,7 @@ #{'build'.pluralize(failed.size)}. %tr.table-warning %td{ style: "font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; border: 1px solid #ededed; border-bottom: 0; border-radius: 4px 4px 0 0; overflow: hidden; background-color: #fdf4f6; color: #d22852; font-size: 14px; line-height: 1.4; text-align: center; padding: 8px 16px;" } - Logs may contain sensitive data. Please consider before forwarding this email. + Failed builds %tr.section %td{ style: "font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; padding: 0 16px; border: 1px solid #ededed; border-radius: 4px; overflow: hidden; border-top: 0; border-radius: 0 0 4px 4px;" } %table.builds{ border: "0", cellpadding: "0", cellspacing: "0", style: "width: 100%; border-collapse: collapse;" } diff --git a/app/views/projects/tags/_tag.html.haml b/app/views/projects/tags/_tag.html.haml index dba9b20fcff..4f14ee4d044 100644 --- a/app/views/projects/tags/_tag.html.haml +++ b/app/views/projects/tags/_tag.html.haml @@ -24,7 +24,7 @@ .text-secondary = sprite_icon("rocket", size: 12) = _("Release") - = link_to release.name, project_releases_path(@project, anchor: release.tag), class: 'tag-release-link' + = link_to release.name, project_releases_path(@project, anchor: release.tag), class: 'gl-text-blue-600!' - if release.description.present? .md.gl-mt-3 = markdown_field(release, :description) diff --git a/changelogs/unreleased/21654-ide-button-in-mr-diff-files.yml b/changelogs/unreleased/21654-ide-button-in-mr-diff-files.yml new file mode 100644 index 00000000000..6c56b39bc0f --- /dev/null +++ b/changelogs/unreleased/21654-ide-button-in-mr-diff-files.yml @@ -0,0 +1,5 @@ +--- +title: Add Web IDE as dropdown item to diff file edit +merge_request: 42275 +author: +type: changed diff --git a/changelogs/unreleased/nfriend-fix-markdown-preview-on-new-release-page.yml b/changelogs/unreleased/nfriend-fix-markdown-preview-on-new-release-page.yml new file mode 100644 index 00000000000..b6854e68776 --- /dev/null +++ b/changelogs/unreleased/nfriend-fix-markdown-preview-on-new-release-page.yml @@ -0,0 +1,5 @@ +--- +title: Fix Markdown "Preview" tab on New/Edit Release and New Snippet pages +merge_request: 42640 +author: +type: fixed diff --git a/changelogs/unreleased/sh-fix-pipeline-notification-email-warning.yml b/changelogs/unreleased/sh-fix-pipeline-notification-email-warning.yml new file mode 100644 index 00000000000..a9f5ddcef33 --- /dev/null +++ b/changelogs/unreleased/sh-fix-pipeline-notification-email-warning.yml @@ -0,0 +1,5 @@ +--- +title: Update pipeline failed notification e-mail warning +merge_request: 42736 +author: +type: fixed diff --git a/config/routes.rb b/config/routes.rb index 9bd68bfeef6..5dbb24dcc7c 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -122,6 +122,7 @@ Rails.application.routes.draw do get 'ide' => 'ide#index' get 'ide/*vueroute' => 'ide#index', format: false + get 'ide/project/:namespace/:project/merge_requests/:id' => 'ide#index', format: false, as: :ide_merge_request draw :operations draw :jira_connect diff --git a/doc/README.md b/doc/README.md index efae2cdd3ff..52123c1db66 100644 --- a/doc/README.md +++ b/doc/README.md @@ -295,7 +295,7 @@ The following documentation relates to the DevOps **Secure** stage: | [Dependency Scanning](user/application_security/dependency_scanning/index.md) **(ULTIMATE)** | Analyze your dependencies for known vulnerabilities. | | [Dynamic Application Security Testing (DAST)](user/application_security/dast/index.md) **(ULTIMATE)** | Analyze running web applications for known vulnerabilities. | | [Group Security Dashboard](user/application_security/security_dashboard/index.md#group-security-dashboard) **(ULTIMATE)** | View vulnerabilities in all the projects in a group and its subgroups. | -| [Instance Security Dashboard](user/application_security/security_dashboard/index.md#instance-security-dashboard) **(ULTIMATE)** | View vulnerabilities in all the projects you're interested in. | +| [Instance Security Center](user/application_security/security_dashboard/index.md#instance-security-center) **(ULTIMATE)** | View vulnerabilities in all the projects you're interested in. | | [License Compliance](user/compliance/license_compliance/index.md) **(ULTIMATE)** | Search your project's dependencies for their licenses. | | [Pipeline Security](user/application_security/security_dashboard/index.md#pipeline-security) **(ULTIMATE)** | View the security reports for your project's pipelines. | | [Project Security Dashboard](user/application_security/security_dashboard/index.md#project-security-dashboard) **(ULTIMATE)** | View the latest security reports for your project. | diff --git a/doc/administration/audit_events.md b/doc/administration/audit_events.md index 099346b2b0b..7b6327838d3 100644 --- a/doc/administration/audit_events.md +++ b/doc/administration/audit_events.md @@ -183,6 +183,7 @@ the steps bellow. CAUTION: **Warning:** This feature might not be available to you. Check the **version history** note above for details. +If available, you can enable it with a [feature flag](#enable-or-disable-audit-log-export-to-csv). Export to CSV allows customers to export the current filter view of your audit log as a CSV file, diff --git a/doc/ci/img/gitlab_vault_workflow_v13_4.png b/doc/ci/img/gitlab_vault_workflow_v13_4.png Binary files differnew file mode 100644 index 00000000000..80d07362bf4 --- /dev/null +++ b/doc/ci/img/gitlab_vault_workflow_v13_4.png diff --git a/doc/ci/secrets/index.md b/doc/ci/secrets/index.md index 6d561fe00a3..09aeebcc7cc 100644 --- a/doc/ci/secrets/index.md +++ b/doc/ci/secrets/index.md @@ -17,23 +17,36 @@ Unlike CI variables, which are always presented to a job, secrets must be explic required by a job. Read [GitLab CI/CD pipeline configuration reference](../yaml/README.md#secrets) for more information about the syntax. -GitLab has selected [Vault by Hashicorp](https://www.vaultproject.io) as the +GitLab has selected [Vault by HashiCorp](https://www.vaultproject.io) as the first supported provider, and [KV-V2](https://www.vaultproject.io/docs/secrets/kv/kv-v2) as the first supported secrets engine. GitLab authenticates using Vault's -[JWT Auth method](https://www.vaultproject.io/docs/auth/jwt#jwt-authentication), using +[JSON Web Token (JWT) authentication method](https://www.vaultproject.io/docs/auth/jwt#jwt-authentication), using the [JSON Web Token](https://gitlab.com/gitlab-org/gitlab/-/issues/207125) (`CI_JOB_JWT`) introduced in GitLab 12.10. You must [configure your Vault server](#configure-your-vault-server) before you can use [use Vault secrets in a CI job](#use-vault-secrets-in-a-ci-job). +The flow for using GitLab with HashiCorp Vault +is summarized by this diagram: + +![Flow between GitLab and HashiCorp](../img/gitlab_vault_workflow_v13_4.png "How GitLab CI_JOB_JWT works with HashiCorp Vault") + +1. Configure your vault and secrets. +1. Generate your JWT and provide it to your CI job. +1. Runner contacts HashiCorp Vault and authenticates using the JWT. +1. HashiCorp Vault verifies the JWT. +1. HashiCorp Vault checks the bounded claims and attaches policies. +1. HashiCorp Vault returns the token. +1. Runner reads secrets from the HashiCoupr Vault. + NOTE: **Note:** -Read the [Authenticating and Reading Secrets With Hashicorp Vault](../examples/authenticating-with-hashicorp-vault/index.md) -tutorial for a version of this feature that is available to all +Read the [Authenticating and Reading Secrets With HashiCorp Vault](../examples/authenticating-with-hashicorp-vault/index.md) +tutorial for a version of this feature. It's available to all subscription levels, supports writing secrets to and deleting secrets from Vault, -and multiple secrets engines. +and supports multiple secrets engines. ## Configure your Vault server @@ -149,7 +162,7 @@ generated by this GitLab instance may be allowed to authenticate using this role For a full list of `CI_JOB_JWT` claims, read the [How it works](../examples/authenticating-with-hashicorp-vault/index.md#how-it-works) section of the -[Authenticating and Reading Secrets With Hashicorp Vault](../examples/authenticating-with-hashicorp-vault/index.md) tutorial. +[Authenticating and Reading Secrets With HashiCorp Vault](../examples/authenticating-with-hashicorp-vault/index.md) tutorial. You can also specify some attributes for the resulting Vault tokens, such as time-to-live, IP address range, and number of uses. The full list of options is available in diff --git a/doc/development/integrations/secure_partner_integration.md b/doc/development/integrations/secure_partner_integration.md index 830cb84e257..36a40162184 100644 --- a/doc/development/integrations/secure_partner_integration.md +++ b/doc/development/integrations/secure_partner_integration.md @@ -44,7 +44,7 @@ best place to integrate your own product and its results into GitLab. - If certain policies (such as [merge request approvals](../../user/project/merge_requests/merge_request_approvals.md)) are in place for a project, developers must resolve specific findings or get an approval from a specific list of people. -- The [security dashboard](../../user/application_security/security_dashboard/index.md#gitlab-security-dashboard) +- The [security dashboard](../../user/application_security/security_dashboard/index.md) also shows results which can developers can use to quickly see all the vulnerabilities that need to be addressed in the code. - When the developer reads the details about a vulnerability, they are diff --git a/doc/development/redis.md b/doc/development/redis.md index d205082b9c6..502bb656c22 100644 --- a/doc/development/redis.md +++ b/doc/development/redis.md @@ -96,10 +96,14 @@ requests that read the most data from the cache, we can just sort by ### The slow log +TIP: **Tip:** +There is a [video showing how to see the slow log](https://youtu.be/BBI68QuYRH8) (GitLab internal) +on GitLab.com + On GitLab.com, entries from the [Redis slow log](https://redis.io/commands/slowlog) are available in the `pubsub-redis-inf-gprd*` index with the [`redis.slowlog` -tag](https://log.gprd.gitlab.net/app/kibana#/discover?_g=(filters:!(),refreshInterval:(pause:!t,value:0),time:(from:now-1d,to:now))&_a=(columns:!(json.type,json.command,json.exec_time),filters:!(('$state':(store:appState),meta:(alias:!n,disabled:!f,index:AWSQX_Vf93rHTYrsexmk,key:json.tag,negate:!f,params:(query:redis.slowlog),type:phrase),query:(match:(json.tag:(query:redis.slowlog,type:phrase))))),index:AWSQX_Vf93rHTYrsexmk)). +tag](https://log.gprd.gitlab.net/app/kibana#/discover?_g=(filters:!(),refreshInterval:(pause:!t,value:0),time:(from:now-1d,to:now))&_a=(columns:!(json.type,json.command,json.exec_time_s),filters:!(('$state':(store:appState),meta:(alias:!n,disabled:!f,index:AWSQX_Vf93rHTYrsexmk,key:json.tag,negate:!f,params:(query:redis.slowlog),type:phrase),query:(match:(json.tag:(query:redis.slowlog,type:phrase))))),index:AWSQX_Vf93rHTYrsexmk)). This shows commands that have taken a long time and may be a performance concern. diff --git a/doc/user/application_security/security_dashboard/img/instance_security_center_settings_v13_4.png b/doc/user/application_security/security_dashboard/img/instance_security_center_settings_v13_4.png Binary files differnew file mode 100644 index 00000000000..d7d5961087c --- /dev/null +++ b/doc/user/application_security/security_dashboard/img/instance_security_center_settings_v13_4.png diff --git a/doc/user/application_security/security_dashboard/img/instance_security_dashboard_v13_4.png b/doc/user/application_security/security_dashboard/img/instance_security_dashboard_v13_4.png Binary files differindex d010adcc90c..5e52bcc650a 100644 --- a/doc/user/application_security/security_dashboard/img/instance_security_dashboard_v13_4.png +++ b/doc/user/application_security/security_dashboard/img/instance_security_dashboard_v13_4.png diff --git a/doc/user/application_security/security_dashboard/index.md b/doc/user/application_security/security_dashboard/index.md index 8c461e27e70..974131e7683 100644 --- a/doc/user/application_security/security_dashboard/index.md +++ b/doc/user/application_security/security_dashboard/index.md @@ -5,21 +5,26 @@ group: Threat Insights info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers --- -# GitLab Security Dashboard **(ULTIMATE)** +# GitLab Security Dashboard, Security Center, and Vulnerability Reports **(ULTIMATE)** -The Security Dashboard is a good place to get an overview of all the security -vulnerabilities in your groups, projects, and pipelines. +GitLab provides a comprehensive set of features for viewing and managing vulnerabilities: + +- Security dashboards: An overview of the security status in your instance, groups, and projects. +- Vulnerability reports: Detailed lists of all vulnerabilities for the instance, group, project, or + pipeline. This is where you triage and manage vulnerabilities. +- Security Center: A dedicated area for vulnerability management at the instance level. This + includes a security dashboard, vulnerability report, and settings. You can also drill down into a vulnerability and get extra information. This includes the project it comes from, any related file(s), and metadata that helps you analyze the risk it poses. You can also dismiss a vulnerability or create an issue for it. -To benefit from the Security Dashboard you must first configure one of the +To benefit from these features, you must first configure one of the [security scanners](../index.md). ## Supported reports -The Security Dashboard displays vulnerabilities detected by scanners such as: +The vulnerability report displays vulnerabilities detected by scanners such as: - [Container Scanning](../container_scanning/index.md) - [Dynamic Application Security Testing](../dast/index.md) @@ -29,7 +34,7 @@ The Security Dashboard displays vulnerabilities detected by scanners such as: ## Requirements -To use the instance, group, project, or pipeline security dashboard: +To use the security dashboards and vulnerability reports: 1. At least one project inside a group must be configured with at least one of the [supported reports](#supported-reports). @@ -112,38 +117,43 @@ Next to the timeline chart is a list of projects, grouped and sorted by the seve Projects with no vulnerability tests configured will not appear in the list. Additionally, dismissed vulnerabilities are excluded. -Navigate to the group's [Vulnerability Report](#vulnerability-list) to view the vulnerabilities found. +Navigate to the group's [vulnerability report](#vulnerability-report) to view the vulnerabilities found. + +## Instance Security Center -## Instance Security Dashboard +> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3426) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 13.4. -> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/6953) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.8. +The Security Center is where you manage vulnerabilities for your instance. It displays the +vulnerabilities present in the default branches of all the projects you configure. It includes the +following: -At the instance level, the Security Dashboard displays the vulnerabilities present in the default -branches of all the projects you configure to display on the dashboard. It includes all the -[group Security Dashboard's](#group-security-dashboard) -features. +- The [group security dashboard's](#group-security-dashboard) features. +- A [vulnerability report](#vulnerability-report). +- A dedicated settings area to configure which projects to display. ![Instance Security Dashboard with projects](img/instance_security_dashboard_v13_4.png) -You can access the Instance Security Dashboard from the menu +You can access the Instance Security Center from the menu bar at the top of the page. Under **More**, select **Security**. -![Instance Security Dashboard navigation link](img/instance_security_dashboard_link_v12_4.png) +![Instance Security Center navigation link](img/instance_security_dashboard_link_v12_4.png) -The dashboard is empty before you add projects to it. +The dashboard and vulnerability report are empty before you add projects. -![Uninitialized Instance Security Dashboard](img/instance_security_dashboard_empty_v13_4.png) +![Uninitialized Instance Security Center](img/instance_security_dashboard_empty_v13_4.png) -### Adding projects to the dashboard +### Adding projects to the Security Center -To add projects to the dashboard: +To add projects to the Security Center: 1. Click **Settings** in the left navigation bar or click the **Add projects** button. 1. Search for and add one or more projects using the **Search your projects** field. 1. Click the **Add projects** button. -After you add projects, the Security Dashboard displays the vulnerabilities found in those projects' -default branches. +![Adding projects to Instance Security Center](img/instance_security_center_settings_v13_4.png) + +After you add projects, the security dashboard and vulnerability report display the vulnerabilities +found in those projects' default branches. ## Export vulnerabilities @@ -192,14 +202,14 @@ When using [Auto DevOps](../../../topics/autodevops/index.md), use [special environment variables](../../../topics/autodevops/customize.md#environment-variables) to configure daily security scans. -## Vulnerability list +## Vulnerability report -Each dashboard's vulnerability list contains vulnerabilities from the latest scans that were merged +Each vulnerability report contains vulnerabilities from the latest scans that were merged into the default branch. ![Vulnerability Report](img/group_vulnerability_report_v13_4.png) -You can filter which vulnerabilities the Security Dashboard displays by: +You can filter which vulnerabilities the vulnerability report displays by: - Status - Severity diff --git a/doc/user/clusters/agent/index.md b/doc/user/clusters/agent/index.md index 7b745577cc4..98d76e51cc0 100644 --- a/doc/user/clusters/agent/index.md +++ b/doc/user/clusters/agent/index.md @@ -8,8 +8,6 @@ info: To determine the technical writer assigned to the Stage/Group associated w > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/223061) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.4. -## Goals - The [GitLab Kubernetes Agent](https://gitlab.com/gitlab-org/cluster-integration/gitlab-agent) is an active in-cluster component for solving GitLab and Kubernetes integration tasks in a secure and cloud native way. Features: diff --git a/doc/user/permissions.md b/doc/user/permissions.md index e2baac1a962..eeaa759b193 100644 --- a/doc/user/permissions.md +++ b/doc/user/permissions.md @@ -159,6 +159,7 @@ The following table depicts the various user permission levels in a project. | Remove fork relationship | | | | | ✓ | | Delete project | | | | | ✓ | | Archive project | | | | | ✓ | +| Export project | | | | ✓ | ✓ | | Delete issues | | | | | ✓ | | Delete pipelines | | | | | ✓ | | Delete merge request | | | | | ✓ | diff --git a/locale/gitlab.pot b/locale/gitlab.pot index d0d0606a34e..1a3525408c6 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -5374,6 +5374,9 @@ msgstr "" msgid "ClusterIntegration|An error occurred while trying to fetch zone machine types: %{error}" msgstr "" +msgid "ClusterIntegration|An unknown error occurred while attempting to connect to Kubernetes." +msgstr "" + msgid "ClusterIntegration|Any project namespaces" msgstr "" @@ -5389,6 +5392,9 @@ msgstr "" msgid "ClusterIntegration|Authenticate with Amazon Web Services" msgstr "" +msgid "ClusterIntegration|Authentication Error" +msgstr "" + msgid "ClusterIntegration|Base domain" msgstr "" @@ -5407,6 +5413,15 @@ msgstr "" msgid "ClusterIntegration|Certificate Authority bundle (PEM format)" msgstr "" +msgid "ClusterIntegration|Check your CA certificate" +msgstr "" + +msgid "ClusterIntegration|Check your cluster status" +msgstr "" + +msgid "ClusterIntegration|Check your token" +msgstr "" + msgid "ClusterIntegration|Choose the %{startLink}security group %{externalLinkIcon} %{endLink} to apply to the EKS-managed Elastic Network Interfaces that are created in your worker node subnets." msgstr "" @@ -5449,6 +5464,9 @@ msgstr "" msgid "ClusterIntegration|Connect existing cluster" msgstr "" +msgid "ClusterIntegration|Connection Error" +msgstr "" + msgid "ClusterIntegration|Copy API URL" msgstr "" @@ -5605,6 +5623,12 @@ msgstr "" msgid "ClusterIntegration|GitLab Runner connects to the repository and executes CI/CD jobs, pushing results back and deploying applications to production." msgstr "" +msgid "ClusterIntegration|GitLab failed to authenticate." +msgstr "" + +msgid "ClusterIntegration|GitLab failed to connect to the cluster." +msgstr "" + msgid "ClusterIntegration|GitLab-managed cluster" msgstr "" @@ -5626,6 +5650,9 @@ msgstr "" msgid "ClusterIntegration|Group cluster" msgstr "" +msgid "ClusterIntegration|HTTP Error" +msgstr "" + msgid "ClusterIntegration|Helm Tiller" msgstr "" @@ -5770,6 +5797,9 @@ msgstr "" msgid "ClusterIntegration|Machine type" msgstr "" +msgid "ClusterIntegration|Make sure your API endpoint is correct" +msgstr "" + msgid "ClusterIntegration|Make sure your account %{link_to_requirements} to create Kubernetes clusters" msgstr "" @@ -5821,6 +5851,9 @@ msgstr "" msgid "ClusterIntegration|No zones matched your search" msgstr "" +msgid "ClusterIntegration|Node calculations use the Kubernetes Metrics API. Make sure your cluster has metrics installed" +msgstr "" + msgid "ClusterIntegration|Number of nodes" msgstr "" @@ -6088,6 +6121,9 @@ msgstr "" msgid "ClusterIntegration|There was a problem authenticating with your cluster. Please ensure your CA Certificate and Token are valid." msgstr "" +msgid "ClusterIntegration|There was an HTTP error when connecting to your cluster." +msgstr "" + msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below" msgstr "" @@ -6115,9 +6151,21 @@ msgstr "" msgid "ClusterIntegration|To use a new project, first create one on %{docsLinkStart}Google Cloud Platform%{docsLinkEnd}." msgstr "" +msgid "ClusterIntegration|Troubleshooting tips:" +msgstr "" + +msgid "ClusterIntegration|Unable to Authenticate" +msgstr "" + +msgid "ClusterIntegration|Unable to Connect" +msgstr "" + msgid "ClusterIntegration|Uninstall %{appTitle}" msgstr "" +msgid "ClusterIntegration|Unknown Error" +msgstr "" + msgid "ClusterIntegration|Update %{appTitle}" msgstr "" @@ -9228,7 +9276,7 @@ msgstr "" msgid "Edit environment" msgstr "" -msgid "Edit file" +msgid "Edit file in..." msgstr "" msgid "Edit files in the editor and commit changes here" @@ -9243,6 +9291,12 @@ msgstr "" msgid "Edit identity for %{user_name}" msgstr "" +msgid "Edit in Web IDE" +msgstr "" + +msgid "Edit in single-file editor" +msgstr "" + msgid "Edit issues" msgstr "" diff --git a/package.json b/package.json index ca76e4cab0b..307c24c80ce 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "@babel/preset-env": "^7.10.1", "@gitlab/at.js": "1.5.5", "@gitlab/svgs": "1.164.0", - "@gitlab/ui": "21.3.1", + "@gitlab/ui": "21.4.2", "@gitlab/visual-review-tools": "1.6.1", "@rails/actioncable": "^6.0.3-1", "@sentry/browser": "^5.22.3", diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_lfs_over_http_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_lfs_over_http_spec.rb index f96b424d233..5672060a953 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_lfs_over_http_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_lfs_over_http_spec.rb @@ -3,7 +3,7 @@ module QA RSpec.describe 'Create' do describe 'Push mirror a repository over HTTP' do - it 'configures and syncs LFS objects for a (push) mirrored repository', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/414' do + it 'configures and syncs LFS objects for a (push) mirrored repository', :requires_admin, testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/414' do Runtime::Feature.enable_and_verify('push_mirror_syncs_lfs') Runtime::Browser.visit(:gitlab, Page::Main::Login) Page::Main::Login.perform(&:sign_in_using_credentials) diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb index 326647b25f7..8de739f1559 100644 --- a/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb +++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - RSpec.describe 'Verify', :docker, :runner do + RSpec.describe 'Verify', :runner do describe 'Pipeline creation and processing' do let(:executor) { "qa-runner-#{Time.now.to_i}" } let(:max_wait) { 30 } diff --git a/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb index a296d60b27c..9ce87f353d0 100644 --- a/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb +++ b/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - RSpec.describe 'Verify', :docker, :runner do + RSpec.describe 'Verify', :runner do describe 'Runner registration' do let(:executor) { "qa-runner-#{Time.now.to_i}" } let!(:runner) do diff --git a/qa/qa/specs/features/browser_ui/4_verify/testing/view_code_coverage_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/testing/view_code_coverage_spec.rb index f4edaaa84a8..5bfc88e45f2 100644 --- a/qa/qa/specs/features/browser_ui/4_verify/testing/view_code_coverage_spec.rb +++ b/qa/qa/specs/features/browser_ui/4_verify/testing/view_code_coverage_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - RSpec.describe 'Verify', :docker, :runner do + RSpec.describe 'Verify', :runner do describe 'Code coverage statistics' do let(:simplecov) { '\(\d+.\d+\%\) covered' } let(:executor) { "qa-runner-#{Time.now.to_i}" } diff --git a/qa/qa/specs/features/browser_ui/5_package/maven_repository_spec.rb b/qa/qa/specs/features/browser_ui/5_package/maven_repository_spec.rb index a617f3b3e29..4ca356c9b65 100644 --- a/qa/qa/specs/features/browser_ui/5_package/maven_repository_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/maven_repository_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - RSpec.describe 'Package', :docker, :orchestrated, :packages do + RSpec.describe 'Package', :orchestrated, :packages do describe 'Maven Repository' do include Runtime::Fixtures diff --git a/qa/qa/specs/features/browser_ui/5_package/npm_registry_spec.rb b/qa/qa/specs/features/browser_ui/5_package/npm_registry_spec.rb index e97ede35610..43c708093b3 100644 --- a/qa/qa/specs/features/browser_ui/5_package/npm_registry_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/npm_registry_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - RSpec.describe 'Package', :docker, :orchestrated, :packages do + RSpec.describe 'Package', :orchestrated, :packages do describe 'NPM registry' do include Runtime::Fixtures diff --git a/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb b/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb index 18eb52830a2..abac4f2b91d 100644 --- a/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb +++ b/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb @@ -3,7 +3,7 @@ require 'digest/sha1' module QA - RSpec.describe 'Release', :docker, :runner do + RSpec.describe 'Release', :runner do describe 'Git clone using a deploy key' do before do Flow::Login.sign_in diff --git a/qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_dependent_relationship_spec.rb b/qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_dependent_relationship_spec.rb index 47a1b3b5670..ece45d093a7 100644 --- a/qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_dependent_relationship_spec.rb +++ b/qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_dependent_relationship_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - RSpec.describe 'Release', :docker, :runner, :reliable do + RSpec.describe 'Release', :runner, :reliable do describe 'Parent-child pipelines dependent relationship' do let!(:project) do Resource::Project.fabricate_via_api! do |project| diff --git a/qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_independent_relationship_spec.rb b/qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_independent_relationship_spec.rb index 9eb81244aa4..38cee0e62ca 100644 --- a/qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_independent_relationship_spec.rb +++ b/qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_independent_relationship_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - RSpec.describe 'Release', :docker, :runner, :reliable do + RSpec.describe 'Release', :runner, :reliable do describe 'Parent-child pipelines independent relationship' do let!(:project) do Resource::Project.fabricate_via_api! do |project| diff --git a/spec/features/merge_request/maintainer_edits_fork_spec.rb b/spec/features/merge_request/maintainer_edits_fork_spec.rb index 0e65cb358da..2a3f7d62828 100644 --- a/spec/features/merge_request/maintainer_edits_fork_spec.rb +++ b/spec/features/merge_request/maintainer_edits_fork_spec.rb @@ -26,7 +26,10 @@ RSpec.describe 'a maintainer edits files on a source-branch of an MR from a fork visit project_merge_request_path(target_project, merge_request) click_link 'Changes' wait_for_requests - first('.js-file-title').find('.js-edit-blob').click + within first('.js-file-title') do + find('[data-testid="edit_file"]').click + click_link 'Edit in single-file editor' + end wait_for_requests end diff --git a/spec/features/merge_request/user_sees_diff_spec.rb b/spec/features/merge_request/user_sees_diff_spec.rb index 7a3a14e61e3..b4c06535b68 100644 --- a/spec/features/merge_request/user_sees_diff_spec.rb +++ b/spec/features/merge_request/user_sees_diff_spec.rb @@ -63,7 +63,7 @@ RSpec.describe 'Merge request > User sees diff', :js do visit diffs_project_merge_request_path(project, merge_request) # Throws `Capybara::Poltergeist::InvalidSelector` if we try to use `#hash` syntax - expect(page).to have_selector("[id=\"#{changelog_id}\"] a.js-edit-blob") + expect(page).to have_selector("[id=\"#{changelog_id}\"] [data-testid='edit_file']") end end @@ -73,7 +73,7 @@ RSpec.describe 'Merge request > User sees diff', :js do visit diffs_project_merge_request_path(project, merge_request) # Throws `Capybara::Poltergeist::InvalidSelector` if we try to use `#hash` syntax - find("[id=\"#{changelog_id}\"] .js-edit-blob").click + find("[id=\"#{changelog_id}\"] [data-testid=\"edit_file\"").click expect(page).to have_selector('.js-fork-suggestion-button', count: 1) expect(page).to have_selector('.js-cancel-fork-suggestion-button', count: 1) diff --git a/spec/features/projects/blobs/edit_spec.rb b/spec/features/projects/blobs/edit_spec.rb index 5aca994f53e..3949c70e718 100644 --- a/spec/features/projects/blobs/edit_spec.rb +++ b/spec/features/projects/blobs/edit_spec.rb @@ -23,6 +23,19 @@ RSpec.describe 'Editing file blob', :js do def edit_and_commit(commit_changes: true) wait_for_requests find('.js-edit-blob').click + + fill_and_commit(commit_changes) + end + + def mr_edit_and_commit(commit_changes: true) + wait_for_requests + find('[data-testid="edit_file"]').click + click_link 'Edit in single-file editor' + + fill_and_commit(commit_changes) + end + + def fill_and_commit(commit_changes) fill_editor(content: 'class NextFeature\\nend\\n') if commit_changes @@ -38,7 +51,7 @@ RSpec.describe 'Editing file blob', :js do context 'from MR diff' do before do visit diffs_project_merge_request_path(project, merge_request) - edit_and_commit + mr_edit_and_commit end it 'returns me to the mr' do diff --git a/spec/features/projects/releases/user_creates_release_spec.rb b/spec/features/projects/releases/user_creates_release_spec.rb index 5d05a7e4c91..dd0d7338a26 100644 --- a/spec/features/projects/releases/user_creates_release_spec.rb +++ b/spec/features/projects/releases/user_creates_release_spec.rb @@ -108,6 +108,24 @@ RSpec.describe 'User creates release', :js do end end + context 'when the release notes "Preview" tab is clicked' do + before do + find_field('Release notes').click + + fill_release_notes('**some** _markdown_ [content](https://example.com)') + + click_on 'Preview' + + wait_for_all_requests + end + + it 'renders a preview of the release notes markdown' do + within('[data-testid="release-notes"]') do + expect(page).to have_text('some markdown content') + end + end + end + def fill_out_form_and_submit fill_tag_name(tag_name) diff --git a/spec/frontend/alert_settings/__snapshots__/alert_settings_form_spec.js.snap b/spec/frontend/alert_settings/__snapshots__/alert_settings_form_spec.js.snap index 16e92bf505a..a65d1eae2e3 100644 --- a/spec/frontend/alert_settings/__snapshots__/alert_settings_form_spec.js.snap +++ b/spec/frontend/alert_settings/__snapshots__/alert_settings_form_spec.js.snap @@ -26,7 +26,7 @@ exports[`AlertsSettingsForm with default values renders the initial template 1`] </gl-form-group-stub> <gl-form-group-stub label=\\"Authorization key\\" label-for=\\"authorization-key\\" label-class=\\"label-bold\\"> <gl-form-input-group-stub value=\\"abcedfg123\\" predefinedoptions=\\"[object Object]\\" id=\\"authorization-key\\" readonly=\\"\\" class=\\"gl-mb-2\\"></gl-form-input-group-stub> - <gl-button-stub category=\\"primary\\" variant=\\"default\\" size=\\"medium\\" icon=\\"\\" disabled=\\"true\\" class=\\"gl-mt-3\\" role=\\"button\\" tabindex=\\"0\\">Reset key</gl-button-stub> + <gl-button-stub category=\\"primary\\" variant=\\"default\\" size=\\"medium\\" icon=\\"\\" buttontextclasses=\\"\\" disabled=\\"true\\" class=\\"gl-mt-3\\" role=\\"button\\" tabindex=\\"0\\">Reset key</gl-button-stub> <gl-modal-stub modalid=\\"authKeyModal\\" titletag=\\"h4\\" modalclass=\\"\\" size=\\"md\\" title=\\"Reset key\\" ok-title=\\"Reset key\\" ok-variant=\\"danger\\"> Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in. </gl-modal-stub> @@ -35,13 +35,13 @@ exports[`AlertsSettingsForm with default values renders the initial template 1`] <gl-form-textarea-stub noresize=\\"true\\" id=\\"alert-json\\" disabled=\\"true\\" state=\\"true\\" placeholder=\\"Enter test alert JSON....\\" rows=\\"6\\" max-rows=\\"10\\"></gl-form-textarea-stub> </gl-form-group-stub> <div class=\\"gl-display-flex gl-justify-content-end\\"> - <gl-button-stub category=\\"primary\\" variant=\\"default\\" size=\\"medium\\" icon=\\"\\" disabled=\\"true\\">Test alert payload</gl-button-stub> + <gl-button-stub category=\\"primary\\" variant=\\"default\\" size=\\"medium\\" icon=\\"\\" buttontextclasses=\\"\\" disabled=\\"true\\">Test alert payload</gl-button-stub> </div> <div class=\\"footer-block row-content-block gl-display-flex gl-justify-content-space-between\\"> - <gl-button-stub category=\\"primary\\" variant=\\"default\\" size=\\"medium\\" icon=\\"\\" disabled=\\"true\\"> + <gl-button-stub category=\\"primary\\" variant=\\"default\\" size=\\"medium\\" icon=\\"\\" buttontextclasses=\\"\\" disabled=\\"true\\"> Cancel </gl-button-stub> - <gl-button-stub category=\\"primary\\" variant=\\"success\\" size=\\"medium\\" icon=\\"\\" disabled=\\"true\\"> + <gl-button-stub category=\\"primary\\" variant=\\"success\\" size=\\"medium\\" icon=\\"\\" buttontextclasses=\\"\\" disabled=\\"true\\"> Save changes </gl-button-stub> </div> diff --git a/spec/frontend/clusters_list/components/clusters_spec.js b/spec/frontend/clusters_list/components/clusters_spec.js index 628c35ae839..34d99473eb7 100644 --- a/spec/frontend/clusters_list/components/clusters_spec.js +++ b/spec/frontend/clusters_list/components/clusters_spec.js @@ -164,18 +164,18 @@ describe('Clusters', () => { }); it.each` - nodeSize | lineNumber - ${'Unknown'} | ${0} - ${'1'} | ${1} - ${'2'} | ${2} - ${'1'} | ${3} - ${'1'} | ${4} - ${'Unknown'} | ${5} - `('renders node size for each cluster', ({ nodeSize, lineNumber }) => { + nodeText | lineNumber + ${'Unable to Authenticate'} | ${0} + ${'1'} | ${1} + ${'2'} | ${2} + ${'1'} | ${3} + ${'1'} | ${4} + ${'Unknown Error'} | ${5} + `('renders node size for each cluster', ({ nodeText, lineNumber }) => { const sizes = findTable().findAll('td:nth-child(3)'); const size = sizes.at(lineNumber); - expect(size.text()).toBe(nodeSize); + expect(size.text()).toContain(nodeText); expect(size.find(GlSkeletonLoading).exists()).toBe(false); }); }); diff --git a/spec/frontend/clusters_list/components/node_error_help_text_spec.js b/spec/frontend/clusters_list/components/node_error_help_text_spec.js new file mode 100644 index 00000000000..4d157b3a8ab --- /dev/null +++ b/spec/frontend/clusters_list/components/node_error_help_text_spec.js @@ -0,0 +1,33 @@ +import { shallowMount } from '@vue/test-utils'; +import { GlPopover } from '@gitlab/ui'; +import NodeErrorHelpText from '~/clusters_list/components/node_error_help_text.vue'; + +describe('NodeErrorHelpText', () => { + let wrapper; + + const createWrapper = propsData => { + wrapper = shallowMount(NodeErrorHelpText, { propsData, stubs: { GlPopover } }); + return wrapper.vm.$nextTick(); + }; + + const findPopover = () => wrapper.find(GlPopover); + + afterEach(() => { + wrapper.destroy(); + }); + + it.each` + errorType | wrapperText | popoverText + ${'authentication_error'} | ${'Unable to Authenticate'} | ${'GitLab failed to authenticate'} + ${'connection_error'} | ${'Unable to Connect'} | ${'GitLab failed to connect to the cluster'} + ${'http_error'} | ${'Unable to Connect'} | ${'There was an HTTP error when connecting to your cluster'} + ${'default'} | ${'Unknown Error'} | ${'An unknown error occurred while attempting to connect to Kubernetes.'} + ${'unknown_error_type'} | ${'Unknown Error'} | ${'An unknown error occurred while attempting to connect to Kubernetes.'} + ${null} | ${'Unknown Error'} | ${'An unknown error occurred while attempting to connect to Kubernetes.'} + `('displays error text', ({ errorType, wrapperText, popoverText }) => { + return createWrapper({ errorType, popoverId: 'id' }).then(() => { + expect(wrapper.text()).toContain(wrapperText); + expect(findPopover().text()).toContain(popoverText); + }); + }); +}); diff --git a/spec/frontend/clusters_list/mock_data.js b/spec/frontend/clusters_list/mock_data.js index 48af3b91c94..ed32655d10e 100644 --- a/spec/frontend/clusters_list/mock_data.js +++ b/spec/frontend/clusters_list/mock_data.js @@ -6,6 +6,11 @@ export const clusterList = [ provider_type: 'gcp', status: 'creating', nodes: null, + kubernetes_errors: { + connection_error: 'authentication_error', + node_connection_error: 'connection_error', + metrics_connection_error: 'http_error', + }, }, { name: 'My Cluster 2', @@ -19,6 +24,7 @@ export const clusterList = [ usage: { cpu: '246155922n', memory: '1255212Ki' }, }, ], + kubernetes_errors: {}, }, { name: 'My Cluster 3', @@ -36,6 +42,7 @@ export const clusterList = [ usage: { cpu: '307051934n', memory: '1379136Ki' }, }, ], + kubernetes_errors: {}, }, { name: 'My Cluster 4', @@ -48,6 +55,7 @@ export const clusterList = [ usage: { cpu: '1missingCpuUnit', memory: '1missingMemoryUnit' }, }, ], + kubernetes_errors: {}, }, { name: 'My Cluster 5', @@ -59,12 +67,14 @@ export const clusterList = [ status: { allocatable: { cpu: '1missingCpuUnit', memory: '1missingMemoryUnit' } }, }, ], + kubernetes_errors: {}, }, { name: 'My Cluster 6', environment_scope: '*', cluster_type: 'project_type', status: 'cleanup_ongoing', + kubernetes_errors: {}, }, ]; diff --git a/spec/frontend/code_navigation/components/__snapshots__/popover_spec.js.snap b/spec/frontend/code_navigation/components/__snapshots__/popover_spec.js.snap index 745a163951a..62b751ec59b 100644 --- a/spec/frontend/code_navigation/components/__snapshots__/popover_spec.js.snap +++ b/spec/frontend/code_navigation/components/__snapshots__/popover_spec.js.snap @@ -56,6 +56,7 @@ exports[`Code navigation popover component renders popover 1`] = ` class="popover-body border-top" > <gl-button-stub + buttontextclasses="" category="primary" class="w-100" data-testid="go-to-definition-btn" diff --git a/spec/frontend/design_management/components/toolbar/__snapshots__/design_navigation_spec.js.snap b/spec/frontend/design_management/components/toolbar/__snapshots__/design_navigation_spec.js.snap index a7d6145285c..f6a941fc99c 100644 --- a/spec/frontend/design_management/components/toolbar/__snapshots__/design_navigation_spec.js.snap +++ b/spec/frontend/design_management/components/toolbar/__snapshots__/design_navigation_spec.js.snap @@ -13,6 +13,7 @@ exports[`Design management pagination component renders navigation buttons 1`] = class="ml-3 mr-3" > <gl-button-stub + buttontextclasses="" category="primary" class="js-previous-design" disabled="true" @@ -23,6 +24,7 @@ exports[`Design management pagination component renders navigation buttons 1`] = /> <gl-button-stub + buttontextclasses="" category="primary" class="js-next-design" icon="angle-right" diff --git a/spec/frontend/design_management/components/toolbar/__snapshots__/index_spec.js.snap b/spec/frontend/design_management/components/toolbar/__snapshots__/index_spec.js.snap index b286a74ebb8..adf713d0811 100644 --- a/spec/frontend/design_management/components/toolbar/__snapshots__/index_spec.js.snap +++ b/spec/frontend/design_management/components/toolbar/__snapshots__/index_spec.js.snap @@ -41,6 +41,7 @@ exports[`Design management toolbar component renders design and updated data 1`] /> <gl-button-stub + buttontextclasses="" category="primary" href="/-/designs/306/7f747adcd4693afadbe968d7ba7d983349b9012d" icon="download" diff --git a/spec/frontend/design_management/components/upload/__snapshots__/button_spec.js.snap b/spec/frontend/design_management/components/upload/__snapshots__/button_spec.js.snap index 3d7939df28e..eaa7460ae15 100644 --- a/spec/frontend/design_management/components/upload/__snapshots__/button_spec.js.snap +++ b/spec/frontend/design_management/components/upload/__snapshots__/button_spec.js.snap @@ -5,6 +5,7 @@ exports[`Design management upload button component renders inverted upload desig isinverted="true" > <gl-button-stub + buttontextclasses="" category="primary" icon="" size="small" @@ -30,6 +31,7 @@ exports[`Design management upload button component renders inverted upload desig exports[`Design management upload button component renders loading icon 1`] = ` <div> <gl-button-stub + buttontextclasses="" category="primary" disabled="true" icon="" @@ -62,6 +64,7 @@ exports[`Design management upload button component renders loading icon 1`] = ` exports[`Design management upload button component renders upload design button 1`] = ` <div> <gl-button-stub + buttontextclasses="" category="primary" icon="" size="small" diff --git a/spec/frontend/design_management/pages/__snapshots__/index_spec.js.snap b/spec/frontend/design_management/pages/__snapshots__/index_spec.js.snap index b80b7fdb43e..07ba6c7309e 100644 --- a/spec/frontend/design_management/pages/__snapshots__/index_spec.js.snap +++ b/spec/frontend/design_management/pages/__snapshots__/index_spec.js.snap @@ -110,6 +110,7 @@ exports[`Design management index page designs renders designs list and header wi class="qa-selector-toolbar gl-display-flex gl-align-items-center" > <gl-button-stub + buttontextclasses="" category="primary" class="gl-mr-4 js-select-all" icon="" diff --git a/spec/frontend/design_management/pages/design/__snapshots__/index_spec.js.snap b/spec/frontend/design_management/pages/design/__snapshots__/index_spec.js.snap index c849e4d4ed6..8546f9fbf51 100644 --- a/spec/frontend/design_management/pages/design/__snapshots__/index_spec.js.snap +++ b/spec/frontend/design_management/pages/design/__snapshots__/index_spec.js.snap @@ -67,6 +67,7 @@ exports[`Design management design index page renders design index 1`] = ` /> <gl-button-stub + buttontextclasses="" category="primary" class="link-inherit-color gl-text-body gl-text-decoration-none gl-font-weight-bold gl-mb-4" data-testid="resolved-comments" diff --git a/spec/frontend/diffs/components/diff_file_header_spec.js b/spec/frontend/diffs/components/diff_file_header_spec.js index a0cad32b9fb..817cb8dbf0c 100644 --- a/spec/frontend/diffs/components/diff_file_header_spec.js +++ b/spec/frontend/diffs/components/diff_file_header_spec.js @@ -76,6 +76,7 @@ describe('DiffFileHeader component', () => { const findReplacedFileButton = () => wrapper.find({ ref: 'replacedFileButton' }); const findViewFileButton = () => wrapper.find({ ref: 'viewButton' }); const findCollapseIcon = () => wrapper.find({ ref: 'collapseIcon' }); + const hasZDropdownMenuClass = () => wrapper.classes('gl-z-dropdown-menu!'); const findIconByName = iconName => { const icons = wrapper.findAll(GlIcon).filter(w => w.props('name') === iconName); @@ -151,6 +152,10 @@ describe('DiffFileHeader component', () => { expect(wrapper.find(ClipboardButton).exists()).toBe(true); }); + it('should not have z dropdown menu class', () => { + expect(hasZDropdownMenuClass()).toBe(false); + }); + describe('for submodule', () => { const submoduleDiffFile = { ...diffFile, @@ -303,6 +308,27 @@ describe('DiffFileHeader component', () => { expect(wrapper.find(EditButton).exists()).toBe(true); }); + describe('when edit button opens', () => { + beforeEach(async () => { + createComponent({ addMergeRequestButtons: true }); + wrapper.find(EditButton).vm.$emit('open'); + + await wrapper.vm.$nextTick(); + }); + + it('should add z dropdown menu class when edit button opens', async () => { + expect(hasZDropdownMenuClass()).toBe(true); + }); + + it('when closes again, should remove class', async () => { + wrapper.find(EditButton).vm.$emit('close'); + + await wrapper.vm.$nextTick(); + + expect(hasZDropdownMenuClass()).toBe(false); + }); + }); + describe('view on environment button', () => { it('is displayed when external url is provided', () => { const externalUrl = 'link://to/external'; diff --git a/spec/frontend/diffs/components/edit_button_spec.js b/spec/frontend/diffs/components/edit_button_spec.js index 71512c1c4af..cc425cc7301 100644 --- a/spec/frontend/diffs/components/edit_button_spec.js +++ b/spec/frontend/diffs/components/edit_button_spec.js @@ -1,15 +1,34 @@ -import { shallowMount } from '@vue/test-utils'; -import { GlDeprecatedButton } from '@gitlab/ui'; +import { shallowMount, mount } from '@vue/test-utils'; +import { GlDeprecatedDropdown, GlDeprecatedDropdownItem, GlIcon } from '@gitlab/ui'; +import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; import EditButton from '~/diffs/components/edit_button.vue'; -const editPath = 'test-path'; +jest.mock('lodash/uniqueId', () => (str = '') => `${str}fake`); + +const TOOLTIP_ID = 'edit_button_tooltip_fake'; +const EDIT_ITEM = { + href: 'test-path', + text: 'Edit in single-file editor', +}; +const IDE_EDIT_ITEM = { + href: 'ide-test-path', + text: 'Edit in Web IDE', +}; describe('EditButton', () => { let wrapper; - const createComponent = (props = {}) => { - wrapper = shallowMount(EditButton, { - propsData: { ...props }, + const createComponent = (props = {}, mountFn = shallowMount) => { + wrapper = mountFn(EditButton, { + propsData: { + editPath: EDIT_ITEM.href, + ideEditPath: IDE_EDIT_ITEM.href, + canCurrentUserFork: false, + ...props, + }, + directives: { + GlTooltip: createMockDirective(), + }, }); }; @@ -17,59 +36,105 @@ describe('EditButton', () => { wrapper.destroy(); }); - it('has correct href attribute', () => { - createComponent({ - editPath, - canCurrentUserFork: false, - }); + const getTooltip = () => getBinding(wrapper.element, 'gl-tooltip').value; + const findDropdown = () => wrapper.find(GlDeprecatedDropdown); + const parseDropdownItems = () => + wrapper.findAll(GlDeprecatedDropdownItem).wrappers.map(x => ({ + text: x.text(), + href: x.attributes('href'), + })); + const triggerShow = () => { + const event = new Event(''); + jest.spyOn(event, 'preventDefault'); + + findDropdown().vm.$emit('show', event); + + return event; + }; + + it.each` + props | expectedItems + ${{}} | ${[EDIT_ITEM, IDE_EDIT_ITEM]} + ${{ editPath: '' }} | ${[IDE_EDIT_ITEM]} + ${{ ideEditPath: '' }} | ${[EDIT_ITEM]} + `('should render items with=$props', ({ props, expectedItems }) => { + createComponent(props); - expect(wrapper.find(GlDeprecatedButton).attributes('href')).toBe(editPath); + expect(parseDropdownItems()).toEqual(expectedItems); }); - it('emits a show fork message event if current user can fork', () => { - createComponent({ - editPath, - canCurrentUserFork: true, + describe('with default', () => { + beforeEach(() => { + createComponent({}, mount); }); - wrapper.find(GlDeprecatedButton).trigger('click'); - return wrapper.vm.$nextTick().then(() => { - expect(wrapper.emitted('showForkMessage')).toBeTruthy(); + it('does not have tooltip', () => { + expect(getTooltip()).toEqual({ id: TOOLTIP_ID, title: 'Edit file in...' }); }); - }); - it('doesnt emit a show fork message event if current user cannot fork', () => { - createComponent({ - editPath, - canCurrentUserFork: false, + it('shows pencil dropdown', () => { + expect(wrapper.find(GlIcon).props('name')).toBe('pencil'); + expect(wrapper.find('.gl-dropdown-caret').exists()).toBe(true); }); - wrapper.find(GlDeprecatedButton).trigger('click'); - return wrapper.vm.$nextTick().then(() => { - expect(wrapper.emitted('showForkMessage')).toBeFalsy(); + describe.each` + event | expectedEmit | expectedRootEmit + ${'show'} | ${'open'} | ${[['bv::hide::tooltip', TOOLTIP_ID]]} + ${'hide'} | ${'close'} | ${[]} + `('when dropdown emits $event', ({ event, expectedEmit, expectedRootEmit }) => { + let rootEmitSpy; + + beforeEach(() => { + rootEmitSpy = jest.spyOn(wrapper.vm.$root, '$emit'); + + findDropdown().vm.$emit(event); + }); + + it(`emits ${expectedEmit}`, () => { + expect(wrapper.emitted(expectedEmit)).toEqual([[]]); + }); + + it(`emits root = ${JSON.stringify(expectedRootEmit)}`, () => { + expect(rootEmitSpy.mock.calls).toEqual(expectedRootEmit); + }); }); }); - it('doesnt emit a show fork message event if current user can modify blob', () => { - createComponent({ - editPath, - canCurrentUserFork: true, - canModifyBlob: true, + describe('with cant modify blob and can fork', () => { + beforeEach(() => { + createComponent({ + canModifyBlob: false, + canCurrentUserFork: true, + }); }); - wrapper.find(GlDeprecatedButton).trigger('click'); - return wrapper.vm.$nextTick().then(() => { - expect(wrapper.emitted('showForkMessage')).toBeFalsy(); + it('when try to open, emits showForkMessage', () => { + expect(wrapper.emitted('showForkMessage')).toBeUndefined(); + + const event = triggerShow(); + + expect(wrapper.emitted('showForkMessage')).toEqual([[]]); + expect(event.preventDefault).toHaveBeenCalled(); + expect(wrapper.emitted('open')).toBeUndefined(); }); }); - it('disables button if editPath is empty', () => { - createComponent({ - editPath: '', - canCurrentUserFork: true, - canModifyBlob: true, + describe('with editPath is falsey', () => { + beforeEach(() => { + createComponent({ + editPath: '', + }); + }); + + it('should disable dropdown', () => { + expect(findDropdown().attributes('disabled')).toBe('true'); }); - expect(wrapper.find(GlDeprecatedButton).attributes('disabled')).toBe('true'); + it('should have tooltip', () => { + expect(getTooltip()).toEqual({ + id: TOOLTIP_ID, + title: "Can't edit as source branch was deleted", + }); + }); }); }); diff --git a/spec/frontend/grafana_integration/components/__snapshots__/grafana_integration_spec.js.snap b/spec/frontend/grafana_integration/components/__snapshots__/grafana_integration_spec.js.snap index 0befe1aa192..dd889e2ab6f 100644 --- a/spec/frontend/grafana_integration/components/__snapshots__/grafana_integration_spec.js.snap +++ b/spec/frontend/grafana_integration/components/__snapshots__/grafana_integration_spec.js.snap @@ -17,6 +17,7 @@ exports[`grafana integration component default state to match the default snapsh </h3> <gl-button-stub + buttontextclasses="" category="primary" class="js-settings-toggle" icon="" @@ -96,6 +97,7 @@ exports[`grafana integration component default state to match the default snapsh class="gl-display-flex gl-justify-content-end" > <gl-button-stub + buttontextclasses="" category="primary" icon="" size="medium" diff --git a/spec/frontend/incidents_settings/components/__snapshots__/alerts_form_spec.js.snap b/spec/frontend/incidents_settings/components/__snapshots__/alerts_form_spec.js.snap index cab2165b5db..cd8a3d7a381 100644 --- a/spec/frontend/incidents_settings/components/__snapshots__/alerts_form_spec.js.snap +++ b/spec/frontend/incidents_settings/components/__snapshots__/alerts_form_spec.js.snap @@ -97,6 +97,7 @@ exports[`Alert integration settings form default state should match the default class="gl-display-flex gl-justify-content-end" > <gl-button-stub + buttontextclasses="" category="primary" class="js-no-auto-disable" data-qa-selector="save_changes_button" diff --git a/spec/frontend/incidents_settings/components/__snapshots__/incidents_settings_tabs_spec.js.snap b/spec/frontend/incidents_settings/components/__snapshots__/incidents_settings_tabs_spec.js.snap index 3ad4c13382d..53c3e131466 100644 --- a/spec/frontend/incidents_settings/components/__snapshots__/incidents_settings_tabs_spec.js.snap +++ b/spec/frontend/incidents_settings/components/__snapshots__/incidents_settings_tabs_spec.js.snap @@ -18,6 +18,7 @@ exports[`IncidentsSettingTabs should render the component 1`] = ` </h4> <gl-button-stub + buttontextclasses="" category="primary" class="js-settings-toggle" icon="" diff --git a/spec/frontend/incidents_settings/components/__snapshots__/pagerduty_form_spec.js.snap b/spec/frontend/incidents_settings/components/__snapshots__/pagerduty_form_spec.js.snap index 78bb238fcb6..a6c910f9023 100644 --- a/spec/frontend/incidents_settings/components/__snapshots__/pagerduty_form_spec.js.snap +++ b/spec/frontend/incidents_settings/components/__snapshots__/pagerduty_form_spec.js.snap @@ -46,6 +46,7 @@ exports[`Alert integration settings form should match the default snapshot 1`] = class="gl-display-flex gl-justify-content-end" > <gl-button-stub + buttontextclasses="" category="primary" class="gl-mt-3" data-testid="webhook-reset-btn" @@ -80,6 +81,7 @@ exports[`Alert integration settings form should match the default snapshot 1`] = class="gl-display-flex gl-justify-content-end" > <gl-button-stub + buttontextclasses="" category="primary" class="js-no-auto-disable" icon="" diff --git a/spec/frontend/packages/shared/components/__snapshots__/package_list_row_spec.js.snap b/spec/frontend/packages/shared/components/__snapshots__/package_list_row_spec.js.snap index 6aaefed92d0..cf5451490eb 100644 --- a/spec/frontend/packages/shared/components/__snapshots__/package_list_row_spec.js.snap +++ b/spec/frontend/packages/shared/components/__snapshots__/package_list_row_spec.js.snap @@ -118,6 +118,7 @@ exports[`packages_list_row renders 1`] = ` > <gl-button-stub aria-label="Remove package" + buttontextclasses="" category="primary" data-testid="action-delete" icon="remove" diff --git a/spec/frontend/pages/admin/users/components/__snapshots__/delete_user_modal_spec.js.snap b/spec/frontend/pages/admin/users/components/__snapshots__/delete_user_modal_spec.js.snap index 2fbc700d4f5..ddeaa2a79db 100644 --- a/spec/frontend/pages/admin/users/components/__snapshots__/delete_user_modal_spec.js.snap +++ b/spec/frontend/pages/admin/users/components/__snapshots__/delete_user_modal_spec.js.snap @@ -39,6 +39,7 @@ exports[`User Operation confirmation modal renders modal with form included 1`] /> </form> <gl-button-stub + buttontextclasses="" category="primary" icon="" size="medium" @@ -48,6 +49,7 @@ exports[`User Operation confirmation modal renders modal with form included 1`] </gl-button-stub> <gl-button-stub + buttontextclasses="" category="primary" disabled="true" icon="" @@ -60,6 +62,7 @@ exports[`User Operation confirmation modal renders modal with form included 1`] </gl-button-stub> <gl-button-stub + buttontextclasses="" category="primary" disabled="true" icon="" 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 455467e7b29..a0fd6012546 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 @@ -17,6 +17,7 @@ exports[`Project remove modal initialized matches the snapshot 1`] = ` /> <gl-button-stub + buttontextclasses="" category="primary" icon="" role="button" 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 692b8f6cf52..4630415f61c 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 @@ -18,6 +18,7 @@ exports[`Project remove modal intialized matches the snapshot 1`] = ` /> <gl-button-stub + buttontextclasses="" category="primary" icon="" role="button" @@ -84,6 +85,7 @@ exports[`Project remove modal intialized matches the snapshot 1`] = ` <template> <gl-button-stub + buttontextclasses="" category="primary" class="js-modal-action-cancel" icon="" @@ -98,6 +100,7 @@ exports[`Project remove modal intialized matches the snapshot 1`] = ` <!----> <gl-button-stub + buttontextclasses="" category="primary" class="js-modal-action-primary" disabled="true" diff --git a/spec/frontend/serverless/components/__snapshots__/empty_state_spec.js.snap b/spec/frontend/serverless/components/__snapshots__/empty_state_spec.js.snap index 22689080063..6b3d65ff037 100644 --- a/spec/frontend/serverless/components/__snapshots__/empty_state_spec.js.snap +++ b/spec/frontend/serverless/components/__snapshots__/empty_state_spec.js.snap @@ -11,7 +11,7 @@ exports[`EmptyStateComponent should render content 1`] = ` <p>In order to start using functions as a service, you must first install Knative on your Kubernetes cluster. <gl-link-stub href=\\"/help\\">More information</gl-link-stub> </p> <div> - <gl-button-stub category=\\"primary\\" variant=\\"success\\" size=\\"medium\\" icon=\\"\\" href=\\"/clusters\\">Install Knative</gl-button-stub> + <gl-button-stub category=\\"primary\\" variant=\\"success\\" size=\\"medium\\" icon=\\"\\" buttontextclasses=\\"\\" href=\\"/clusters\\">Install Knative</gl-button-stub> <!----> </div> </div> diff --git a/spec/frontend/vue_shared/components/__snapshots__/clone_dropdown_spec.js.snap b/spec/frontend/vue_shared/components/__snapshots__/clone_dropdown_spec.js.snap index dfd114a2d1c..ec4a81054db 100644 --- a/spec/frontend/vue_shared/components/__snapshots__/clone_dropdown_spec.js.snap +++ b/spec/frontend/vue_shared/components/__snapshots__/clone_dropdown_spec.js.snap @@ -39,6 +39,7 @@ exports[`Clone Dropdown Button rendering matches the snapshot 1`] = ` tag="div" > <gl-button-stub + buttontextclasses="" category="primary" class="d-inline-flex" data-clipboard-text="ssh://foo.bar" @@ -80,6 +81,7 @@ exports[`Clone Dropdown Button rendering matches the snapshot 1`] = ` tag="div" > <gl-button-stub + buttontextclasses="" category="primary" class="d-inline-flex" data-clipboard-text="http://foo.bar" diff --git a/spec/serializers/cluster_serializer_spec.rb b/spec/serializers/cluster_serializer_spec.rb index 04999975276..e65e97b6ae0 100644 --- a/spec/serializers/cluster_serializer_spec.rb +++ b/spec/serializers/cluster_serializer_spec.rb @@ -13,6 +13,7 @@ RSpec.describe ClusterSerializer do :cluster_type, :enabled, :environment_scope, + :id, :gitlab_managed_apps_logs_path, :enable_advanced_logs_querying, :kubernetes_errors, diff --git a/spec/serializers/diff_file_base_entity_spec.rb b/spec/serializers/diff_file_base_entity_spec.rb index 94c39e11790..99dbaff4b7e 100644 --- a/spec/serializers/diff_file_base_entity_spec.rb +++ b/spec/serializers/diff_file_base_entity_spec.rb @@ -3,10 +3,24 @@ require 'spec_helper' RSpec.describe DiffFileBaseEntity do - let(:project) { create(:project, :repository) } + include ProjectForksHelper + + let_it_be(:project) { create(:project, :repository) } + let_it_be(:user) { create(:user) } + let(:repository) { project.repository } let(:entity) { described_class.new(diff_file, options).as_json } + shared_examples 'nil if removed source branch' do |key| + before do + allow(merge_request).to receive(:source_branch_exists?).and_return(false) + end + + specify do + expect(entity[key]).to eq(nil) + end + end + context 'submodule information for a' do let(:commit_sha) { "" } let(:commit) { project.commit(commit_sha) } @@ -67,7 +81,7 @@ RSpec.describe DiffFileBaseEntity do context 'edit_path' do let(:diff_file) { merge_request.diffs.diff_files.to_a.last } - let(:options) { { request: EntityRequest.new(current_user: create(:user)), merge_request: merge_request } } + let(:options) { { request: EntityRequest.new(current_user: user), merge_request: merge_request } } let(:params) { {} } shared_examples 'a diff file edit path to the source branch' do @@ -81,16 +95,7 @@ RSpec.describe DiffFileBaseEntity do let(:params) { { from_merge_request_iid: merge_request.iid } } it_behaves_like 'a diff file edit path to the source branch' - - context 'removed source branch' do - before do - allow(merge_request).to receive(:source_branch_exists?).and_return(false) - end - - it do - expect(entity[:edit_path]).to eq(nil) - end - end + it_behaves_like 'nil if removed source branch', :edit_path end context 'closed' do @@ -118,4 +123,30 @@ RSpec.describe DiffFileBaseEntity do end end end + + context 'ide_edit_path' do + let(:source_project) { project } + let(:merge_request) { create(:merge_request, iid: 123, target_project: target_project, source_project: source_project) } + let(:diff_file) { merge_request.diffs.diff_files.to_a.last } + let(:options) { { request: EntityRequest.new(current_user: user), merge_request: merge_request } } + let(:expected_merge_request_path) { "/-/ide/project/#{source_project.full_path}/merge_requests/#{merge_request.iid}" } + + context 'when source_project and target_project are the same' do + let(:target_project) { source_project } + + it_behaves_like 'nil if removed source branch', :ide_edit_path + + it 'returns the merge_request ide route' do + expect(entity[:ide_edit_path]).to eq expected_merge_request_path + end + end + + context 'when source_project and target_project are different' do + let(:target_project) { fork_project(source_project, source_project.owner, repository: true) } + + it 'returns the merge_request ide route with the target_project as param' do + expect(entity[:ide_edit_path]).to eq("#{expected_merge_request_path}?target_project=#{ERB::Util.url_encode(target_project.full_path)}") + end + end + end end diff --git a/yarn.lock b/yarn.lock index 9bb1756daa6..d9967c03d4e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -848,10 +848,10 @@ resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.164.0.tgz#6cefad871c45f945ef92b99015d0f510b1d2de4a" integrity sha512-a9e/cYUc1QQk7azjH4x/m6/p3icavwGEi5F9ipNlDqiJtUor5tqojxvMxPOhuVbN/mTwnC6lGsSZg4tqTsdJAQ== -"@gitlab/ui@21.3.1": - version "21.3.1" - resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-21.3.1.tgz#027b767804540539da73d4874370895d7398adea" - integrity sha512-ynyg8i8W8Ud+GoySr4hAjJoW55kWMwSEFLX5MEX8CbdqGurkTLqHYLLpXPBSSnVEcw4stR+bFbKSc35rmBkWPA== +"@gitlab/ui@21.4.2": + version "21.4.2" + resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-21.4.2.tgz#c3d36167ab4df49ce978e20bdd3790e716f5a2d1" + integrity sha512-p8ujeGvCG06Opn0eQlrwZyi9v9RK3T2V4TUcljTAUYDdm0p23qJjjIlFjfGHlQsNg0wRgnkbKFXfkZ/Oy8GyiQ== dependencies: "@babel/standalone" "^7.0.0" "@gitlab/vue-toasted" "^1.3.0" |