Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-06-05 18:08:19 +00:00
parent 86e1f47cd1
commit aee8d27430
65 changed files with 879 additions and 245 deletions

View File

@ -1,12 +1,15 @@
<script>
import eventHub from '../event_hub';
import { GlToggle } from '@gitlab/ui';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { GlFormGroup, GlToggle } from '@gitlab/ui';
export default {
name: 'ActiveToggle',
components: {
GlFormGroup,
GlToggle,
},
mixins: [glFeatureFlagsMixin()],
props: {
initialActivated: {
type: Boolean,
@ -33,7 +36,17 @@ export default {
</script>
<template>
<div>
<div v-if="glFeatures.integrationFormRefactor">
<gl-form-group :label="__('Enable integration')" label-for="service[active]">
<gl-toggle
v-model="activated"
name="service[active]"
class="gl-display-block gl-line-height-0"
@change="onToggle"
/>
</gl-form-group>
</div>
<div v-else>
<div class="form-group row" role="group">
<label for="service[active]" class="col-form-label col-sm-2">{{ __('Active') }}</label>
<div class="col-sm-10 pt-1">

View File

@ -1,4 +1,5 @@
<script>
import eventHub from '../event_hub';
import { capitalize, lowerCase, isEmpty } from 'lodash';
import { __, sprintf } from '~/locale';
import { GlFormGroup, GlFormCheckbox, GlFormInput, GlFormSelect, GlFormTextarea } from '@gitlab/ui';
@ -54,6 +55,7 @@ export default {
data() {
return {
model: this.value,
validated: false,
};
},
computed: {
@ -106,17 +108,35 @@ export default {
name: this.fieldName,
};
},
valid() {
return !this.required || !isEmpty(this.model) || !this.validated;
},
},
created() {
if (this.isNonEmptyPassword) {
this.model = null;
}
eventHub.$on('validateForm', this.validateForm);
},
beforeDestroy() {
eventHub.$off('validateForm', this.validateForm);
},
methods: {
validateForm() {
this.validated = true;
},
},
};
</script>
<template>
<gl-form-group :label="label" :label-for="fieldId" :description="help">
<gl-form-group
:label="label"
:label-for="fieldId"
:invalid-feedback="__('This field is required.')"
:state="valid"
:description="help"
>
<template v-if="isCheckbox">
<input :name="fieldName" type="hidden" value="false" />
<gl-form-checkbox v-model="model" v-bind="sharedProps">

View File

@ -1,12 +1,31 @@
<script>
import { GlFormCheckbox, GlFormRadio } from '@gitlab/ui';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { s__ } from '~/locale';
import { GlFormGroup, GlFormCheckbox, GlFormRadio } from '@gitlab/ui';
const commentDetailOptions = [
{
value: 'standard',
label: s__('Integrations|Standard'),
help: s__('Integrations|Includes commit title and branch'),
},
{
value: 'all_details',
label: s__('Integrations|All details'),
help: s__(
'Integrations|Includes Standard plus entire commit message, commit hash, and issue IDs',
),
},
];
export default {
name: 'JiraTriggerFields',
components: {
GlFormGroup,
GlFormCheckbox,
GlFormRadio,
},
mixins: [glFeatureFlagsMixin()],
props: {
initialTriggerCommit: {
type: Boolean,
@ -32,13 +51,71 @@ export default {
triggerMergeRequest: this.initialTriggerMergeRequest,
enableComments: this.initialEnableComments,
commentDetail: this.initialCommentDetail,
commentDetailOptions,
};
},
computed: {
showEnableComments() {
return this.triggerCommit || this.triggerMergeRequest;
},
},
};
</script>
<template>
<div class="form-group row pt-2" role="group">
<div v-if="glFeatures.integrationFormRefactor">
<gl-form-group
:label="__('Trigger')"
label-for="service[trigger]"
:description="
s__(
'Integrations|When a Jira issue is mentioned in a commit or merge request a remote link and comment (if enabled) will be created.',
)
"
>
<input name="service[commit_events]" type="hidden" value="false" />
<gl-form-checkbox v-model="triggerCommit" name="service[commit_events]">
{{ __('Commit') }}
</gl-form-checkbox>
<input name="service[merge_requests_events]" type="hidden" value="false" />
<gl-form-checkbox v-model="triggerMergeRequest" name="service[merge_requests_events]">
{{ __('Merge request') }}
</gl-form-checkbox>
</gl-form-group>
<gl-form-group
v-show="showEnableComments"
:label="s__('Integrations|Comment settings:')"
data-testid="comment-settings"
>
<input name="service[comment_on_event_enabled]" type="hidden" value="false" />
<gl-form-checkbox v-model="enableComments" name="service[comment_on_event_enabled]">
{{ s__('Integrations|Enable comments') }}
</gl-form-checkbox>
</gl-form-group>
<gl-form-group
v-show="showEnableComments && enableComments"
:label="s__('Integrations|Comment detail:')"
data-testid="comment-detail"
>
<gl-form-radio
v-for="commentDetailOption in commentDetailOptions"
:key="commentDetailOption.value"
v-model="commentDetail"
:value="commentDetailOption.value"
name="service[comment_detail]"
>
{{ commentDetailOption.label }}
<template #help>
{{ commentDetailOption.help }}
</template>
</gl-form-radio>
</gl-form-group>
</div>
<div v-else class="form-group row pt-2" role="group">
<label for="service[trigger]" class="col-form-label col-sm-2 pt-0">{{ __('Trigger') }}</label>
<div class="col-sm-10">
<label class="weight-normal mb-2">
@ -76,20 +153,16 @@ export default {
<label>
{{ s__('Integrations|Comment detail:') }}
</label>
<gl-form-radio v-model="commentDetail" value="standard" name="service[comment_detail]">
{{ s__('Integrations|Standard') }}
<gl-form-radio
v-for="commentDetailOption in commentDetailOptions"
:key="commentDetailOption.value"
v-model="commentDetail"
:value="commentDetailOption.value"
name="service[comment_detail]"
>
{{ commentDetailOption.label }}
<template #help>
{{ s__('Integrations|Includes commit title and branch') }}
</template>
</gl-form-radio>
<gl-form-radio v-model="commentDetail" value="all_details" name="service[comment_detail]">
{{ s__('Integrations|All details') }}
<template #help>
{{
s__(
'Integrations|Includes Standard plus entire commit message, commit hash, and issue IDs',
)
}}
{{ commentDetailOption.help }}
</template>
</gl-form-radio>
</div>

View File

@ -45,10 +45,15 @@ export default class IntegrationSettingsForm {
// 2) If this service can be tested
// If both conditions are true, we override form submission
// and test the service using provided configuration.
if (this.$form.get(0).checkValidity() && this.canTestService) {
if (this.$form.get(0).checkValidity()) {
if (this.canTestService) {
e.preventDefault();
// eslint-disable-next-line no-jquery/no-serialize
this.testSettings(this.$form.serialize());
}
} else {
e.preventDefault();
// eslint-disable-next-line no-jquery/no-serialize
this.testSettings(this.$form.serialize());
eventHub.$emit('validateForm');
}
}

View File

@ -1,7 +1,11 @@
<script>
import { GlAlert, GlLabel } from '@gitlab/ui';
import getIssuesListDetailsQuery from '../queries/get_issues_list_details.query.graphql';
import { calculateJiraImportLabel, isFinished, isInProgress } from '~/jira_import/utils';
import {
calculateJiraImportLabel,
isFinished,
isInProgress,
} from '~/jira_import/utils/jira_import_utils';
export default {
name: 'IssuableListRoot',

View File

@ -4,7 +4,8 @@ import { last } from 'lodash';
import { __ } from '~/locale';
import getJiraImportDetailsQuery from '../queries/get_jira_import_details.query.graphql';
import initiateJiraImportMutation from '../queries/initiate_jira_import.mutation.graphql';
import { IMPORT_STATE, isInProgress, extractJiraProjectsOptions } from '../utils';
import { addInProgressImportToStore } from '../utils/cache_update';
import { isInProgress, extractJiraProjectsOptions } from '../utils/jira_import_utils';
import JiraImportForm from './jira_import_form.vue';
import JiraImportProgress from './jira_import_progress.vue';
import JiraImportSetup from './jira_import_setup.vue';
@ -20,14 +21,14 @@ export default {
JiraImportSetup,
},
props: {
isJiraConfigured: {
type: Boolean,
required: true,
},
inProgressIllustration: {
type: String,
required: true,
},
isJiraConfigured: {
type: Boolean,
required: true,
},
issuesPath: {
type: String,
required: true,
@ -62,9 +63,10 @@ export default {
};
},
update: ({ project }) => ({
projects: extractJiraProjectsOptions(project.services.nodes[0].projects.nodes),
status: project.jiraImportStatus,
imports: project.jiraImports.nodes,
isInProgress: isInProgress(project.jiraImportStatus),
mostRecentImport: last(project.jiraImports.nodes),
projects: extractJiraProjectsOptions(project.services.nodes[0].projects.nodes),
}),
skip() {
return !this.isJiraConfigured;
@ -72,32 +74,22 @@ export default {
},
},
computed: {
isImportInProgress() {
return isInProgress(this.jiraImportDetails.status);
},
mostRecentImport() {
// The backend returns JiraImports ordered by created_at asc in app/models/project.rb
return last(this.jiraImportDetails.imports);
},
numberOfPreviousImportsForProject() {
numberOfPreviousImports() {
return this.jiraImportDetails.imports?.reduce?.(
(acc, jiraProject) => (jiraProject.jiraProjectKey === this.selectedProject ? acc + 1 : acc),
0,
);
},
hasPreviousImports() {
return this.numberOfPreviousImports > 0;
},
importLabel() {
return this.selectedProject
? `jira-import::${this.selectedProject}-${this.numberOfPreviousImportsForProject + 1}`
? `jira-import::${this.selectedProject}-${this.numberOfPreviousImports + 1}`
: 'jira-import::KEY-1';
},
hasPreviousImports() {
return this.numberOfPreviousImportsForProject > 0;
},
},
methods: {
dismissAlert() {
this.showAlert = false;
},
initiateJiraImport(project) {
this.$apollo
.mutate({
@ -108,39 +100,8 @@ export default {
jiraProjectKey: project,
},
},
update: (store, { data }) => {
if (data.jiraImportStart.errors.length) {
return;
}
const cacheData = store.readQuery({
query: getJiraImportDetailsQuery,
variables: {
fullPath: this.projectPath,
},
});
store.writeQuery({
query: getJiraImportDetailsQuery,
variables: {
fullPath: this.projectPath,
},
data: {
project: {
jiraImportStatus: IMPORT_STATE.SCHEDULED,
jiraImports: {
nodes: [
...cacheData.project.jiraImports.nodes,
data.jiraImportStart.jiraImport,
],
__typename: 'JiraImportConnection',
},
// eslint-disable-next-line @gitlab/require-i18n-strings
__typename: 'Project',
},
},
});
},
update: (store, { data }) =>
addInProgressImportToStore(store, data.jiraImportStart, this.projectPath),
})
.then(({ data }) => {
if (data.jiraImportStart.errors.length) {
@ -155,7 +116,13 @@ export default {
this.errorMessage = message;
this.showAlert = true;
},
dismissAlert() {
this.showAlert = false;
},
},
previousImportsMessage: __(
'You have imported from this project %{numberOfPreviousImports} times before. Each new import will create duplicate issues.',
),
};
</script>
@ -165,16 +132,8 @@ export default {
{{ errorMessage }}
</gl-alert>
<gl-alert v-if="hasPreviousImports" variant="warning" :dismissible="false">
<gl-sprintf
:message="
__(
'You have imported from this project %{numberOfPreviousImportsForProject} times before. Each new import will create duplicate issues.',
)
"
>
<template #numberOfPreviousImportsForProject>{{
numberOfPreviousImportsForProject
}}</template>
<gl-sprintf :message="$options.previousImportsMessage">
<template #numberOfPreviousImports>{{ numberOfPreviousImports }}</template>
</gl-sprintf>
</gl-alert>
@ -185,11 +144,11 @@ export default {
/>
<gl-loading-icon v-else-if="$apollo.loading" size="md" class="mt-3" />
<jira-import-progress
v-else-if="isImportInProgress"
v-else-if="jiraImportDetails.isInProgress"
:illustration="inProgressIllustration"
:import-initiator="mostRecentImport.scheduledBy.name"
:import-project="mostRecentImport.jiraProjectKey"
:import-time="mostRecentImport.scheduledAt"
:import-initiator="jiraImportDetails.mostRecentImport.scheduledBy.name"
:import-project="jiraImportDetails.mostRecentImport.jiraProjectKey"
:import-time="jiraImportDetails.mostRecentImport.scheduledAt"
:issues-path="issuesPath"
/>
<jira-import-form

View File

@ -0,0 +1,37 @@
import getJiraImportDetailsQuery from '../queries/get_jira_import_details.query.graphql';
import { IMPORT_STATE } from './jira_import_utils';
export const addInProgressImportToStore = (store, jiraImportStart, fullPath) => {
if (jiraImportStart.errors.length) {
return;
}
const queryDetails = {
query: getJiraImportDetailsQuery,
variables: {
fullPath,
},
};
const cacheData = store.readQuery({
...queryDetails,
});
store.writeQuery({
...queryDetails,
data: {
project: {
...cacheData.project,
jiraImportStatus: IMPORT_STATE.SCHEDULED,
jiraImports: {
...cacheData.project.jiraImports,
nodes: cacheData.project.jiraImports.nodes.concat(jiraImportStart.jiraImport),
},
},
},
});
};
export default {
addInProgressImportToStore,
};

View File

@ -20,6 +20,7 @@ class Projects::BlameController < Projects::ApplicationController
environment_params[:find_latest] = true
@environment = EnvironmentsFinder.new(@project, current_user, environment_params).execute.last
@blame_groups = Gitlab::Blame.new(@blob, @commit).groups
@blame = Gitlab::Blame.new(@blob, @commit)
@blame = Gitlab::View::Presenter::Factory.new(@blame, project: @project, path: @path).fabricate!
end
end

View File

@ -11,6 +11,9 @@ class Projects::ServicesController < Projects::ApplicationController
before_action :web_hook_logs, only: [:edit, :update]
before_action :set_deprecation_notice_for_prometheus_service, only: [:edit, :update]
before_action :redirect_deprecated_prometheus_service, only: [:update]
before_action only: :edit do
push_frontend_feature_flag(:integration_form_refactor)
end
respond_to :html

View File

@ -55,6 +55,7 @@ module TodosHelper
def todo_target_type_name(todo)
return _('design') if todo.for_design?
return _('alert') if todo.for_alert?
todo.target_type.titleize.downcase
end
@ -68,6 +69,8 @@ module TodosHelper
project_commit_path(todo.project, todo.target, path_options)
elsif todo.for_design?
todos_design_path(todo, path_options)
elsif todo.for_alert?
details_project_alert_management_path(todo.project, todo.target)
else
path = [todo.resource_parent, todo.target]

View File

@ -4,6 +4,7 @@ require_dependency 'alert_management'
module AlertManagement
class Alert < ApplicationRecord
include IidRoutes
include AtomicInternalId
include ShaAttribute
include Sortable
@ -143,6 +144,12 @@ module AlertManagement
increment!(:events)
end
# required for todos (typically contains an identifier like issue iid)
# no-op; we could use iid, but we don't have a reference prefix
def to_reference(_from = nil, full: false)
''
end
private
def hosts_length

View File

@ -11,12 +11,7 @@ module PerformanceMonitoring
class << self
def from_json(json_content)
dashboard = new(
dashboard: json_content['dashboard'],
panel_groups: json_content['panel_groups']&.map { |group| PrometheusPanelGroup.from_json(group) }
)
dashboard.tap(&:validate!)
build_from_hash(json_content).tap(&:validate!)
end
def find_for(project:, user:, path:, options: {})
@ -30,6 +25,17 @@ module PerformanceMonitoring
}.merge(dashboard_response[:dashboard])
)
end
private
def build_from_hash(attributes)
return new unless attributes.is_a?(Hash)
new(
dashboard: attributes['dashboard'],
panel_groups: attributes['panel_groups']&.map { |group| PrometheusPanelGroup.from_json(group) }
)
end
end
def to_yaml

View File

@ -10,16 +10,24 @@ module PerformanceMonitoring
validates :query, presence: true, unless: :query_range
validates :query_range, presence: true, unless: :query
def self.from_json(json_content)
metric = PrometheusMetric.new(
id: json_content['id'],
unit: json_content['unit'],
label: json_content['label'],
query: json_content['query'],
query_range: json_content['query_range']
)
class << self
def from_json(json_content)
build_from_hash(json_content).tap(&:validate!)
end
metric.tap(&:validate!)
private
def build_from_hash(attributes)
return new unless attributes.is_a?(Hash)
new(
id: attributes['id'],
unit: attributes['unit'],
label: attributes['label'],
query: attributes['query'],
query_range: attributes['query_range']
)
end
end
end
end

View File

@ -8,17 +8,24 @@ module PerformanceMonitoring
validates :title, presence: true
validates :metrics, presence: true
class << self
def from_json(json_content)
build_from_hash(json_content).tap(&:validate!)
end
def self.from_json(json_content)
panel = new(
type: json_content['type'],
title: json_content['title'],
y_label: json_content['y_label'],
weight: json_content['weight'],
metrics: json_content['metrics']&.map { |metric| PrometheusMetric.from_json(metric) }
)
private
panel.tap(&:validate!)
def build_from_hash(attributes)
return new unless attributes.is_a?(Hash)
new(
type: attributes['type'],
title: attributes['title'],
y_label: attributes['y_label'],
weight: attributes['weight'],
metrics: attributes['metrics']&.map { |metric| PrometheusMetric.from_json(metric) }
)
end
end
def id(group_title)

View File

@ -8,15 +8,22 @@ module PerformanceMonitoring
validates :group, presence: true
validates :panels, presence: true
class << self
def from_json(json_content)
build_from_hash(json_content).tap(&:validate!)
end
def self.from_json(json_content)
panel_group = new(
group: json_content['group'],
priority: json_content['priority'],
panels: json_content['panels']&.map { |panel| PrometheusPanel.from_json(panel) }
)
private
panel_group.tap(&:validate!)
def build_from_hash(attributes)
return new unless attributes.is_a?(Hash)
new(
group: attributes['group'],
priority: attributes['priority'],
panels: attributes['panels']&.map { |panel| PrometheusPanel.from_json(panel) }
)
end
end
end
end

View File

@ -189,6 +189,10 @@ class Todo < ApplicationRecord
target_type == DesignManagement::Design.name
end
def for_alert?
target_type == AlertManagement::Alert.name
end
# override to return commits, which are not active record
def target
if for_commit?

View File

@ -0,0 +1,82 @@
# frozen_string_literal: true
module Gitlab
class BlamePresenter < Gitlab::View::Presenter::Simple
include ActionView::Helpers::UrlHelper
include ActionView::Helpers::TranslationHelper
include ActionView::Context
include AvatarsHelper
include BlameHelper
include CommitsHelper
include ApplicationHelper
include TreeHelper
include IconsHelper
presents :blame
CommitData = Struct.new(
:author_avatar,
:age_map_class,
:commit_link,
:commit_author_link,
:project_blame_link,
:time_ago_tooltip)
def initialize(subject, **attributes)
super
@commits = {}
precalculate_data_by_commit!
end
def groups
@groups ||= blame.groups
end
def commit_data(commit)
@commits[commit.id] ||= get_commit_data(commit)
end
private
# Huge source files with a high churn rate (e.g. 'locale/gitlab.pot') could have
# 10x times more blame groups than unique commits across all the groups.
# That means we could cache per-commit data we need
# to avoid recalculating it multiple times.
# For such files, it could significantly improve the performance of the Blame.
def precalculate_data_by_commit!
groups.each { |group| commit_data(group[:commit]) }
end
def get_commit_data(commit)
CommitData.new.tap do |data|
data.author_avatar = author_avatar(commit, size: 36, has_tooltip: false)
data.age_map_class = age_map_class(commit.committed_date, project_duration)
data.commit_link = link_to commit.title, project_commit_path(project, commit.id), class: "cdark", title: commit.title
data.commit_author_link = commit_author_link(commit, avatar: false)
data.project_blame_link = project_blame_link(commit)
data.time_ago_tooltip = time_ago_with_tooltip(commit.committed_date)
end
end
def project_blame_link(commit)
previous_commit_id = commit.parent_id
return unless previous_commit_id
link_to project_blame_path(project, tree_join(previous_commit_id, path)),
title: _('View blame prior to this change'),
aria: { label: _('View blame prior to this change') },
data: { toggle: 'tooltip', placement: 'right', container: 'body' } do
versions_sprite_icon
end
end
def project_duration
@project_duration ||= age_map_duration(groups, project)
end
def versions_sprite_icon
@versions_sprite_icon ||= sprite_icon('doc-versions', size: 16, css_class: 'doc-versions align-text-bottom')
end
end
end

View File

@ -0,0 +1,26 @@
%tr
%td.blame-commit{ class: commit_data.age_map_class }
.commit
= commit_data.author_avatar
.commit-row-title
%span.item-title.str-truncated-100
= commit_data.commit_link
%span
= commit_data.project_blame_link
&nbsp;
.light
= commit_data.commit_author_link
= _('committed')
#{commit_data.time_ago_tooltip}
%td.line-numbers
- line_count = blame_group[:lines].count
- (current_line...(current_line + line_count)).each do |i|
%a.diff-line-num{ href: "#L#{i}", id: "L#{i}", 'data-line-number' => i }
= link_icon
= i
\
%td.lines
%pre.code.highlight
%code
- blame_group[:lines].each do |line|
#{line}

View File

@ -1,5 +1,5 @@
- project_duration = age_map_duration(@blame_groups, @project)
- page_title "Blame", @blob.path, @ref
- link_icon = icon("link")
#blob-content-holder.tree-holder
= render "projects/blob/breadcrumb", blob: @blob, blame: true
@ -11,38 +11,13 @@
.table-responsive.file-content.blame.code.js-syntax-highlight
%table
- current_line = 1
- @blame_groups.each do |blame_group|
%tr
- commit = blame_group[:commit]
%td.blame-commit{ class: age_map_class(commit.committed_date, project_duration) }
.commit
= author_avatar(commit, size: 36, has_tooltip: false)
.commit-row-title
%span.item-title.str-truncated-100
= link_to commit.title, project_commit_path(@project, commit.id), class: "cdark", title: commit.title
%span
- previous_commit_id = commit.parent_id
- if previous_commit_id
= link_to project_blame_path(@project, tree_join(previous_commit_id, @path)),
title: _('View blame prior to this change'),
aria: { label: _('View blame prior to this change') },
data: { toggle: 'tooltip', placement: 'right', container: 'body' } do
= sprite_icon('doc-versions', size: 16, css_class: 'doc-versions align-text-bottom')
&nbsp;
.light
= commit_author_link(commit, avatar: false)
committed
#{time_ago_with_tooltip(commit.committed_date)}
%td.line-numbers
- line_count = blame_group[:lines].count
- (current_line...(current_line + line_count)).each do |i|
%a.diff-line-num{ href: "#L#{i}", id: "L#{i}", 'data-line-number' => i }
= icon("link")
= i
\
- current_line += line_count
%td.lines
%pre.code.highlight
%code
- blame_group[:lines].each do |line|
#{line}
- @blame.groups.each do |blame_group|
- commit_data = @blame.commit_data(blame_group[:commit])
= render 'blame_group',
blame_group: blame_group,
current_line: current_line,
link_icon: link_icon,
commit_data: commit_data
- current_line += blame_group[:lines].count

View File

@ -0,0 +1,5 @@
---
title: Remove FF hide_token_from_runners_api
merge_request: 33947
author:
type: other

View File

@ -0,0 +1,6 @@
---
title: Fixed dashboard YAML file validaiton for files which do not contain object
as root element
merge_request: 33935
author:
type: fixed

View File

@ -19,6 +19,7 @@ exceptions:
- CSS
- CSV
- DNS
- EKS
- GET
- GNU
- GPG

View File

@ -47,15 +47,15 @@ GET /projects/:id/vulnerability_findings?pipeline_id=42
```
CAUTION: **Deprecation:**
Beginning with GitLab 12.9, the `undefined` severity level is deprecated and the `undefined` confidence level isn't reported for new vulnerabilities.
Beginning with GitLab 12.9, the `undefined` severity and confidence level is no longer reported.
| Attribute | Type | Required | Description |
| ------------- | -------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) which the authenticated user is a member of. |
| `report_type` | string array | no | Returns vulnerability findings belonging to specified report type. Valid values: `sast`, `dast`, `dependency_scanning`, or `container_scanning`. Defaults to all. |
| `scope` | string | no | Returns vulnerability findings for the given scope: `all` or `dismissed`. Defaults to `dismissed`. |
| `severity` | string array | no | Returns vulnerability findings belonging to specified severity level: `undefined`, `info`, `unknown`, `low`, `medium`, `high`, or `critical`. Defaults to all. |
| `confidence` | string array | no | Returns vulnerability findings belonging to specified confidence level: `undefined`, `ignore`, `unknown`, `experimental`, `low`, `medium`, `high`, or `confirmed`. Defaults to all. |
| `severity` | string array | no | Returns vulnerability findings belonging to specified severity level: `info`, `unknown`, `low`, `medium`, `high`, or `critical`. Defaults to all. |
| `confidence` | string array | no | Returns vulnerability findings belonging to specified confidence level: `ignore`, `unknown`, `experimental`, `low`, `medium`, `high`, or `confirmed`. Defaults to all. |
| `pipeline_id` | integer/string | no | Returns vulnerability findings belonging to specified pipeline. |
```shell

View File

@ -456,7 +456,8 @@ On GitLab.com, we have DangerBot setup to monitor Telemetry related files and Da
| `dependency_list_usages_total` | `counts` | | |
| `epics` | `counts` | | |
| `feature_flags` | `counts` | | |
| `geo_nodes` | `counts` | | |
| `geo_nodes` | `counts` | `geo` | Number of sites in a Geo deployment |
| `geo_event_log_max_id` | `counts` | `geo` | Number of replication events on a Geo primary |
| `incident_issues` | `counts` | `monitor` | Issues created by the alert bot |
| `alert_bot_incident_issues` | `counts` | `monitor` | Issues created by the alert bot |
| `incident_labeled_issues` | `counts` | `monitor` | Issues with the incident label |

View File

@ -803,7 +803,7 @@ restore:
```shell
# This command will overwrite the contents of your GitLab database!
sudo gitlab-backup restore BACKUP=1493107454_2018_04_25_10.6.4-ce
sudo gitlab-backup restore BACKUP=11493107454_2018_04_25_10.6.4-ce
```
NOTE: **Note**

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

View File

@ -52,29 +52,29 @@ NOTE: **Note:**
set up by administrators. However, you can turn this off by disabling the
**Allow requests to the local network from system hooks** option.
## Whitelist for local requests
## Allowlist for local requests
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/44496) in GitLab 12.2
You can allow certain domains and IP addresses to be accessible to both *system hooks*
and *webhooks* even when local requests are not allowed by adding them to the
whitelist. Navigate to **Admin Area > Settings > Network** (`/admin/application_settings/network`)
allowlist. Navigate to **Admin Area > Settings > Network** (`/admin/application_settings/network`)
and expand **Outbound requests**:
![Outbound local requests whitelist](img/whitelist.png)
![Outbound local requests allowlist](img/allowlist_v13_0.png)
The whitelist entries can be separated by semicolons, commas or whitespaces
The allowed entries can be separated by semicolons, commas or whitespaces
(including newlines) and be in different formats like hostnames, IP addresses and/or
IP ranges. IPv6 is supported. Hostnames that contain unicode characters should
use IDNA encoding.
The whitelist can hold a maximum of 1000 entries. Each entry can be a maximum of
The allowlist can hold a maximum of 1000 entries. Each entry can be a maximum of
255 characters.
You can whitelist a particular port by specifying it in the whitelist entry.
You can allow a particular port by specifying it in the allowlist entry.
For example `127.0.0.1:8080` will only allow connections to port 8080 on `127.0.0.1`.
If no port is mentioned, all ports on that IP/domain are whitelisted. An IP range
will whitelist all ports on all IPs in that range.
If no port is mentioned, all ports on that IP/domain are allowed. An IP range
will allow all ports on all IPs in that range.
Example:

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

View File

@ -35,6 +35,17 @@ shows a total of 15 months for the chart in the GitLab.org group.
![Issues created per month](img/issues_created_per_month_v12_8.png)
## Drill into the information
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/196547) in GitLab 13.1.
You can examine details of individual issues by browsing the table
located below the chart.
The chart displays the top 100 issues based on the global page filters.
![Issues table](img/issues_table_v13_1.png)
<!-- ## Troubleshooting
Include any troubleshooting steps that you can foresee. If you know beforehand what issues

View File

@ -164,6 +164,41 @@ You will need to add your AWS external ID to the
[IAM Role in the AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-role.html#cli-configure-role-xaccount)
to manage your cluster using `kubectl`.
### Troubleshooting creating a new cluster
The following errors are commonly encountered when creating a new cluster.
#### Error: Request failed with status code 422
When submitting the initial authentication form, GitLab returns a status code 422
error when it can't determine the role you've provided. Make sure you've
correctly configured your role with the **Account ID** and **External ID**
provided by GitLab. In GitLab, make sure to enter the correct **Role ARN**.
#### Could not load Security Groups for this VPC
When populating options in the configuration form, GitLab returns this error
because GitLab has successfully assumed your provided role, but the role has
insufficient permissions to retrieve the resources needed for the form. Make sure
you've assigned the role the correct permissions.
#### `ROLLBACK_FAILED` during cluster creation
The creation process halted because GitLab encountered an error when creating
one or more resources. You can inspect the associated
[CloudFormation stack](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-console-view-stack-data-resources.html)
to find the specific resources that failed to create.
If the `Cluster` resource failed with the error
`The provided role doesn't have the Amazon EKS Managed Policies associated with it.`,
the role specified in **Role name** is not configured correctly.
NOTE: **Note:**
This role should not be the same as the one created above. If you don't have an
existing
[EKS cluster IAM role](https://docs.aws.amazon.com/eks/latest/userguide/service_IAM_role.html),
you must create one.
## Existing EKS cluster
To add an existing EKS cluster to your project, group, or instance:

View File

@ -100,7 +100,7 @@ When you create a project in GitLab, you'll have access to a large number of
- [Maven packages](../packages/maven_repository/index.md): your private Maven repository in GitLab. **(PREMIUM)**
- [NPM packages](../packages/npm_registry/index.md): your private NPM package registry in GitLab. **(PREMIUM)**
- [Code owners](code_owners.md): specify code owners for certain files **(STARTER)**
- [License Compliance](../compliance/license_compliance/index.md): approve and blacklist licenses for projects. **(ULTIMATE)**
- [License Compliance](../compliance/license_compliance/index.md): approve and deny licenses for projects. **(ULTIMATE)**
- [Dependency List](../application_security/dependency_list/index.md): view project dependencies. **(ULTIMATE)**
- [Requirements](requirements/index.md): Requirements allow you to create criteria to check your products against. **(ULTIMATE)**
- [Static Site Editor](static_site_editor/index.md): quickly edit content on static websites without prior knowledge of the codebase or Git commands.

View File

@ -385,7 +385,7 @@ The following tables outline the details of expected properties.
| Property | Type | Required | Description |
| -------- | ---- | -------- | ----------- |
| `variables` | hash | no | Variables can be defined here. |
| `variables` | hash | yes | Variables can be defined here. |
Read the documentation on [templating](#templating-variables-for-metrics-dashboards).

View File

@ -11,13 +11,6 @@ module API
expose :version, :revision, :platform, :architecture
expose :contacted_at
# Will be removed: https://gitlab.com/gitlab-org/gitlab/-/issues/217105
expose(:token, if: ->(runner, options) do
return false if ::Feature.enabled?(:hide_token_from_runners_api, default_enabled: true)
options[:current_user].admin? || !runner.instance_type?
end)
# rubocop: disable CodeReuse/ActiveRecord
expose :projects, with: Entities::BasicProjectDetails do |runner, options|
if options[:current_user].admin?

View File

@ -45,7 +45,7 @@ module Gitlab
def license_usage_data
{
recorded_at: Time.now, # should be calculated very first
recorded_at: recorded_at,
uuid: alt_usage_data { Gitlab::CurrentSettings.uuid },
hostname: alt_usage_data { Gitlab.config.gitlab.host },
version: alt_usage_data { Gitlab::VERSION },
@ -55,6 +55,10 @@ module Gitlab
}
end
def recorded_at
Time.now
end
def recording_ce_finish_data
{
recording_ce_finished_at: Time.now

View File

@ -8248,6 +8248,9 @@ msgstr ""
msgid "Enable header and footer in emails"
msgstr ""
msgid "Enable integration"
msgstr ""
msgid "Enable maintenance mode"
msgstr ""
@ -15118,6 +15121,15 @@ msgstr ""
msgid "On track"
msgstr ""
msgid "OnDemandScans|Create new DAST scan"
msgstr ""
msgid "OnDemandScans|On-demand Scans"
msgstr ""
msgid "OnDemandScans|Schedule or run scans immediately against target sites. Currently available on-demand scan type: DAST. %{helpLinkStart}More information%{helpLinkEnd}"
msgstr ""
msgid "Onboarding"
msgstr ""
@ -25495,7 +25507,7 @@ msgstr ""
msgid "You have declined the invitation to join %{label}."
msgstr ""
msgid "You have imported from this project %{numberOfPreviousImportsForProject} times before. Each new import will create duplicate issues."
msgid "You have imported from this project %{numberOfPreviousImports} times before. Each new import will create duplicate issues."
msgstr ""
msgid "You have no permissions"
@ -25923,6 +25935,9 @@ msgstr ""
msgid "ago"
msgstr ""
msgid "alert"
msgstr ""
msgid "allowed to fail"
msgstr ""
@ -26235,6 +26250,9 @@ msgstr ""
msgid "commit %{commit_id}"
msgstr ""
msgid "committed"
msgstr ""
msgid "confidentiality|You are going to turn off the confidentiality. This means <strong>everyone</strong> will be able to see and leave a comment on this issue."
msgstr ""

View File

@ -2,12 +2,10 @@
require 'spec_helper'
describe 'Admin activates Prometheus' do
describe 'Admin activates Prometheus', :js do
let(:admin) { create(:user, :admin) }
before do
stub_feature_flags(integration_form_refactor: false)
sign_in(admin)
visit(admin_application_settings_services_path)

View File

@ -2,10 +2,10 @@
require 'spec_helper'
RSpec.describe 'Disable individual triggers' do
RSpec.describe 'Disable individual triggers', :js do
include_context 'project service activation'
let(:checkbox_selector) { 'input[type=checkbox][id$=_events]' }
let(:checkbox_selector) { 'input[type=checkbox][name$="_events]"]' }
before do
visit_project_integration(service_name)

View File

@ -23,6 +23,6 @@ RSpec.describe 'User activates Atlassian Bamboo CI' do
# Password field should not be filled in.
click_link('Atlassian Bamboo CI')
expect(find_field('Enter new password').value).to be_blank
expect(find_field('Enter new Password').value).to be_blank
end
end

View File

@ -12,7 +12,7 @@ RSpec.describe 'User activates JetBrains TeamCity CI' do
it 'activates service', :js do
visit_project_integration('JetBrains TeamCity CI')
check('Push')
check('Merge request')
check('Merge Request')
fill_in('Teamcity url', with: 'http://teamcity.example.com')
fill_in('Build type', with: 'GitlabTest_Build')
fill_in('Username', with: 'user')

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe 'User activates Slack notifications' do
RSpec.describe 'User activates Slack notifications', :js do
include_context 'project service activation'
context 'when service is not configured yet' do
@ -10,7 +10,7 @@ RSpec.describe 'User activates Slack notifications' do
visit_project_integration('Slack notifications')
end
it 'activates service', :js do
it 'activates service' do
fill_in('Webhook', with: 'https://hooks.slack.com/services/SVRWFV0VVAR97N/B02R25XN3/ZBqu7xMupaEEICInN685')
click_test_then_save_integration
@ -38,13 +38,13 @@ RSpec.describe 'User activates Slack notifications' do
end
it 'filters events by channel' do
expect(page.find_field('service_push_channel').value).to have_content('1')
expect(page.find_field('service_issue_channel').value).to have_content('2')
expect(page.find_field('service_merge_request_channel').value).to have_content('3')
expect(page.find_field('service_note_channel').value).to have_content('4')
expect(page.find_field('service_tag_push_channel').value).to have_content('5')
expect(page.find_field('service_pipeline_channel').value).to have_content('6')
expect(page.find_field('service_wiki_page_channel').value).to have_content('7')
expect(page.find_field(name: 'service[push_channel]').value).to have_content('1')
expect(page.find_field(name: 'service[issue_channel]').value).to have_content('2')
expect(page.find_field(name: 'service[merge_request_channel]').value).to have_content('3')
expect(page.find_field(name: 'service[note_channel]').value).to have_content('4')
expect(page.find_field(name: 'service[tag_push_channel]').value).to have_content('5')
expect(page.find_field(name: 'service[pipeline_channel]').value).to have_content('6')
expect(page.find_field(name: 'service[wiki_page_channel]').value).to have_content('7')
end
end
end

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe 'Slack slash commands' do
RSpec.describe 'Slack slash commands', :js do
include_context 'project service activation'
before do
@ -10,7 +10,7 @@ RSpec.describe 'Slack slash commands' do
end
it 'shows a token placeholder' do
token_placeholder = find_field('service_token')['placeholder']
token_placeholder = find_field('Token')['placeholder']
expect(token_placeholder).to eq('XXxxXXxxXXxxXXxxXXxxXXxx')
end
@ -19,8 +19,8 @@ RSpec.describe 'Slack slash commands' do
expect(page).to have_content('This service allows users to perform common')
end
it 'redirects to the integrations page after saving but not activating', :js do
fill_in 'service_token', with: 'token'
it 'redirects to the integrations page after saving but not activating' do
fill_in 'Token', with: 'token'
click_active_toggle
click_on 'Save'
@ -28,8 +28,8 @@ RSpec.describe 'Slack slash commands' do
expect(page).to have_content('Slack slash commands settings saved, but not activated.')
end
it 'redirects to the integrations page after activating', :js do
fill_in 'service_token', with: 'token'
it 'redirects to the integrations page after activating' do
fill_in 'Token', with: 'token'
click_on 'Save'
expect(current_path).to eq(project_settings_integrations_path(project))

View File

@ -1,5 +1,18 @@
dashboard: 'Test Dashboard'
priority: 1
links:
- title: Link 1
url: https://gitlab.com
- title: Link 2
url: https://docs.gitlab.com
templating:
variables:
text_variable_full_syntax:
label: 'Variable 1'
type: text
options:
default_value: 'default'
text_variable_simple_syntax: 'default value'
panel_groups:
- group: Group A
priority: 1

View File

@ -11,7 +11,9 @@
"panel_groups": {
"type": "array",
"items": { "$ref": "spec/fixtures/lib/gitlab/metrics/dashboard/schemas/panel_groups.json" }
}
},
"templating": { "$ref": "spec/fixtures/lib/gitlab/metrics/dashboard/schemas/templating.json" },
"links": { "$ref": "spec/fixtures/lib/gitlab/metrics/dashboard/schemas/links.json" }
},
"additionalProperties": false
}

View File

@ -0,0 +1,9 @@
{
"type": "array",
"required": ["url"],
"properties": {
"url": { "type": "string" },
"title": { "type": "string" }
},
"additionalProperties": false
}

View File

@ -0,0 +1,8 @@
{
"type": "object",
"required": ["variables"],
"properties": {
"variables": { "$ref": "variables.json" }
},
"additionalProperties": false
}

View File

@ -0,0 +1,12 @@
{
"type": "object",
"required": [
"type", "options"
],
"properties": {
"type": { "type": "string" },
"label": { "type": "string" },
"options": { "$ref": "text_variable_options.json" }
},
"additionalProperties": false
}

View File

@ -0,0 +1,7 @@
{
"type": "object",
"properties": {
"default_value": { "type": "string" }
},
"additionalProperties": false
}

View File

@ -0,0 +1,12 @@
{
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9_]*$": {
"anyOf": [
{ "$ref": "text_variable_full_syntax.json" },
{ "type": "string" }
]
}
},
"additionalProperties": false
}

View File

@ -6,14 +6,13 @@ import JiraImportForm from '~/jira_import/components/jira_import_form.vue';
import JiraImportProgress from '~/jira_import/components/jira_import_progress.vue';
import JiraImportSetup from '~/jira_import/components/jira_import_setup.vue';
import initiateJiraImportMutation from '~/jira_import/queries/initiate_jira_import.mutation.graphql';
import { IMPORT_STATE } from '~/jira_import/utils';
const mountComponent = ({
isJiraConfigured = true,
errorMessage = '',
selectedProject = 'MTG',
showAlert = false,
status = IMPORT_STATE.NONE,
isInProgress = false,
loading = false,
mutate = jest.fn(() => Promise.resolve()),
mountType,
@ -22,8 +21,8 @@ const mountComponent = ({
return mountFunction(JiraImportApp, {
propsData: {
isJiraConfigured,
inProgressIllustration: 'in-progress-illustration.svg',
isJiraConfigured,
issuesPath: 'gitlab-org/gitlab-test/-/issues',
jiraIntegrationPath: 'gitlab-org/gitlab-test/-/services/jira/edit',
projectPath: 'gitlab-org/gitlab-test',
@ -35,12 +34,7 @@ const mountComponent = ({
showAlert,
selectedProject,
jiraImportDetails: {
projects: [
{ text: 'My Jira Project (MJP)', value: 'MJP' },
{ text: 'My Second Jira Project (MSJP)', value: 'MSJP' },
{ text: 'Migrate to GitLab (MTG)', value: 'MTG' },
],
status,
isInProgress,
imports: [
{
jiraProjectKey: 'MTG',
@ -64,6 +58,18 @@ const mountComponent = ({
},
},
],
mostRecentImport: {
jiraProjectKey: 'MTG',
scheduledAt: '2020-04-09T16:17:18+00:00',
scheduledBy: {
name: 'Jane Doe',
},
},
projects: [
{ text: 'My Jira Project (MJP)', value: 'MJP' },
{ text: 'My Second Jira Project (MSJP)', value: 'MSJP' },
{ text: 'Migrate to GitLab (MTG)', value: 'MTG' },
],
},
};
},
@ -140,7 +146,7 @@ describe('JiraImportApp', () => {
describe('when Jira integration is configured but import is in progress', () => {
beforeEach(() => {
wrapper = mountComponent({ status: IMPORT_STATE.SCHEDULED });
wrapper = mountComponent({ isInProgress: true });
});
it('does not show the "Set up Jira integration" screen', () => {
@ -184,7 +190,7 @@ describe('JiraImportApp', () => {
describe('import in progress screen', () => {
beforeEach(() => {
wrapper = mountComponent({ status: IMPORT_STATE.SCHEDULED });
wrapper = mountComponent({ isInProgress: true });
});
it('shows the illustration', () => {

View File

@ -0,0 +1,72 @@
import getJiraImportDetailsQuery from '~/jira_import/queries/get_jira_import_details.query.graphql';
import { IMPORT_STATE } from '~/jira_import/utils/jira_import_utils';
export const fullPath = 'gitlab-org/gitlab-test';
export const queryDetails = {
query: getJiraImportDetailsQuery,
variables: {
fullPath,
},
};
export const jiraImportDetailsQueryResponse = {
project: {
jiraImportStatus: IMPORT_STATE.NONE,
jiraImports: {
nodes: [
{
jiraProjectKey: 'MJP',
scheduledAt: '2020-01-01T12:34:56Z',
scheduledBy: {
name: 'Jane Doe',
__typename: 'User',
},
__typename: 'JiraImport',
},
],
__typename: 'JiraImportConnection',
},
services: {
nodes: [
{
projects: {
nodes: [
{
key: 'MJP',
name: 'My Jira Project',
__typename: 'JiraProject',
},
{
key: 'MTG',
name: 'Migrate To GitLab',
__typename: 'JiraProject',
},
],
__typename: 'JiraProjectConnection',
},
__typename: 'JiraService',
},
],
__typename: 'ServiceConnection',
},
__typename: 'Project',
},
};
export const jiraImportMutationResponse = {
jiraImportStart: {
clientMutationId: null,
jiraImport: {
jiraProjectKey: 'MTG',
scheduledAt: '2020-02-02T20:20:20Z',
scheduledBy: {
name: 'John Doe',
__typename: 'User',
},
__typename: 'JiraImport',
},
errors: [],
__typename: 'JiraImportStartPayload',
},
};

View File

@ -0,0 +1,64 @@
import { addInProgressImportToStore } from '~/jira_import/utils/cache_update';
import { IMPORT_STATE } from '~/jira_import/utils/jira_import_utils';
import {
fullPath,
queryDetails,
jiraImportDetailsQueryResponse,
jiraImportMutationResponse,
} from '../mock_data';
describe('addInProgressImportToStore', () => {
const store = {
readQuery: jest.fn(() => jiraImportDetailsQueryResponse),
writeQuery: jest.fn(),
};
describe('when updating the cache', () => {
beforeEach(() => {
addInProgressImportToStore(store, jiraImportMutationResponse.jiraImportStart, fullPath);
});
it('reads the cache with the correct query', () => {
expect(store.readQuery).toHaveBeenCalledWith(queryDetails);
});
it('writes to the cache with the expected arguments', () => {
const expected = {
...queryDetails,
data: {
project: {
...jiraImportDetailsQueryResponse.project,
jiraImportStatus: IMPORT_STATE.SCHEDULED,
jiraImports: {
...jiraImportDetailsQueryResponse.project.jiraImports,
nodes: jiraImportDetailsQueryResponse.project.jiraImports.nodes.concat(
jiraImportMutationResponse.jiraImportStart.jiraImport,
),
},
},
},
};
expect(store.writeQuery).toHaveBeenCalledWith(expected);
});
});
describe('when there are errors', () => {
beforeEach(() => {
const jiraImportStart = {
...jiraImportMutationResponse.jiraImportStart,
errors: ['There was an error'],
};
addInProgressImportToStore(store, jiraImportStart, fullPath);
});
it('does not read from the store', () => {
expect(store.readQuery).not.toHaveBeenCalled();
});
it('does not write to the store', () => {
expect(store.writeQuery).not.toHaveBeenCalled();
});
});
});

View File

@ -4,7 +4,7 @@ import {
IMPORT_STATE,
isFinished,
isInProgress,
} from '~/jira_import/utils';
} from '~/jira_import/utils/jira_import_utils';
describe('isInProgress', () => {
it.each`

View File

@ -20,6 +20,10 @@ describe TodosHelper do
author: author,
note: note)
end
let_it_be(:alert_todo) do
alert = create(:alert_management_alert, iid: 1001)
create(:todo, target: alert)
end
describe '#todos_count_format' do
it 'shows fuzzy count for 100 or more items' do
@ -115,6 +119,18 @@ describe TodosHelper do
expect(path).to eq("#{issue_path}/designs/#{design.filename}##{dom_id(design_todo.note)}")
end
end
context 'when given an alert' do
let(:todo) { alert_todo }
it 'responds with an appropriate path' do
path = helper.todo_target_path(todo)
expect(path).to eq(
"/#{todo.project.full_path}/-/alert_management/#{todo.target.iid}/details"
)
end
end
end
describe '#todo_target_type_name' do
@ -127,6 +143,16 @@ describe TodosHelper do
expect(name).to eq('design')
end
end
context 'when given an alert todo' do
let(:todo) { alert_todo }
it 'responds with an appropriate target type name' do
name = helper.todo_target_type_name(todo)
expect(name).to eq('alert')
end
end
end
describe '#todo_types_options' do

View File

@ -10,6 +10,17 @@ describe Gitlab::UsageData, :aggregate_failures do
stub_object_store_settings
end
describe '#uncached_data' do
it 'ensures recorded_at is set before any other usage data calculation' do
%i(alt_usage_data redis_usage_data distinct_count count).each do |method|
expect(described_class).not_to receive(method)
end
expect(described_class).to receive(:recorded_at).and_raise(Exception.new('Stopped calculating recorded_at'))
expect { described_class.uncached_data }.to raise_error('Stopped calculating recorded_at')
end
end
describe '#data' do
let!(:ud) { build(:usage_data) }

View File

@ -242,6 +242,12 @@ describe AlertManagement::Alert do
end
end
describe '#to_reference' do
let(:alert) { build(:alert_management_alert) }
it { expect(alert.to_reference).to eq('') }
end
describe '#trigger' do
subject { alert.trigger }

View File

@ -47,6 +47,24 @@ describe PerformanceMonitoring::PrometheusDashboard do
end
end
context 'dashboard content is missing' do
let(:json_content) { nil }
it_behaves_like 'validation failed', panel_groups: ["can't be blank"], dashboard: ["can't be blank"]
end
context 'dashboard content is NOT a hash' do
let(:json_content) { YAML.safe_load("'test'") }
it_behaves_like 'validation failed', panel_groups: ["can't be blank"], dashboard: ["can't be blank"]
end
context 'content is an array' do
let(:json_content) { [{ "dashboard" => "Dashboard Title" }] }
it_behaves_like 'validation failed', panel_groups: ["can't be blank"], dashboard: ["can't be blank"]
end
context 'dashboard definition is missing panels_groups and dashboard keys' do
let(:json_content) do
{

View File

@ -24,6 +24,14 @@ describe PerformanceMonitoring::PrometheusMetric do
end
describe 'validations' do
context 'json_content is not a hash' do
let(:json_content) { nil }
subject { described_class.from_json(json_content) }
it { expect { subject }.to raise_error(ActiveModel::ValidationError) }
end
context 'when unit is missing' do
before do
json_content['unit'] = nil

View File

@ -30,6 +30,14 @@ describe PerformanceMonitoring::PrometheusPanelGroup do
end
describe 'validations' do
context 'json_content is not a hash' do
let(:json_content) { nil }
subject { described_class.from_json(json_content) }
it { expect { subject }.to raise_error(ActiveModel::ValidationError) }
end
context 'when group is missing' do
before do
json_content.delete('group')

View File

@ -42,6 +42,14 @@ describe PerformanceMonitoring::PrometheusPanel do
end
describe 'validations' do
context 'json_content is not a hash' do
let(:json_content) { nil }
subject { described_class.from_json(json_content) }
it { expect { subject }.to raise_error(ActiveModel::ValidationError) }
end
context 'when title is missing' do
before do
json_content['title'] = nil

View File

@ -100,6 +100,20 @@ describe Todo do
end
end
describe '#for_alert?' do
it 'returns true when target is a Alert' do
subject.target_type = 'AlertManagement::Alert'
expect(subject.for_alert?).to eq(true)
end
it 'returns false when target is not a Alert' do
subject.target_type = 'Issue'
expect(subject.for_alert?).to eq(false)
end
end
describe '#target' do
context 'for commits' do
let(:project) { create(:project, :repository) }

View File

@ -0,0 +1,45 @@
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::BlamePresenter do
let(:project) { create(:project, :repository) }
let(:path) { 'files/ruby/popen.rb' }
let(:commit) { project.commit('master') }
let(:blob) { project.repository.blob_at(commit.id, path) }
let(:blame) { Gitlab::Blame.new(blob, commit) }
subject { described_class.new(blame, project: project, path: path) }
it 'precalculates necessary data on init' do
expect_any_instance_of(described_class)
.to receive(:precalculate_data_by_commit!)
.and_call_original
subject
end
describe '#groups' do
it 'delegates #groups call to the blame' do
expect(blame).to receive(:groups).and_call_original
subject.groups
end
end
describe '#commit_data' do
it 'has the data necessary to render the view' do
commit = blame.groups.first[:commit]
data = subject.commit_data(commit)
aggregate_failures do
expect(data.author_avatar.to_s).to include('src="https://www.gravatar.com/')
expect(data.age_map_class).to include('blame-commit-age-')
expect(data.commit_link.to_s).to include '913c66a37b4a45b9769037c55c2d238bd0942d2e">Files, encoding and much more</a>'
expect(data.commit_author_link.to_s).to include('<a class="commit-author-link" href=')
expect(data.project_blame_link.to_s).to include('<a title="View blame prior to this change"')
expect(data.time_ago_tooltip.to_s).to include('data-container="body">Feb 27, 2014</time>')
end
end
end
end

View File

@ -326,32 +326,6 @@ describe API::Runners do
expect(response).to have_gitlab_http_status(:unauthorized)
end
end
context 'FF hide_token_from_runners_api is enabled' do
before do
stub_feature_flags(hide_token_from_runners_api: true)
end
it "does not return runner's token" do
get api("/runners/#{shared_runner.id}", admin)
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).not_to have_key('token')
end
end
context 'FF hide_token_from_runners_api is disabled' do
before do
stub_feature_flags(hide_token_from_runners_api: false)
end
it "returns runner's token" do
get api("/runners/#{shared_runner.id}", admin)
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to have_key('token')
end
end
end
describe 'PUT /runners/:id' do

View File

@ -5,7 +5,6 @@ shared_context 'project service activation' do
let(:user) { create(:user) }
before do
stub_feature_flags(integration_form_refactor: false)
project.add_maintainer(user)
sign_in(user)
end