Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
96c78a921f
commit
f3352dd3f1
74 changed files with 693 additions and 171 deletions
|
@ -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}">
|
||||
|
|
|
@ -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>
|
|
@ -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'),
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -49,6 +49,7 @@ export default {
|
|||
:add-spacing-classes="false"
|
||||
:markdown-preview-path="markdownPreviewPath"
|
||||
:markdown-docs-path="markdownDocsPath"
|
||||
:textarea-value="value"
|
||||
>
|
||||
<template #textarea>
|
||||
<textarea
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
.tag-release-link {
|
||||
color: $blue-600 !important;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -12,6 +12,7 @@ class ClusterSerializer < BaseSerializer
|
|||
:environment_scope,
|
||||
:gitlab_managed_apps_logs_path,
|
||||
:enable_advanced_logs_querying,
|
||||
:id,
|
||||
:kubernetes_errors,
|
||||
:name,
|
||||
:nodes,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;" }
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add Web IDE as dropdown item to diff file edit
|
||||
merge_request: 42275
|
||||
author:
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix Markdown "Preview" tab on New/Edit Release and New Snippet pages
|
||||
merge_request: 42640
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Update pipeline failed notification e-mail warning
|
||||
merge_request: 42736
|
||||
author:
|
||||
type: fixed
|
|
@ -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
|
||||
|
|
|
@ -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. |
|
||||
|
|
|
@ -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,
|
||||
|
|
BIN
doc/ci/img/gitlab_vault_workflow_v13_4.png
Normal file
BIN
doc/ci/img/gitlab_vault_workflow_v13_4.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 46 KiB |
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 86 KiB |
Binary file not shown.
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 85 KiB |
|
@ -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 Dashboard
|
||||
## Instance Security Center
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/6953) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.8.
|
||||
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3426) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 13.4.
|
||||
|
||||
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 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:
|
||||
|
||||
- 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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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 | | | | | ✓ |
|
||||
|
|
|
@ -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 ""
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}" }
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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|
|
||||
|
|
|
@ -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|
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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: {},
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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=""
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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');
|
||||
|
||||
expect(wrapper.find(GlDeprecatedButton).attributes('href')).toBe(editPath);
|
||||
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(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('shows pencil dropdown', () => {
|
||||
expect(wrapper.find(GlIcon).props('name')).toBe('pencil');
|
||||
expect(wrapper.find('.gl-dropdown-caret').exists()).toBe(true);
|
||||
});
|
||||
|
||||
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 cannot fork', () => {
|
||||
createComponent({
|
||||
editPath,
|
||||
canCurrentUserFork: false,
|
||||
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('doesnt emit a show fork message event if current user can modify blob', () => {
|
||||
createComponent({
|
||||
editPath,
|
||||
canCurrentUserFork: true,
|
||||
canModifyBlob: true,
|
||||
});
|
||||
wrapper.find(GlDeprecatedButton).trigger('click');
|
||||
|
||||
return wrapper.vm.$nextTick().then(() => {
|
||||
expect(wrapper.emitted('showForkMessage')).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
it('disables button if editPath is empty', () => {
|
||||
createComponent({
|
||||
editPath: '',
|
||||
canCurrentUserFork: true,
|
||||
canModifyBlob: true,
|
||||
describe('with editPath is falsey', () => {
|
||||
beforeEach(() => {
|
||||
createComponent({
|
||||
editPath: '',
|
||||
});
|
||||
});
|
||||
|
||||
expect(wrapper.find(GlDeprecatedButton).attributes('disabled')).toBe('true');
|
||||
it('should disable dropdown', () => {
|
||||
expect(findDropdown().attributes('disabled')).toBe('true');
|
||||
});
|
||||
|
||||
it('should have tooltip', () => {
|
||||
expect(getTooltip()).toEqual({
|
||||
id: TOOLTIP_ID,
|
||||
title: "Can't edit as source branch was deleted",
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -18,6 +18,7 @@ exports[`IncidentsSettingTabs should render the component 1`] = `
|
|||
</h4>
|
||||
|
||||
<gl-button-stub
|
||||
buttontextclasses=""
|
||||
category="primary"
|
||||
class="js-settings-toggle"
|
||||
icon=""
|
||||
|
|
|
@ -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=""
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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=""
|
||||
|
|
|
@ -17,6 +17,7 @@ exports[`Project remove modal initialized matches the snapshot 1`] = `
|
|||
/>
|
||||
|
||||
<gl-button-stub
|
||||
buttontextclasses=""
|
||||
category="primary"
|
||||
icon=""
|
||||
role="button"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in a new issue