Add latest changes from gitlab-org/gitlab@master
2
Gemfile
|
@ -274,7 +274,7 @@ gem 'licensee', '~> 9.14.1'
|
|||
gem 'charlock_holmes', '~> 0.7.7'
|
||||
|
||||
# Detect mime content type from content
|
||||
gem 'mimemagic', '~> 0.3.2'
|
||||
gem 'ruby-magic-static'
|
||||
|
||||
# Faster blank
|
||||
gem 'fast_blank'
|
||||
|
|
|
@ -1109,6 +1109,7 @@ GEM
|
|||
i18n
|
||||
ruby-fogbugz (0.2.1)
|
||||
crack (~> 0.4)
|
||||
ruby-magic-static (0.3.0)
|
||||
ruby-prof (1.3.1)
|
||||
ruby-progressbar (1.11.0)
|
||||
ruby-saml (1.7.2)
|
||||
|
@ -1481,7 +1482,6 @@ DEPENDENCIES
|
|||
marginalia (~> 1.10.0)
|
||||
memory_profiler (~> 0.9)
|
||||
method_source (~> 1.0)
|
||||
mimemagic (~> 0.3.2)
|
||||
mini_magick (~> 4.10.1)
|
||||
minitest (~> 5.11.0)
|
||||
multi_json (~> 1.14.1)
|
||||
|
@ -1555,6 +1555,7 @@ DEPENDENCIES
|
|||
rspec_junit_formatter
|
||||
rspec_profiling (~> 0.0.6)
|
||||
ruby-fogbugz (~> 0.2.1)
|
||||
ruby-magic-static
|
||||
ruby-prof (~> 1.3.0)
|
||||
ruby-progressbar (~> 1.10)
|
||||
ruby_parser (~> 3.15)
|
||||
|
|
|
@ -158,7 +158,7 @@ export default {
|
|||
data-testid="issue-blocked-icon"
|
||||
@mouseenter="handleMouseEnter"
|
||||
/>
|
||||
<gl-popover :target="glIconId" placement="top" triggers="hover">
|
||||
<gl-popover :target="glIconId" placement="top">
|
||||
<template #title
|
||||
><span data-testid="popover-title">{{ blockedLabel }}</span></template
|
||||
>
|
||||
|
|
|
@ -37,7 +37,7 @@ export default {
|
|||
|
||||
<template>
|
||||
<div id="popover-container">
|
||||
<gl-popover :target="target" triggers="hover" placement="top" container="popover-container">
|
||||
<gl-popover :target="target" placement="top" container="popover-container">
|
||||
<div
|
||||
class="gl-display-flex gl-justify-content-space-between gl-align-items-center gl-word-break-all"
|
||||
>
|
||||
|
|
|
@ -34,7 +34,7 @@ export default {
|
|||
|
||||
<gl-icon name="status_warning" :size="24" class="gl-p-2" />
|
||||
|
||||
<gl-popover :container="popoverId" :target="popoverId" placement="top" triggers="hover focus">
|
||||
<gl-popover :container="popoverId" :target="popoverId" placement="top">
|
||||
<template #title>
|
||||
<span class="gl-display-block gl-text-left">{{ errorContent.title }}</span>
|
||||
</template>
|
||||
|
|
|
@ -71,7 +71,6 @@ export default {
|
|||
ref="popover"
|
||||
:target="$options.targetId"
|
||||
:css-classes="['feature-highlight-popover']"
|
||||
triggers="hover"
|
||||
container="body"
|
||||
placement="right"
|
||||
boundary="viewport"
|
||||
|
|
|
@ -60,11 +60,7 @@ export default {
|
|||
</select>
|
||||
<span v-if="requestsWithWarnings.length">
|
||||
<span id="performance-bar-request-selector-warning" v-html="glEmojiTag('warning')"></span>
|
||||
<gl-popover
|
||||
target="performance-bar-request-selector-warning"
|
||||
:content="warningMessage"
|
||||
triggers="hover focus"
|
||||
/>
|
||||
<gl-popover target="performance-bar-request-selector-warning" :content="warningMessage" />
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -37,6 +37,6 @@ export default {
|
|||
<template>
|
||||
<span v-if="hasWarnings">
|
||||
<span :id="htmlId" v-html="glEmojiTag('warning')"></span>
|
||||
<gl-popover :target="htmlId" :content="warningMessage" triggers="hover focus" />
|
||||
<gl-popover :target="htmlId" :content="warningMessage" />
|
||||
</span>
|
||||
</template>
|
||||
|
|
|
@ -149,12 +149,7 @@ export default {
|
|||
>
|
||||
{{ s__('mrWidget|Resolve conflicts') }}
|
||||
</gl-button>
|
||||
<gl-popover
|
||||
v-if="showPopover"
|
||||
:target="() => $refs.popover"
|
||||
placement="top"
|
||||
triggers="hover focus"
|
||||
>
|
||||
<gl-popover v-if="showPopover" :target="() => $refs.popover" placement="top">
|
||||
<template #title>
|
||||
<div class="gl-font-weight-normal gl-font-base">
|
||||
{{ $options.i18n.title }}
|
||||
|
|
|
@ -124,7 +124,7 @@ export default {
|
|||
},
|
||||
pipeline() {
|
||||
if (this.glFeatures.mergeRequestWidgetGraphql) {
|
||||
return this.state.pipelines?.nodes?.[0];
|
||||
return this.state.headPipeline;
|
||||
}
|
||||
|
||||
return this.mr.pipeline;
|
||||
|
|
|
@ -11,11 +11,10 @@ query getState($projectPath: ID!, $iid: String!) {
|
|||
mergeError
|
||||
mergeStatus
|
||||
mergeableDiscussionsState
|
||||
pipelines(first: 1) {
|
||||
nodes {
|
||||
status
|
||||
warnings
|
||||
}
|
||||
headPipeline {
|
||||
id
|
||||
status
|
||||
warnings
|
||||
}
|
||||
shouldBeRebased
|
||||
sourceBranchExists
|
||||
|
|
|
@ -30,13 +30,11 @@ fragment ReadyToMerge on Project {
|
|||
message
|
||||
}
|
||||
}
|
||||
pipelines(first: 1) {
|
||||
nodes {
|
||||
id
|
||||
status
|
||||
path
|
||||
active
|
||||
}
|
||||
headPipeline {
|
||||
id
|
||||
status
|
||||
path
|
||||
active
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -163,7 +163,7 @@ export default class MergeRequestStore {
|
|||
|
||||
setGraphqlData(project) {
|
||||
const { mergeRequest } = project;
|
||||
const pipeline = mergeRequest.pipelines?.nodes?.[0];
|
||||
const pipeline = mergeRequest.headPipeline;
|
||||
|
||||
this.projectArchived = project.archived;
|
||||
this.onlyAllowMergeIfPipelineSucceeds = project.onlyAllowMergeIfPipelineSucceeds;
|
||||
|
|
|
@ -21,7 +21,7 @@ import Tracking from '~/tracking';
|
|||
import initUserPopovers from '~/user_popovers';
|
||||
import AlertDetailsTable from '~/vue_shared/components/alert_details_table.vue';
|
||||
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
|
||||
import { SEVERITY_LEVELS } from '../constants';
|
||||
import { PAGE_CONFIG, SEVERITY_LEVELS } from '../constants';
|
||||
import createIssueMutation from '../graphql/mutations/alert_issue_create.mutation.graphql';
|
||||
import toggleSidebarStatusMutation from '../graphql/mutations/alert_sidebar_status.mutation.graphql';
|
||||
import alertQuery from '../graphql/queries/alert_details.query.graphql';
|
||||
|
@ -92,6 +92,9 @@ export default {
|
|||
projectIssuesPath: {
|
||||
default: '',
|
||||
},
|
||||
statuses: {
|
||||
default: PAGE_CONFIG.OPERATIONS.STATUSES,
|
||||
},
|
||||
trackAlertsDetailsViewsOptions: {
|
||||
default: null,
|
||||
},
|
||||
|
@ -367,7 +370,7 @@ export default {
|
|||
>
|
||||
{{ alert.runbook }}
|
||||
</alert-summary-row>
|
||||
<alert-details-table :alert="alert" :loading="loading" />
|
||||
<alert-details-table :alert="alert" :loading="loading" :statuses="statuses" />
|
||||
</gl-tab>
|
||||
<gl-tab
|
||||
v-if="!isThreatMonitoringPage"
|
||||
|
|
|
@ -19,10 +19,6 @@ export default {
|
|||
projectId: {
|
||||
default: '',
|
||||
},
|
||||
// TODO remove this limitation in https://gitlab.com/gitlab-org/gitlab/-/issues/296717
|
||||
isThreatMonitoringPage: {
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
props: {
|
||||
alert: {
|
||||
|
@ -66,7 +62,6 @@ export default {
|
|||
@alert-error="$emit('alert-error', $event)"
|
||||
/>
|
||||
<sidebar-status
|
||||
v-if="!isThreatMonitoringPage"
|
||||
:project-path="projectPath"
|
||||
:alert="alert"
|
||||
@toggle-sidebar="$emit('toggle-sidebar')"
|
||||
|
|
|
@ -3,6 +3,7 @@ import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
|
|||
import updateAlertStatusMutation from '~/graphql_shared/mutations/alert_status_update.mutation.graphql';
|
||||
import { s__ } from '~/locale';
|
||||
import Tracking from '~/tracking';
|
||||
import { PAGE_CONFIG } from '../constants';
|
||||
|
||||
export default {
|
||||
i18n: {
|
||||
|
@ -11,11 +12,6 @@ export default {
|
|||
),
|
||||
UPDATE_ALERT_STATUS_INSTRUCTION: s__('AlertManagement|Please try again.'),
|
||||
},
|
||||
statuses: {
|
||||
TRIGGERED: s__('AlertManagement|Triggered'),
|
||||
ACKNOWLEDGED: s__('AlertManagement|Acknowledged'),
|
||||
RESOLVED: s__('AlertManagement|Resolved'),
|
||||
},
|
||||
components: {
|
||||
GlDropdown,
|
||||
GlDropdownItem,
|
||||
|
@ -42,6 +38,11 @@ export default {
|
|||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
statuses: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => PAGE_CONFIG.OPERATIONS.STATUSES,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
dropdownClass() {
|
||||
|
@ -57,13 +58,13 @@ export default {
|
|||
mutation: updateAlertStatusMutation,
|
||||
variables: {
|
||||
iid: this.alert.iid,
|
||||
status: status.toUpperCase(),
|
||||
status,
|
||||
projectPath: this.projectPath,
|
||||
},
|
||||
})
|
||||
.then((resp) => {
|
||||
if (this.trackAlertStatusUpdateOptions) {
|
||||
this.trackStatusUpdate(status);
|
||||
this.trackStatusUpdate(this.statuses[status]);
|
||||
}
|
||||
const errors = resp.data?.updateAlertStatus?.errors || [];
|
||||
|
||||
|
@ -99,7 +100,7 @@ export default {
|
|||
<gl-dropdown
|
||||
ref="dropdown"
|
||||
right
|
||||
:text="$options.statuses[alert.status]"
|
||||
:text="statuses[alert.status]"
|
||||
class="w-100"
|
||||
toggle-class="dropdown-menu-toggle"
|
||||
@keydown.esc.native="$emit('hide-dropdown')"
|
||||
|
@ -110,12 +111,12 @@ export default {
|
|||
</p>
|
||||
<div class="dropdown-content dropdown-body">
|
||||
<gl-dropdown-item
|
||||
v-for="(label, field) in $options.statuses"
|
||||
v-for="(label, field) in statuses"
|
||||
:key="field"
|
||||
data-testid="statusDropdownItem"
|
||||
:active="label.toUpperCase() === alert.status"
|
||||
:active="field === alert.status"
|
||||
:active-class="'is-active'"
|
||||
@click="updateAlertStatus(label)"
|
||||
@click="updateAlertStatus(field)"
|
||||
>
|
||||
{{ label }}
|
||||
</gl-dropdown-item>
|
||||
|
|
|
@ -1,14 +1,9 @@
|
|||
<script>
|
||||
import { GlIcon, GlLoadingIcon, GlTooltip, GlSprintf } from '@gitlab/ui';
|
||||
import { s__ } from '~/locale';
|
||||
import { PAGE_CONFIG } from '../../constants';
|
||||
import AlertStatus from '../alert_status.vue';
|
||||
|
||||
export default {
|
||||
statuses: {
|
||||
TRIGGERED: s__('AlertManagement|Triggered'),
|
||||
ACKNOWLEDGED: s__('AlertManagement|Acknowledged'),
|
||||
RESOLVED: s__('AlertManagement|Resolved'),
|
||||
},
|
||||
components: {
|
||||
GlIcon,
|
||||
GlLoadingIcon,
|
||||
|
@ -16,6 +11,11 @@ export default {
|
|||
GlSprintf,
|
||||
AlertStatus,
|
||||
},
|
||||
inject: {
|
||||
statuses: {
|
||||
default: PAGE_CONFIG.OPERATIONS.STATUSES,
|
||||
},
|
||||
},
|
||||
props: {
|
||||
projectPath: {
|
||||
type: String,
|
||||
|
@ -94,6 +94,7 @@ export default {
|
|||
:project-path="projectPath"
|
||||
:is-dropdown-showing="isDropdownShowing"
|
||||
:is-sidebar="true"
|
||||
:statuses="statuses"
|
||||
@alert-error="$emit('alert-error', $event)"
|
||||
@hide-dropdown="hideDropdown"
|
||||
@handle-updating="handleUpdating"
|
||||
|
@ -103,14 +104,11 @@ export default {
|
|||
<p
|
||||
v-else-if="!isDropdownShowing"
|
||||
class="value gl-m-0"
|
||||
:class="{ 'no-value': !$options.statuses[alert.status] }"
|
||||
:class="{ 'no-value': !statuses[alert.status] }"
|
||||
>
|
||||
<span
|
||||
v-if="$options.statuses[alert.status]"
|
||||
class="gl-text-gray-500"
|
||||
data-testid="status"
|
||||
>{{ $options.statuses[alert.status] }}</span
|
||||
>
|
||||
<span v-if="statuses[alert.status]" class="gl-text-gray-500" data-testid="status">
|
||||
{{ statuses[alert.status] }}
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ s__('AlertManagement|None') }}
|
||||
</span>
|
||||
|
|
|
@ -13,6 +13,11 @@ export const SEVERITY_LEVELS = {
|
|||
export const PAGE_CONFIG = {
|
||||
OPERATIONS: {
|
||||
TITLE: 'OPERATIONS',
|
||||
STATUSES: {
|
||||
TRIGGERED: s__('AlertManagement|Triggered'),
|
||||
ACKNOWLEDGED: s__('AlertManagement|Acknowledged'),
|
||||
RESOLVED: s__('AlertManagement|Resolved'),
|
||||
},
|
||||
// Tracks snowplow event when user views alert details
|
||||
TRACK_ALERTS_DETAILS_VIEWS_OPTIONS: {
|
||||
category: 'Alert Management',
|
||||
|
@ -27,5 +32,11 @@ export const PAGE_CONFIG = {
|
|||
},
|
||||
THREAT_MONITORING: {
|
||||
TITLE: 'THREAT_MONITORING',
|
||||
STATUSES: {
|
||||
TRIGGERED: s__('ThreatMonitoring|Unreviewed'),
|
||||
ACKNOWLEDGED: s__('ThreatMonitoring|In review'),
|
||||
RESOLVED: s__('ThreatMonitoring|Resolved'),
|
||||
IGNORED: s__('ThreatMonitoring|Dismissed'),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -55,6 +55,7 @@ export default (selector) => {
|
|||
page,
|
||||
projectIssuesPath,
|
||||
projectId,
|
||||
statuses: PAGE_CONFIG[page].STATUSES,
|
||||
};
|
||||
|
||||
if (page === PAGE_CONFIG.OPERATIONS.TITLE) {
|
||||
|
|
|
@ -7,6 +7,7 @@ import {
|
|||
splitCamelCase,
|
||||
} from '~/lib/utils/text_utility';
|
||||
import { s__ } from '~/locale';
|
||||
import { PAGE_CONFIG } from '~/vue_shared/alert_details/constants';
|
||||
|
||||
const thClass = 'gl-bg-transparent! gl-border-1! gl-border-b-solid! gl-border-gray-200!';
|
||||
const tdClass = 'gl-border-gray-100! gl-p-5!';
|
||||
|
@ -42,6 +43,11 @@ export default {
|
|||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
statuses: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => PAGE_CONFIG.OPERATIONS.STATUSES,
|
||||
},
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
|
@ -71,6 +77,8 @@ export default {
|
|||
let value;
|
||||
if (fieldName === 'environment') {
|
||||
value = fieldValue?.name;
|
||||
} else if (fieldName === 'status') {
|
||||
value = this.statuses[fieldValue] || fieldValue;
|
||||
} else {
|
||||
value = fieldValue;
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ export default {
|
|||
<template>
|
||||
<span>
|
||||
<gl-button ref="popoverTrigger" variant="link" icon="question" tabindex="0" />
|
||||
<gl-popover triggers="hover focus" :target="() => $refs.popoverTrigger.$el" v-bind="options">
|
||||
<gl-popover :target="() => $refs.popoverTrigger.$el" v-bind="options">
|
||||
<template v-if="options.title" #title>
|
||||
<span v-safe-html="options.title"></span>
|
||||
</template>
|
||||
|
|
|
@ -60,7 +60,7 @@ export default {
|
|||
|
||||
<template>
|
||||
<!-- 200ms delay so not every mouseover triggers Popover -->
|
||||
<gl-popover :target="target" :delay="200" boundary="viewport" triggers="hover" placement="top">
|
||||
<gl-popover :target="target" :delay="200" boundary="viewport" placement="top">
|
||||
<div class="gl-p-3 gl-line-height-normal gl-display-flex" data-testid="user-popover">
|
||||
<div class="gl-p-2 flex-shrink-1">
|
||||
<user-avatar-image :img-src="user.avatarUrl" :size="60" css-classes="gl-mr-3!" />
|
||||
|
|
|
@ -226,10 +226,10 @@ class Projects::JobsController < Projects::ApplicationController
|
|||
end
|
||||
|
||||
def raw_trace_content_disposition(raw_data)
|
||||
mime_type = MimeMagic.by_magic(raw_data)
|
||||
mime_type = Gitlab::Utils::MimeType.from_string(raw_data)
|
||||
|
||||
# if mime_type is nil can also represent 'text/plain'
|
||||
return 'inline' if mime_type.nil? || mime_type.type == 'text/plain'
|
||||
return 'inline' if mime_type.nil? || mime_type == 'text/plain'
|
||||
|
||||
'attachment'
|
||||
end
|
||||
|
|
|
@ -14,8 +14,6 @@ module Ci
|
|||
|
||||
BuildArchivedError = Class.new(StandardError)
|
||||
|
||||
ignore_columns :artifacts_file, :artifacts_file_store, :artifacts_metadata, :artifacts_metadata_store, :artifacts_size, :commands, remove_after: '2019-12-15', remove_with: '12.7'
|
||||
|
||||
belongs_to :project, inverse_of: :builds
|
||||
belongs_to :runner
|
||||
belongs_to :trigger_request
|
||||
|
|
|
@ -45,8 +45,6 @@ module Ci
|
|||
FORM_EDITABLE = %i[description tag_list active run_untagged locked access_level maximum_timeout_human_readable].freeze
|
||||
MINUTES_COST_FACTOR_FIELDS = %i[public_projects_minutes_cost_factor private_projects_minutes_cost_factor].freeze
|
||||
|
||||
ignore_column :is_shared, remove_after: '2019-12-15', remove_with: '12.6'
|
||||
|
||||
has_many :builds
|
||||
has_many :runner_projects, inverse_of: :runner, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
|
||||
has_many :projects, through: :runner_projects
|
||||
|
|
|
@ -13,8 +13,6 @@ class DeployKey < Key
|
|||
scope :are_public, -> { where(public: true) }
|
||||
scope :with_projects, -> { includes(deploy_keys_projects: { project: [:route, namespace: :route] }) }
|
||||
|
||||
ignore_column :can_push, remove_after: '2019-12-15', remove_with: '12.6'
|
||||
|
||||
accepts_nested_attributes_for :deploy_keys_projects
|
||||
|
||||
def private?
|
||||
|
|
|
@ -43,7 +43,7 @@ module ContentTypeWhitelist
|
|||
def mime_magic_content_type(path)
|
||||
if path
|
||||
File.open(path) do |file|
|
||||
MimeMagic.by_magic(file).try(:type) || 'invalid/invalid'
|
||||
Gitlab::Utils::MimeType.from_io(file) || 'invalid/invalid'
|
||||
end
|
||||
end
|
||||
rescue Errno::ENOENT
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Generalize alert details status
|
||||
merge_request: 56800
|
||||
author:
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Show popovers on hover and focus by default
|
||||
merge_request: 56778
|
||||
author:
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Refactor MimeMagic calls to new MimeType class
|
||||
merge_request: 57421
|
||||
author:
|
||||
type: other
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Remove direct mimemagic dependency
|
||||
merge_request: 57387
|
||||
author:
|
||||
type: other
|
|
@ -1148,7 +1148,7 @@ POST /projects
|
|||
| `namespace_id` | integer | **{dotted-circle}** No | Namespace for the new project (defaults to the current user's namespace). |
|
||||
| `operations_access_level` | string | **{dotted-circle}** No | One of `disabled`, `private`, or `enabled`. |
|
||||
| `only_allow_merge_if_all_discussions_are_resolved` | boolean | **{dotted-circle}** No | Set whether merge requests can only be merged when all the discussions are resolved. |
|
||||
| `only_allow_merge_if_pipeline_succeeds` | boolean | **{dotted-circle}** No | Set whether merge requests can only be merged with successful jobs. |
|
||||
| `only_allow_merge_if_pipeline_succeeds` | boolean | **{dotted-circle}** No | Set whether merge requests can only be merged with successful pipelines. This setting is named [**Pipelines must succeed**](../user/project/merge_requests/merge_when_pipeline_succeeds.md#only-allow-merge-requests-to-be-merged-if-the-pipeline-succeeds) in the project settings. |
|
||||
| `packages_enabled` | boolean | **{dotted-circle}** No | Enable or disable packages repository feature. |
|
||||
| `pages_access_level` | string | **{dotted-circle}** No | One of `disabled`, `private`, `enabled`, or `public`. |
|
||||
| `requirements_access_level` | string | **{dotted-circle}** No | One of `disabled`, `private`, `enabled` or `public` |
|
||||
|
|
Before Width: | Height: | Size: 94 KiB |
After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 39 KiB |
After Width: | Height: | Size: 24 KiB |
|
@ -172,7 +172,7 @@ Add a variable name (key) and value here to override the value defined in
|
|||
[the UI or `.gitlab-ci.yml`](../variables/README.md#custom-cicd-variables),
|
||||
for a single run of the manual job.
|
||||
|
||||
![Manual job variables](img/manual_job_variables.png)
|
||||
![Manual job variables](img/manual_job_variables_v13_10.png)
|
||||
|
||||
## Delay a job
|
||||
|
||||
|
@ -200,10 +200,10 @@ the duration.
|
|||
|
||||
In the following example:
|
||||
|
||||
- Two sections are collapsed and can be expanded.
|
||||
- Three sections are collapsed and can be expanded.
|
||||
- Three sections are expanded and can be collapsed.
|
||||
|
||||
![Collapsible sections](img/collapsible_log_v12_6.png)
|
||||
![Collapsible sections](img/collapsible_log_v13_10.png)
|
||||
|
||||
### Custom collapsible sections
|
||||
|
||||
|
|
|
@ -76,7 +76,7 @@ branch in the project.
|
|||
GitLab CI/CD not only executes the jobs but also shows you what's happening during execution,
|
||||
just as you would see in your terminal:
|
||||
|
||||
![job running](img/job_running.png)
|
||||
![job running](img/job_running_v13_10.png)
|
||||
|
||||
You create the strategy for your app and GitLab runs the pipeline
|
||||
according to what you've defined. Your pipeline status is also
|
||||
|
|
Before Width: | Height: | Size: 97 KiB |
After Width: | Height: | Size: 56 KiB |
Before Width: | Height: | Size: 7.8 KiB |
|
@ -8,9 +8,8 @@ type: reference
|
|||
# Continuous Integration and Deployment Admin settings **(FREE SELF)**
|
||||
|
||||
In this area, you will find settings for Auto DevOps, runners, and job artifacts.
|
||||
You can find it in the **Admin Area > Settings > CI/CD**.
|
||||
|
||||
![Admin Area settings button](../img/admin_area_settings_button.png)
|
||||
You can find it in the [Admin Area](index.md) by navigating to
|
||||
**Admin Area > Settings > CI/CD**.
|
||||
|
||||
## Auto DevOps **(FREE SELF)**
|
||||
|
||||
|
|
|
@ -133,18 +133,6 @@ The results are saved as a
|
|||
that you can later download and analyze. Due to implementation limitations, we
|
||||
always take the latest Secret Detection artifact available.
|
||||
|
||||
### Post-processing
|
||||
|
||||
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/4639) in GitLab 13.6.
|
||||
|
||||
Upon detection of a secret, GitLab supports post processing hooks. These can be used to take actions like notifying the cloud service who issued the secret. The cloud provider can confirm the credentials and take remediation actions like revoking or reissuing a new secret and notifying the creator of the secret. Post-processing workflows vary by supported cloud providers.
|
||||
|
||||
GitLab currently supports post-processing for following service providers:
|
||||
|
||||
- Amazon Web Services (AWS)
|
||||
|
||||
Third party cloud and SaaS providers can [express integration interest by filling out this form](https://forms.gle/wWpvrtLRK21Q2WJL9). Learn more about the [technical details of post-processing secrets](https://gitlab.com/groups/gitlab-org/-/epics/4639).
|
||||
|
||||
### Customizing settings
|
||||
|
||||
The Secret Detection scan settings can be changed through [CI/CD variables](#available-variables)
|
||||
|
@ -249,6 +237,34 @@ From highest to lowest severity, the logging levels are:
|
|||
- `info` (default)
|
||||
- `debug`
|
||||
|
||||
## Post-processing and revocation
|
||||
|
||||
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/4639) in GitLab 13.6.
|
||||
|
||||
Upon detection of a secret, GitLab supports post-processing hooks. These can be used to take actions like notifying the cloud service who issued the secret. The cloud provider can confirm the credentials and take remediation actions like revoking or reissuing a new secret and notifying the creator of the secret. Post-processing workflows vary by supported cloud providers.
|
||||
|
||||
GitLab currently supports post-processing for following service providers:
|
||||
|
||||
- Amazon Web Services (AWS)
|
||||
|
||||
Third party cloud and SaaS providers can [express integration interest by filling out this form](https://forms.gle/wWpvrtLRK21Q2WJL9). Learn more about the [technical details of post-processing secrets](https://gitlab.com/groups/gitlab-org/-/epics/4639).
|
||||
|
||||
NOTE:
|
||||
Post-processing is currently limited to a project's default branch, see the above epic for future efforts to support additional branches.
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
autonumber
|
||||
Rails->>+Sidekiq: gl-secret-detection-report.json
|
||||
Sidekiq-->+Sidekiq: BuildFinishedWorker
|
||||
Sidekiq-->+RevocationAPI: GET revocable keys types
|
||||
RevocationAPI-->>-Sidekiq: OK
|
||||
Sidekiq->>+RevocationAPI: POST revoke revocable keys
|
||||
RevocationAPI-->>-Sidekiq: ACCEPTED
|
||||
RevocationAPI-->>+Cloud Vendor: revoke revocable keys
|
||||
Cloud Vendor-->>+RevocationAPI: ACCEPTED
|
||||
```
|
||||
|
||||
## Full History Secret Scan
|
||||
|
||||
GitLab 12.11 introduced support for scanning the full history of a repository. This new functionality
|
||||
|
|
Before Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 16 KiB |
|
@ -73,12 +73,13 @@ CSV file containing details of the resources scanned.
|
|||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/235558) in GitLab 13.6.
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/285476) in GitLab 13.10, options to zoom in on a date range, and download the vulnerabilities chart.
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/285477) in GitLab 13.11, date range slider to visualise data between given dates.
|
||||
|
||||
At the project level, the Security Dashboard displays a chart with the number of vulnerabilities over time.
|
||||
Access it by navigating to **Security & Compliance > Security Dashboard**. We display historical
|
||||
data up to 365 days. The chart's data is updated daily.
|
||||
|
||||
![Project Security Dashboard](img/project_security_dashboard_chart_v13_10.png)
|
||||
![Project Security Dashboard](img/project_security_dashboard_chart_v13_11.png)
|
||||
|
||||
Filter the historical data by clicking on the corresponding legend name. The image above, for example, shows
|
||||
only the graph for vulnerabilities with **high** severity.
|
||||
|
|
|
@ -62,7 +62,7 @@ module API
|
|||
authorize_upload!(project)
|
||||
bad_request!('File is too large') if max_file_size_exceeded?
|
||||
|
||||
track_event('push_package')
|
||||
::Gitlab::Tracking.event(self.options[:for].name, 'push_package')
|
||||
|
||||
create_package_file_params = declared_params.merge(build: current_authenticated_job)
|
||||
::Packages::Generic::CreatePackageFileService
|
||||
|
@ -94,7 +94,7 @@ module API
|
|||
package = ::Packages::Generic::PackageFinder.new(project).execute!(params[:package_name], params[:package_version])
|
||||
package_file = ::Packages::PackageFileFinder.new(package, params[:file_name]).execute!
|
||||
|
||||
track_event('pull_package')
|
||||
::Gitlab::Tracking.event(self.options[:for].name, 'pull_package')
|
||||
|
||||
present_carrierwave_file!(package_file.file)
|
||||
end
|
||||
|
|
|
@ -544,17 +544,6 @@ module API
|
|||
end
|
||||
end
|
||||
|
||||
def track_event(action = action_name, **args)
|
||||
category = args.delete(:category) || self.options[:for].name
|
||||
raise "invalid category" unless category
|
||||
|
||||
::Gitlab::Tracking.event(category, action.to_s, **args)
|
||||
rescue => error
|
||||
Gitlab::AppLogger.warn(
|
||||
"Tracking event failed for action: #{action}, category: #{category}, message: #{error.message}"
|
||||
)
|
||||
end
|
||||
|
||||
def increment_counter(event_name)
|
||||
feature_name = "usage_data_#{event_name}"
|
||||
return unless Feature.enabled?(feature_name)
|
||||
|
|
|
@ -10,7 +10,7 @@ module API
|
|||
|
||||
def redirect_registry_request(forward_to_registry, package_type, options)
|
||||
if forward_to_registry && redirect_registry_request_available?
|
||||
track_event("#{package_type}_request_forward")
|
||||
::Gitlab::Tracking.event(self.options[:for].name, "#{package_type}_request_forward")
|
||||
redirect(registry_url(package_type, options))
|
||||
else
|
||||
yield
|
||||
|
|
|
@ -50,7 +50,8 @@ module API
|
|||
|
||||
def track_package_event(event_name, scope, **args)
|
||||
::Packages::CreateEventService.new(nil, current_user, event_name: event_name, scope: scope).execute
|
||||
track_event(event_name, **args)
|
||||
category = args.delete(:category) || self.options[:for].name
|
||||
::Gitlab::Tracking.event(category, event_name.to_s, **args)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -85,7 +85,6 @@ module Gitlab
|
|||
id: runner.id,
|
||||
description: runner.description,
|
||||
active: runner.active?,
|
||||
is_shared: runner.instance_type?,
|
||||
tags: runner.tags&.map(&:name)
|
||||
}
|
||||
end
|
||||
|
|
|
@ -77,7 +77,6 @@ module Gitlab
|
|||
id: runner.id,
|
||||
description: runner.description,
|
||||
active: runner.active?,
|
||||
is_shared: runner.instance_type?,
|
||||
tags: runner.tags&.map(&:name)
|
||||
}
|
||||
end
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
# frozen_string_literal: true
|
||||
require 'magic'
|
||||
|
||||
# This wraps calls to a gem which support mime type detection.
|
||||
# We use the `ruby-magic` gem instead of `mimemagic` due to licensing issues
|
||||
module Gitlab
|
||||
module Utils
|
||||
class MimeType
|
||||
class << self
|
||||
def from_io(io)
|
||||
return unless io.is_a?(IO) || io.is_a?(StringIO)
|
||||
|
||||
mime_type = File.magic(io, Magic::MIME_TYPE)
|
||||
mime_type == 'inode/x-empty' ? nil : mime_type
|
||||
end
|
||||
|
||||
def from_string(string)
|
||||
return unless string.is_a?(String)
|
||||
|
||||
string.type
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -59,7 +59,7 @@
|
|||
"@gitlab/favicon-overlay": "2.0.0",
|
||||
"@gitlab/svgs": "1.185.0",
|
||||
"@gitlab/tributejs": "1.0.0",
|
||||
"@gitlab/ui": "28.9.1",
|
||||
"@gitlab/ui": "28.15.0",
|
||||
"@gitlab/visual-review-tools": "1.6.1",
|
||||
"@rails/actioncable": "^6.0.3-4",
|
||||
"@rails/ujs": "^6.0.3-4",
|
||||
|
|
|
@ -333,6 +333,31 @@ RSpec.describe 'Merge request > User sees pipelines triggered by merge request',
|
|||
end
|
||||
end
|
||||
|
||||
context 'when the latest pipeline is running in the parent project' do
|
||||
before do
|
||||
Ci::CreatePipelineService.new(project, user, ref: 'feature')
|
||||
.execute(:merge_request_event, merge_request: merge_request)
|
||||
end
|
||||
|
||||
context 'when the previous pipeline failed in the fork project' do
|
||||
before do
|
||||
detached_merge_request_pipeline.drop!
|
||||
end
|
||||
|
||||
context 'when the parent project enables pipeline must succeed' do
|
||||
before do
|
||||
project.update!(only_allow_merge_if_pipeline_succeeds: true)
|
||||
end
|
||||
|
||||
it 'shows MWPS button' do
|
||||
visit project_merge_request_path(project, merge_request)
|
||||
|
||||
expect(page).to have_button('Merge when pipeline succeeds')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a user merges a merge request from a forked project to the parent project' do
|
||||
before do
|
||||
click_link("Overview")
|
||||
|
|
After Width: | Height: | Size: 348 KiB |
After Width: | Height: | Size: 84 KiB |
After Width: | Height: | Size: 4.2 KiB |
|
@ -41,7 +41,6 @@ describe('feature_highlight/feature_highlight_popover', () => {
|
|||
expect(findPopover().props()).toMatchObject({
|
||||
target: POPOVER_TARGET_ID,
|
||||
cssClasses: ['feature-highlight-popover'],
|
||||
triggers: 'hover',
|
||||
container: 'body',
|
||||
placement: 'right',
|
||||
boundary: 'viewport',
|
||||
|
|
|
@ -52,7 +52,9 @@ exports[`packages_list_app renders 1`] = `
|
|||
with GitLab.
|
||||
</p>
|
||||
|
||||
<div>
|
||||
<div
|
||||
class="gl-display-flex gl-flex-wrap gl-justify-content-center"
|
||||
>
|
||||
<!---->
|
||||
|
||||
<!---->
|
||||
|
|
|
@ -10,8 +10,8 @@ exports[`EmptyStateComponent should render content 1`] = `
|
|||
<h1 class=\\"h4\\">Getting started with serverless</h1>
|
||||
<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=\\"\\" buttontextclasses=\\"\\" href=\\"/clusters\\">Install Knative</gl-button-stub>
|
||||
<div class=\\"gl-display-flex gl-flex-wrap gl-justify-content-center\\">
|
||||
<gl-button-stub category=\\"primary\\" variant=\\"confirm\\" size=\\"medium\\" icon=\\"\\" buttontextclasses=\\"\\" href=\\"/clusters\\" class=\\"gl-mb-3 gl-mx-2\\">Install Knative</gl-button-stub>
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -8,7 +8,7 @@ import { joinPaths } from '~/lib/utils/url_utility';
|
|||
import Tracking from '~/tracking';
|
||||
import AlertDetails from '~/vue_shared/alert_details/components/alert_details.vue';
|
||||
import AlertSummaryRow from '~/vue_shared/alert_details/components/alert_summary_row.vue';
|
||||
import { SEVERITY_LEVELS } from '~/vue_shared/alert_details/constants';
|
||||
import { PAGE_CONFIG, SEVERITY_LEVELS } from '~/vue_shared/alert_details/constants';
|
||||
import createIssueMutation from '~/vue_shared/alert_details/graphql/mutations/alert_issue_create.mutation.graphql';
|
||||
import AlertDetailsTable from '~/vue_shared/components/alert_details_table.vue';
|
||||
import mockAlerts from './mocks/alerts.json';
|
||||
|
@ -271,7 +271,13 @@ describe('AlertDetails', () => {
|
|||
});
|
||||
|
||||
it('should display a table of raw alert details data', () => {
|
||||
expect(findDetailsTable().exists()).toBe(true);
|
||||
const details = findDetailsTable();
|
||||
expect(details.exists()).toBe(true);
|
||||
expect(details.props()).toStrictEqual({
|
||||
alert: mockAlert,
|
||||
statuses: PAGE_CONFIG.OPERATIONS.STATUSES,
|
||||
loading: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ describe('AlertManagementStatus', () => {
|
|||
let wrapper;
|
||||
const findStatusDropdown = () => wrapper.find(GlDropdown);
|
||||
const findFirstStatusOption = () => findStatusDropdown().find(GlDropdownItem);
|
||||
const findAllStatusOptions = () => findStatusDropdown().findAll(GlDropdownItem);
|
||||
|
||||
const selectFirstStatusOption = () => {
|
||||
findFirstStatusOption().vm.$emit('click');
|
||||
|
@ -131,6 +132,24 @@ describe('AlertManagementStatus', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('Statuses', () => {
|
||||
it('renders default translated statuses', () => {
|
||||
mountComponent({});
|
||||
expect(findAllStatusOptions().length).toBe(3);
|
||||
expect(findFirstStatusOption().text()).toBe('Triggered');
|
||||
});
|
||||
|
||||
it('renders translated statuses', () => {
|
||||
const status = 'TEST';
|
||||
const translatedStatus = 'Test';
|
||||
mountComponent({
|
||||
props: { alert: { ...mockAlert, status }, statuses: { [status]: translatedStatus } },
|
||||
});
|
||||
expect(findAllStatusOptions().length).toBe(1);
|
||||
expect(findFirstStatusOption().text()).toBe(translatedStatus);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Snowplow tracking', () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(Tracking, 'event');
|
||||
|
|
|
@ -76,20 +76,4 @@ describe('Alert Details Sidebar', () => {
|
|||
expect(wrapper.find(SidebarStatus).exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('the sidebar renders for threat monitoring', () => {
|
||||
beforeEach(() => {
|
||||
mock = new MockAdapter(axios);
|
||||
mountComponent();
|
||||
});
|
||||
|
||||
it('should not render side bar status dropdown', () => {
|
||||
mountComponent({
|
||||
mountMethod: mount,
|
||||
alert: mockAlert,
|
||||
provide: { isThreatMonitoringPage: true },
|
||||
});
|
||||
expect(wrapper.find(SidebarStatus).exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import { GlDropdown, GlDropdownItem, GlLoadingIcon } from '@gitlab/ui';
|
||||
import { mount } from '@vue/test-utils';
|
||||
import updateAlertStatusMutation from '~/graphql_shared/mutations/alert_status_update.mutation.graphql';
|
||||
import AlertStatus from '~/vue_shared/alert_details/components/alert_status.vue';
|
||||
import AlertSidebarStatus from '~/vue_shared/alert_details/components/sidebar/sidebar_status.vue';
|
||||
import { PAGE_CONFIG } from '~/vue_shared/alert_details/constants';
|
||||
import mockAlerts from '../mocks/alerts.json';
|
||||
|
||||
const mockAlert = mockAlerts[0];
|
||||
|
@ -12,8 +14,16 @@ describe('Alert Details Sidebar Status', () => {
|
|||
const findStatusDropdownItem = () => wrapper.find(GlDropdownItem);
|
||||
const findStatusLoadingIcon = () => wrapper.find(GlLoadingIcon);
|
||||
const findStatusDropdownHeader = () => wrapper.find('[data-testid="dropdown-header"]');
|
||||
const findAlertStatus = () => wrapper.findComponent(AlertStatus);
|
||||
const findStatus = () => wrapper.find('[data-testid="status"]');
|
||||
|
||||
function mountComponent({ data, sidebarCollapsed = true, loading = false, stubs = {} } = {}) {
|
||||
function mountComponent({
|
||||
data,
|
||||
sidebarCollapsed = true,
|
||||
loading = false,
|
||||
stubs = {},
|
||||
provide = {},
|
||||
} = {}) {
|
||||
wrapper = mount(AlertSidebarStatus, {
|
||||
propsData: {
|
||||
alert: { ...mockAlert },
|
||||
|
@ -32,6 +42,7 @@ describe('Alert Details Sidebar Status', () => {
|
|||
},
|
||||
},
|
||||
stubs,
|
||||
provide,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -96,8 +107,24 @@ describe('Alert Details Sidebar Status', () => {
|
|||
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockReturnValue(Promise.reject(new Error()));
|
||||
findStatusDropdownItem().vm.$emit('click');
|
||||
expect(findStatusLoadingIcon().exists()).toBe(false);
|
||||
expect(wrapper.find('[data-testid="status"]').text()).toBe('Triggered');
|
||||
expect(findStatus().text()).toBe('Triggered');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Statuses', () => {
|
||||
it('renders default translated statuses', () => {
|
||||
mountComponent({});
|
||||
expect(findAlertStatus().props('statuses')).toBe(PAGE_CONFIG.OPERATIONS.STATUSES);
|
||||
expect(findStatus().text()).toBe('Triggered');
|
||||
});
|
||||
|
||||
it('renders translated statuses', () => {
|
||||
const status = 'TEST';
|
||||
const statuses = { [status]: 'Test' };
|
||||
mountComponent({ data: { alert: { ...mockAlert, status } }, provide: { statuses } });
|
||||
expect(findAlertStatus().props('statuses')).toBe(statuses);
|
||||
expect(findStatus().text()).toBe(statuses.TEST);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -75,45 +75,62 @@ describe('AlertDetails', () => {
|
|||
});
|
||||
|
||||
describe('with table data', () => {
|
||||
beforeEach(mountComponent);
|
||||
describe('default', () => {
|
||||
beforeEach(mountComponent);
|
||||
|
||||
it('renders a table', () => {
|
||||
expect(findTableComponent().exists()).toBe(true);
|
||||
it('renders a table', () => {
|
||||
expect(findTableComponent().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('renders a cell based on alert data', () => {
|
||||
expect(findTableComponent().text()).toContain('SyntaxError: Invalid or unexpected token');
|
||||
});
|
||||
|
||||
it('should show allowed alert fields', () => {
|
||||
const fields = findTableKeys();
|
||||
['Iid', 'Title', 'Severity', 'Status', 'Hosts', 'Environment'].forEach((field) => {
|
||||
expect(findTableField(fields, field).exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should not show disallowed alert fields', () => {
|
||||
const fields = findTableKeys();
|
||||
['Typename', 'Todos', 'Notes', 'Assignees'].forEach((field) => {
|
||||
expect(findTableField(fields, field).exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('renders a cell based on alert data', () => {
|
||||
expect(findTableComponent().text()).toContain('SyntaxError: Invalid or unexpected token');
|
||||
describe('environment', () => {
|
||||
it('should display only the name for the environment', () => {
|
||||
mountComponent();
|
||||
expect(findTableFieldValueByKey('Environment').text()).toBe(environmentName);
|
||||
});
|
||||
|
||||
it('should not display the environment row if there is not data', () => {
|
||||
environmentData = { name: null, path: null };
|
||||
mountComponent();
|
||||
|
||||
expect(findTableFieldValueByKey('Environment').text()).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
it('should show allowed alert fields', () => {
|
||||
const fields = findTableKeys();
|
||||
describe('status', () => {
|
||||
it('should show the translated status for the default statuses', () => {
|
||||
mountComponent();
|
||||
expect(findTableFieldValueByKey('Status').text()).toBe('Triggered');
|
||||
});
|
||||
|
||||
expect(findTableField(fields, 'Iid').exists()).toBe(true);
|
||||
expect(findTableField(fields, 'Title').exists()).toBe(true);
|
||||
expect(findTableField(fields, 'Severity').exists()).toBe(true);
|
||||
expect(findTableField(fields, 'Status').exists()).toBe(true);
|
||||
expect(findTableField(fields, 'Hosts').exists()).toBe(true);
|
||||
expect(findTableField(fields, 'Environment').exists()).toBe(true);
|
||||
});
|
||||
it('should show the translated status for provided statuses', () => {
|
||||
const translatedStatus = 'Test';
|
||||
mountComponent({ statuses: { TRIGGERED: translatedStatus } });
|
||||
expect(findTableFieldValueByKey('Status').text()).toBe(translatedStatus);
|
||||
});
|
||||
|
||||
it('should not show disallowed alert fields', () => {
|
||||
const fields = findTableKeys();
|
||||
|
||||
expect(findTableField(fields, 'Typename').exists()).toBe(false);
|
||||
expect(findTableField(fields, 'Todos').exists()).toBe(false);
|
||||
expect(findTableField(fields, 'Notes').exists()).toBe(false);
|
||||
expect(findTableField(fields, 'Assignees').exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('should display only the name for the environment', () => {
|
||||
expect(findTableFieldValueByKey('Environment').text()).toBe(environmentName);
|
||||
});
|
||||
|
||||
it('should not display the environment row if there is not data', () => {
|
||||
environmentData = { name: null, path: null };
|
||||
mountComponent();
|
||||
|
||||
expect(findTableFieldValueByKey('Environment').text()).toBeFalsy();
|
||||
it('should show the provided status if value is not defined in statuses', () => {
|
||||
mountComponent({ statuses: {} });
|
||||
expect(findTableFieldValueByKey('Status').text()).toBe('TRIGGERED');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -41,10 +41,6 @@ describe('HelpPopover', () => {
|
|||
expect(findPopover().props().target()).toBe(findQuestionButton().vm.$el);
|
||||
});
|
||||
|
||||
it('triggers popover on hover and focus', () => {
|
||||
expect(findPopover().props().triggers).toBe('hover focus');
|
||||
});
|
||||
|
||||
it('allows rendering title with HTML tags', () => {
|
||||
expect(findPopover().find('strong').exists()).toBe(true);
|
||||
});
|
||||
|
|
|
@ -12,6 +12,10 @@ RSpec.describe API::Helpers::Packages::DependencyProxyHelpers do
|
|||
|
||||
subject { helper.redirect_registry_request(forward_to_registry, package_type, options) { helper.fallback } }
|
||||
|
||||
before do
|
||||
allow(helper).to receive(:options).and_return(for: API::NpmInstancePackages)
|
||||
end
|
||||
|
||||
shared_examples 'executing fallback' do
|
||||
it 'redirects to package registry' do
|
||||
expect(helper).to receive(:registry_url).never
|
||||
|
@ -23,13 +27,14 @@ RSpec.describe API::Helpers::Packages::DependencyProxyHelpers do
|
|||
end
|
||||
|
||||
shared_examples 'executing redirect' do
|
||||
it 'redirects to package registry' do
|
||||
expect(helper).to receive(:track_event).with('npm_request_forward').once
|
||||
it 'redirects to package registry', :snowplow do
|
||||
expect(helper).to receive(:registry_url).once
|
||||
expect(helper).to receive(:redirect).once
|
||||
expect(helper).to receive(:fallback).never
|
||||
|
||||
subject
|
||||
|
||||
expect_snowplow_event(category: 'API::NpmInstancePackages', action: 'npm_request_forward')
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -64,7 +69,6 @@ RSpec.describe API::Helpers::Packages::DependencyProxyHelpers do
|
|||
let(:package_type) { pkg_type }
|
||||
|
||||
it 'raises an error' do
|
||||
allow(helper).to receive(:track_event)
|
||||
expect { subject }.to raise_error(ArgumentError, "Can't build registry_url for package_type #{package_type}")
|
||||
end
|
||||
end
|
||||
|
|
|
@ -175,20 +175,6 @@ RSpec.describe API::Helpers do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#track_event' do
|
||||
it "creates a gitlab tracking event", :snowplow do
|
||||
subject.track_event('my_event', category: 'foo')
|
||||
|
||||
expect_snowplow_event(category: 'foo', action: 'my_event')
|
||||
end
|
||||
|
||||
it "logs an exception" do
|
||||
expect(Gitlab::AppLogger).to receive(:warn).with(/Tracking event failed/)
|
||||
|
||||
subject.track_event('my_event', category: nil)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#increment_unique_values' do
|
||||
let(:value) { '9f302fea-f828-4ca9-aef4-e10bd723c0b3' }
|
||||
let(:event_name) { 'g_compliance_dashboard' }
|
||||
|
|
|
@ -59,7 +59,6 @@ RSpec.describe Gitlab::DataBuilder::Pipeline do
|
|||
expect(runner_data[:id]).to eq(ci_runner.id)
|
||||
expect(runner_data[:description]).to eq(ci_runner.description)
|
||||
expect(runner_data[:active]).to eq(ci_runner.active)
|
||||
expect(runner_data[:is_shared]).to eq(ci_runner.instance_type?)
|
||||
expect(runner_data[:tags]).to match_array(tag_names)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,7 +4,7 @@ require 'spec_helper'
|
|||
|
||||
RSpec.describe Gitlab::SQL::CTE do
|
||||
describe '#to_arel' do
|
||||
it 'generates an Arel relation for the CTE body' do
|
||||
it 'generates an Arel relation for the CTE body', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/325916' do
|
||||
relation = User.where(id: 1)
|
||||
cte = described_class.new(:cte_name, relation)
|
||||
sql = cte.to_arel.to_sql
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "fast_spec_helper"
|
||||
require "rspec/parameterized"
|
||||
|
||||
RSpec.describe Gitlab::Utils::MimeType do
|
||||
describe ".from_io" do
|
||||
subject { described_class.from_io(io) }
|
||||
|
||||
context "input isn't an IO" do
|
||||
let(:io) { "test" }
|
||||
|
||||
it "returns nil" do
|
||||
expect(subject).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context "input is a file" do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
where(:fixture, :mime_type) do
|
||||
"banana_sample.gif" | "image/gif"
|
||||
"rails_sample.jpg" | "image/jpeg"
|
||||
"rails_sample.png" | "image/png"
|
||||
"rails_sample.bmp" | "image/bmp"
|
||||
"rails_sample.tif" | "image/tiff"
|
||||
"sample.ico" | "image/vnd.microsoft.icon"
|
||||
"blockquote_fence_before.md" | "text/plain"
|
||||
"csv_empty.csv" | "application/x-empty"
|
||||
end
|
||||
|
||||
with_them do
|
||||
let(:io) { File.open(File.join(__dir__, "../../../fixtures", fixture)) }
|
||||
|
||||
it { is_expected.to eq(mime_type) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe ".from_string" do
|
||||
subject { described_class.from_string(str) }
|
||||
|
||||
context "input isn't a string" do
|
||||
let(:str) { nil }
|
||||
|
||||
it "returns nil" do
|
||||
expect(subject).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context "input is a string" do
|
||||
let(:str) { "plain text" }
|
||||
|
||||
it { is_expected.to eq('text/plain') }
|
||||
end
|
||||
end
|
||||
end
|
|
@ -13,7 +13,6 @@ end
|
|||
# @param mime_type [String] mime type to forcibly detect.
|
||||
RSpec.shared_context 'force content type detection to mime_type' do
|
||||
before do
|
||||
magic_mime_obj = MimeMagic.new(mime_type)
|
||||
allow(MimeMagic).to receive(:by_magic).with(anything).and_return(magic_mime_obj)
|
||||
allow(Gitlab::Utils::MimeType).to receive(:from_io).and_return(mime_type)
|
||||
end
|
||||
end
|
||||
|
|
12
yarn.lock
|
@ -907,16 +907,16 @@
|
|||
resolved "https://registry.yarnpkg.com/@gitlab/tributejs/-/tributejs-1.0.0.tgz#672befa222aeffc83e7d799b0500a7a4418e59b8"
|
||||
integrity sha512-nmKw1+hB6MHvlmPz63yPwVs1qQkycHwsKgxpEbzmky16Y6mL4EJMk3w1b8QlOAF/AIAzjCERPhe/R4MJiohbZw==
|
||||
|
||||
"@gitlab/ui@28.9.1":
|
||||
version "28.9.1"
|
||||
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-28.9.1.tgz#7d4d4502ff09fca19ab815504f80afbf03dd2fc1"
|
||||
integrity sha512-+JqkpwzkKBnxo4KkC8XSPEJ5Au9y+TIOE7w9I5o+04krgWCbZKNqaiKZkg2IqSlo/sZSfvihXZMhEVc/JXf7HQ==
|
||||
"@gitlab/ui@28.15.0":
|
||||
version "28.15.0"
|
||||
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-28.15.0.tgz#e78a1c0724c7cc8880fcff8161e529ca7bcaf6e8"
|
||||
integrity sha512-muz1tX3nQmu9dMv7GbTNIkWkEwYhvnLPhtwtnrt8eyRGQ0zIUWLEzdoSiwvMNLAqT2JB8kxahoavR5iSFAYtXA==
|
||||
dependencies:
|
||||
"@babel/standalone" "^7.0.0"
|
||||
"@gitlab/vue-toasted" "^1.3.0"
|
||||
bootstrap-vue "2.13.1"
|
||||
copy-to-clipboard "^3.0.8"
|
||||
dompurify "^2.2.6"
|
||||
dompurify "^2.2.7"
|
||||
echarts "^4.9.0"
|
||||
highlight.js "^10.6.0"
|
||||
js-beautify "^1.8.8"
|
||||
|
@ -4270,7 +4270,7 @@ domhandler@^2.3.0:
|
|||
dependencies:
|
||||
domelementtype "1"
|
||||
|
||||
dompurify@^2.2.6, dompurify@^2.2.7:
|
||||
dompurify@^2.2.7:
|
||||
version "2.2.7"
|
||||
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.2.7.tgz#a5f055a2a471638680e779bd08fc334962d11fd8"
|
||||
integrity sha512-jdtDffdGNY+C76jvodNTu9jt5yYj59vuTUyx+wXdzcSwAGTYZDAQkQ7Iwx9zcGrA4ixC1syU4H3RZROqRxokxg==
|
||||
|
|