Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
742a7f35ac
commit
0df696c5f7
|
@ -385,7 +385,6 @@ linters:
|
|||
- "ee/app/views/shared/members/ee/_sso_badge.html.haml"
|
||||
- "ee/app/views/shared/milestones/_burndown.html.haml"
|
||||
- "ee/app/views/shared/milestones/_weight.html.haml"
|
||||
- "ee/app/views/shared/promotions/_promote_burndown_charts.html.haml"
|
||||
- "ee/app/views/shared/promotions/_promote_issue_weights.html.haml"
|
||||
- "ee/app/views/shared/promotions/_promote_repository_features.html.haml"
|
||||
- "ee/app/views/shared/promotions/_promote_servicedesk.html.haml"
|
||||
|
|
|
@ -51,7 +51,7 @@ export default Vue.extend({
|
|||
return Object.keys(this.issue).length;
|
||||
},
|
||||
milestoneTitle() {
|
||||
return this.issue.milestone ? this.issue.milestone.title : __('No Milestone');
|
||||
return this.issue.milestone ? this.issue.milestone.title : __('No milestone');
|
||||
},
|
||||
canRemove() {
|
||||
return !this.list.preset;
|
||||
|
|
|
@ -186,13 +186,13 @@ export default class LabelsSelect {
|
|||
if (showNo) {
|
||||
extraData.unshift({
|
||||
id: 0,
|
||||
title: __('No Label'),
|
||||
title: __('No label'),
|
||||
});
|
||||
}
|
||||
if (showAny) {
|
||||
extraData.unshift({
|
||||
isAny: true,
|
||||
title: __('Any Label'),
|
||||
title: __('Any label'),
|
||||
});
|
||||
}
|
||||
if (extraData.length) {
|
||||
|
@ -294,7 +294,7 @@ export default class LabelsSelect {
|
|||
|
||||
if (selected && selected.id === 0) {
|
||||
this.selected = [];
|
||||
return __('No Label');
|
||||
return __('No label');
|
||||
} else if (isSelected) {
|
||||
this.selected.push(title);
|
||||
} else if (!isSelected && title) {
|
||||
|
|
|
@ -56,7 +56,7 @@ export default class MilestoneSelect {
|
|||
const $loading = $block.find('.block-loading').fadeOut();
|
||||
selectedMilestoneDefault = showAny ? '' : null;
|
||||
selectedMilestoneDefault =
|
||||
showNo && defaultNo ? __('No Milestone') : selectedMilestoneDefault;
|
||||
showNo && defaultNo ? __('No milestone') : selectedMilestoneDefault;
|
||||
selectedMilestone = $dropdown.data('selected') || selectedMilestoneDefault;
|
||||
|
||||
if (issueUpdateURL) {
|
||||
|
@ -74,14 +74,14 @@ export default class MilestoneSelect {
|
|||
extraOptions.push({
|
||||
id: null,
|
||||
name: null,
|
||||
title: __('Any Milestone'),
|
||||
title: __('Any milestone'),
|
||||
});
|
||||
}
|
||||
if (showNo) {
|
||||
extraOptions.push({
|
||||
id: -1,
|
||||
name: __('No Milestone'),
|
||||
title: __('No Milestone'),
|
||||
name: __('No milestone'),
|
||||
title: __('No milestone'),
|
||||
});
|
||||
}
|
||||
if (showUpcoming) {
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
<script>
|
||||
import { GlButton, GlLoadingIcon } from '@gitlab/ui';
|
||||
import { GlButton } from '@gitlab/ui';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlButton,
|
||||
GlLoadingIcon,
|
||||
},
|
||||
props: {
|
||||
returnUrl: {
|
||||
|
@ -26,14 +25,18 @@ export default {
|
|||
};
|
||||
</script>
|
||||
<template>
|
||||
<div class="d-flex bg-light border-top justify-content-between align-items-center py-3 px-4">
|
||||
<gl-loading-icon :class="{ invisible: !savingChanges }" size="md" />
|
||||
<div class="d-flex bg-light border-top justify-content-end align-items-center py-3 px-4">
|
||||
<div>
|
||||
<gl-button v-if="returnUrl" ref="returnUrlLink" :href="returnUrl">{{
|
||||
s__('StaticSiteEditor|Return to site')
|
||||
}}</gl-button>
|
||||
<gl-button variant="success" :disabled="!saveable || savingChanges" @click="$emit('submit')">
|
||||
{{ __('Submit Changes') }}
|
||||
<gl-button
|
||||
variant="success"
|
||||
:disabled="!saveable"
|
||||
:loading="savingChanges"
|
||||
@click="$emit('submit')"
|
||||
>
|
||||
<span>{{ __('Submit Changes') }}</span>
|
||||
</gl-button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,17 +1,13 @@
|
|||
<script>
|
||||
import { mapState, mapActions } from 'vuex';
|
||||
|
||||
import SkeletonLoader from '../components/skeleton_loader.vue';
|
||||
import EditArea from '../components/edit_area.vue';
|
||||
import SavedChangesMessage from '../components/saved_changes_message.vue';
|
||||
import InvalidContentMessage from '../components/invalid_content_message.vue';
|
||||
import SubmitChangesError from '../components/submit_changes_error.vue';
|
||||
|
||||
import { SUCCESS_ROUTE } from '../router/constants';
|
||||
import appDataQuery from '../graphql/queries/app_data.query.graphql';
|
||||
import sourceContentQuery from '../graphql/queries/source_content.query.graphql';
|
||||
|
||||
import createFlash from '~/flash';
|
||||
|
||||
import { LOAD_CONTENT_ERROR } from '../constants';
|
||||
|
||||
export default {
|
||||
|
@ -19,7 +15,6 @@ export default {
|
|||
SkeletonLoader,
|
||||
EditArea,
|
||||
InvalidContentMessage,
|
||||
SavedChangesMessage,
|
||||
SubmitChangesError,
|
||||
},
|
||||
apollo: {
|
||||
|
@ -50,7 +45,7 @@ export default {
|
|||
},
|
||||
},
|
||||
computed: {
|
||||
...mapState(['isSavingChanges', 'submitChangesError', 'savedContentMeta']),
|
||||
...mapState(['isSavingChanges', 'submitChangesError']),
|
||||
isLoadingContent() {
|
||||
return this.$apollo.queries.sourceContent.loading;
|
||||
},
|
||||
|
@ -62,24 +57,15 @@ export default {
|
|||
...mapActions(['setContent', 'submitChanges', 'dismissSubmitChangesError']),
|
||||
onSubmit({ content }) {
|
||||
this.setContent(content);
|
||||
this.submitChanges();
|
||||
|
||||
return this.submitChanges().then(() => this.$router.push(SUCCESS_ROUTE));
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div class="container d-flex gl-flex-direction-column pt-2 h-100">
|
||||
<!-- Success view -->
|
||||
<saved-changes-message
|
||||
v-if="savedContentMeta"
|
||||
:branch="savedContentMeta.branch"
|
||||
:commit="savedContentMeta.commit"
|
||||
:merge-request="savedContentMeta.mergeRequest"
|
||||
:return-url="appData.returnUrl"
|
||||
/>
|
||||
|
||||
<!-- Main view -->
|
||||
<template v-else-if="appData.isSupportedContent">
|
||||
<template v-if="appData.isSupportedContent">
|
||||
<skeleton-loader v-if="isLoadingContent" class="w-75 gl-align-self-center gl-mt-5" />
|
||||
<submit-changes-error
|
||||
v-if="submitChangesError"
|
||||
|
@ -97,7 +83,6 @@ export default {
|
|||
/>
|
||||
</template>
|
||||
|
||||
<!-- Error view -->
|
||||
<invalid-content-message v-else class="w-75" />
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
<script>
|
||||
import { mapState } from 'vuex';
|
||||
import SavedChangesMessage from '../components/saved_changes_message.vue';
|
||||
import { HOME_ROUTE } from '../router/constants';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
SavedChangesMessage,
|
||||
},
|
||||
computed: {
|
||||
...mapState(['savedContentMeta', 'returnUrl']),
|
||||
},
|
||||
created() {
|
||||
if (!this.savedContentMeta) {
|
||||
this.$router.push(HOME_ROUTE);
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div v-if="savedContentMeta" class="container">
|
||||
<saved-changes-message
|
||||
:branch="savedContentMeta.branch"
|
||||
:commit="savedContentMeta.commit"
|
||||
:merge-request="savedContentMeta.mergeRequest"
|
||||
:return-url="returnUrl"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
|
@ -1,2 +1,2 @@
|
|||
// eslint-disable-next-line import/prefer-default-export
|
||||
export const HOME_ROUTE_NAME = 'home';
|
||||
export const HOME_ROUTE = { name: 'home' };
|
||||
export const SUCCESS_ROUTE = { name: 'success' };
|
||||
|
|
|
@ -1,10 +1,17 @@
|
|||
import Home from '../pages/home.vue';
|
||||
import { HOME_ROUTE_NAME } from './constants';
|
||||
import Success from '../pages/success.vue';
|
||||
|
||||
import { HOME_ROUTE, SUCCESS_ROUTE } from './constants';
|
||||
|
||||
export default [
|
||||
{
|
||||
name: HOME_ROUTE_NAME,
|
||||
...HOME_ROUTE,
|
||||
path: '/',
|
||||
component: Home,
|
||||
},
|
||||
{
|
||||
...SUCCESS_ROUTE,
|
||||
path: '/success',
|
||||
component: Success,
|
||||
},
|
||||
];
|
||||
|
|
|
@ -34,7 +34,7 @@ function createMenuItemTemplate({ original }) {
|
|||
|
||||
return `${avatarTag}
|
||||
${original.username}
|
||||
<small class="small font-weight-normal gl-color-inherit">${name}${count}</small>
|
||||
<small class="small font-weight-normal gl-reset-color">${name}${count}</small>
|
||||
${icon}`;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module DesignManagement
|
||||
class DesignsFinder
|
||||
include Gitlab::Allowable
|
||||
|
||||
# Params:
|
||||
# ids: integer[]
|
||||
# filenames: string[]
|
||||
# visible_at_version: ?version
|
||||
# filenames: String[]
|
||||
def initialize(issue, current_user, params = {})
|
||||
@issue = issue
|
||||
@current_user = current_user
|
||||
@params = params
|
||||
end
|
||||
|
||||
def execute
|
||||
items = init_collection
|
||||
|
||||
items = by_visible_at_version(items)
|
||||
items = by_filename(items)
|
||||
items = by_id(items)
|
||||
|
||||
items
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :issue, :current_user, :params
|
||||
|
||||
def init_collection
|
||||
return ::DesignManagement::Design.none unless can?(current_user, :read_design, issue)
|
||||
|
||||
issue.designs
|
||||
end
|
||||
|
||||
# Returns all designs that existed at a particular design version
|
||||
def by_visible_at_version(items)
|
||||
items.visible_at_version(params[:visible_at_version])
|
||||
end
|
||||
|
||||
def by_filename(items)
|
||||
return items if params[:filenames].nil?
|
||||
return ::DesignManagement::Design.none if params[:filenames].empty?
|
||||
|
||||
items.with_filename(params[:filenames])
|
||||
end
|
||||
|
||||
def by_id(items)
|
||||
return items if params[:ids].nil?
|
||||
return ::DesignManagement::Design.none if params[:ids].empty?
|
||||
|
||||
items.id_in(params[:ids])
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,58 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module DesignManagement
|
||||
class VersionsFinder
|
||||
attr_reader :design_or_collection, :current_user, :params
|
||||
|
||||
# The `design_or_collection` argument should be either a:
|
||||
#
|
||||
# - DesignManagement::Design, or
|
||||
# - DesignManagement::DesignCollection
|
||||
#
|
||||
# The object will have `#versions` called on it to set up the
|
||||
# initial scope of the versions.
|
||||
#
|
||||
# valid params:
|
||||
# - earlier_or_equal_to: Version
|
||||
# - sha: String
|
||||
# - version_id: Integer
|
||||
#
|
||||
def initialize(design_or_collection, current_user, params = {})
|
||||
@design_or_collection = design_or_collection
|
||||
@current_user = current_user
|
||||
@params = params
|
||||
end
|
||||
|
||||
def execute
|
||||
unless Ability.allowed?(current_user, :read_design, design_or_collection)
|
||||
return ::DesignManagement::Version.none
|
||||
end
|
||||
|
||||
items = design_or_collection.versions
|
||||
items = by_earlier_or_equal_to(items)
|
||||
items = by_sha(items)
|
||||
items = by_version_id(items)
|
||||
items.ordered
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def by_earlier_or_equal_to(items)
|
||||
return items unless params[:earlier_or_equal_to]
|
||||
|
||||
items.earlier_or_equal_to(params[:earlier_or_equal_to])
|
||||
end
|
||||
|
||||
def by_version_id(items)
|
||||
return items unless params[:version_id]
|
||||
|
||||
items.id_in(params[:version_id])
|
||||
end
|
||||
|
||||
def by_sha(items)
|
||||
return items unless params[:sha]
|
||||
|
||||
items.by_sha(params[:sha])
|
||||
end
|
||||
end
|
||||
end
|
|
@ -23,7 +23,7 @@ class TodosFinder
|
|||
|
||||
NONE = '0'
|
||||
|
||||
TODO_TYPES = Set.new(%w(Issue MergeRequest)).freeze
|
||||
TODO_TYPES = Set.new(%w(Issue MergeRequest DesignManagement::Design)).freeze
|
||||
|
||||
attr_accessor :current_user, :params
|
||||
|
||||
|
|
|
@ -167,6 +167,8 @@ module EventsHelper
|
|||
project_issue_url(event.project, id: event.note_target, anchor: dom_id(event.target))
|
||||
elsif event.merge_request_note?
|
||||
project_merge_request_url(event.project, id: event.note_target, anchor: dom_id(event.target))
|
||||
elsif event.design_note?
|
||||
design_url(event.note_target, anchor: dom_id(event.note))
|
||||
else
|
||||
polymorphic_url([event.project.namespace.becomes(Namespace),
|
||||
event.project, event.note_target],
|
||||
|
@ -237,6 +239,16 @@ module EventsHelper
|
|||
concat content_tag(:span, event.author.to_reference, class: "username")
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def design_url(design, opts)
|
||||
designs_project_issue_url(
|
||||
design.project,
|
||||
design.issue,
|
||||
opts.merge(vueroute: design.filename)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
EventsHelper.prepend_if_ee('EE::EventsHelper')
|
||||
|
|
|
@ -9,7 +9,8 @@ module ExportHelper
|
|||
_('Project configuration, including services'),
|
||||
_('Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities'),
|
||||
_('LFS objects'),
|
||||
_('Issue Boards')
|
||||
_('Issue Boards'),
|
||||
_('Design Management files and data')
|
||||
]
|
||||
end
|
||||
|
||||
|
|
|
@ -54,7 +54,8 @@ module NavHelper
|
|||
current_path?('merge_requests#show') ||
|
||||
current_path?('projects/merge_requests/conflicts#show') ||
|
||||
current_path?('issues#show') ||
|
||||
current_path?('milestones#show')
|
||||
current_path?('milestones#show') ||
|
||||
current_path?('issues#designs')
|
||||
end
|
||||
|
||||
def admin_monitoring_nav_links
|
||||
|
|
|
@ -27,7 +27,11 @@ module SystemNoteHelper
|
|||
'locked' => 'lock',
|
||||
'unlocked' => 'lock-open',
|
||||
'due_date' => 'calendar',
|
||||
'health_status' => 'status-health'
|
||||
'health_status' => 'status-health',
|
||||
'designs_added' => 'doc-image',
|
||||
'designs_modified' => 'doc-image',
|
||||
'designs_removed' => 'doc-image',
|
||||
'designs_discussion_added' => 'doc-image'
|
||||
}.freeze
|
||||
|
||||
def system_note_icon_name(note)
|
||||
|
|
|
@ -53,6 +53,8 @@ module TodosHelper
|
|||
end
|
||||
|
||||
def todo_target_type_name(todo)
|
||||
return _('design') if todo.for_design?
|
||||
|
||||
todo.target_type.titleize.downcase
|
||||
end
|
||||
|
||||
|
@ -63,6 +65,8 @@ module TodosHelper
|
|||
|
||||
if todo.for_commit?
|
||||
project_commit_path(todo.project, todo.target, path_options)
|
||||
elsif todo.for_design?
|
||||
todos_design_path(todo, path_options)
|
||||
else
|
||||
path = [todo.resource_parent, todo.target]
|
||||
|
||||
|
@ -151,7 +155,8 @@ module TodosHelper
|
|||
[
|
||||
{ id: '', text: 'Any Type' },
|
||||
{ id: 'Issue', text: 'Issue' },
|
||||
{ id: 'MergeRequest', text: 'Merge Request' }
|
||||
{ id: 'MergeRequest', text: 'Merge Request' },
|
||||
{ id: 'DesignManagement::Design', text: 'Design' }
|
||||
]
|
||||
end
|
||||
|
||||
|
@ -188,6 +193,18 @@ module TodosHelper
|
|||
|
||||
private
|
||||
|
||||
def todos_design_path(todo, path_options)
|
||||
design = todo.target
|
||||
|
||||
designs_project_issue_path(
|
||||
todo.resource_parent,
|
||||
design.issue,
|
||||
path_options.merge(
|
||||
vueroute: design.filename
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
def todo_action_subject(todo)
|
||||
todo.self_added? ? 'yourself' : 'you'
|
||||
end
|
||||
|
|
|
@ -32,6 +32,9 @@ module Ci
|
|||
scheduler_failure: 2
|
||||
}.freeze
|
||||
|
||||
CODE_NAVIGATION_JOB_NAME = 'code_navigation'
|
||||
DEGRADATION_THRESHOLD_VARIABLE_NAME = 'DEGRADATION_THRESHOLD'
|
||||
|
||||
has_one :deployment, as: :deployable, class_name: 'Deployment'
|
||||
has_one :resource, class_name: 'Ci::Resource', inverse_of: :build
|
||||
has_many :trace_sections, class_name: 'Ci::BuildTraceSection'
|
||||
|
@ -917,6 +920,11 @@ module Ci
|
|||
failure_reason: :data_integrity_failure)
|
||||
end
|
||||
|
||||
def degradation_threshold
|
||||
var = yaml_variables.find { |v| v[:key] == DEGRADATION_THRESHOLD_VARIABLE_NAME }
|
||||
var[:value]&.to_i if var
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def dependencies
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require_dependency 'design_management'
|
||||
|
||||
module DesignManagement
|
||||
class Action < ApplicationRecord
|
||||
include WithUploads
|
||||
|
|
|
@ -11,4 +11,4 @@
|
|||
('js-first-button' if page.first?),
|
||||
('js-last-button' if page.last?),
|
||||
('d-none d-md-block' if !page.current?) ] }
|
||||
= link_to page, url, { remote: remote, rel: page.next? ? 'next' : page.prev? ? 'prev' : nil, class: 'page-link' }
|
||||
= link_to page, url, { remote: remote, rel: page.next? ? 'next' : page.prev? ? 'prev' : nil, class: ['page-link', active_when(page.current?)] }
|
||||
|
|
|
@ -53,4 +53,4 @@
|
|||
%strong Tip:
|
||||
= succeed '.' do
|
||||
You can also checkout merge requests locally by
|
||||
= link_to 'following these guidelines', help_page_path('user/project/merge_requests/index.md', anchor: "checkout-merge-requests-locally"), target: '_blank', rel: 'noopener noreferrer'
|
||||
= link_to 'following these guidelines', help_page_path('user/project/merge_requests/reviewing_and_managing_merge_requests.md', anchor: "checkout-merge-requests-locally"), target: '_blank', rel: 'noopener noreferrer'
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Update the template for Static Site Editor / Middleman
|
||||
merge_request: 30642
|
||||
author:
|
||||
type: other
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Apply active class on active link element in HAML pagination
|
||||
merge_request: 31396
|
||||
author:
|
||||
type: other
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Make edit board text sentence case
|
||||
merge_request: 31418
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix "how to checkout MR" help link
|
||||
merge_request: 31688
|
||||
author:
|
||||
type: fixed
|
|
@ -1,7 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'sidekiq/web'
|
||||
|
||||
def enable_reliable_fetch?
|
||||
return true unless Feature::FlipperFeature.table_exists?
|
||||
|
||||
|
@ -14,21 +12,10 @@ def enable_semi_reliable_fetch_mode?
|
|||
Feature.enabled?(:gitlab_sidekiq_enable_semi_reliable_fetcher, default_enabled: true)
|
||||
end
|
||||
|
||||
# Disable the Sidekiq Rack session since GitLab already has its own session store.
|
||||
# CSRF protection still works (https://github.com/mperham/sidekiq/commit/315504e766c4fd88a29b7772169060afc4c40329).
|
||||
Sidekiq::Web.set :sessions, false
|
||||
|
||||
# Custom Queues configuration
|
||||
queues_config_hash = Gitlab::Redis::Queues.params
|
||||
queues_config_hash[:namespace] = Gitlab::Redis::Queues::SIDEKIQ_NAMESPACE
|
||||
|
||||
# Default is to retry 25 times with exponential backoff. That's too much.
|
||||
Sidekiq.default_worker_options = { retry: 3 }
|
||||
|
||||
if Rails.env.development?
|
||||
Sidekiq.default_worker_options[:backtrace] = true
|
||||
end
|
||||
|
||||
enable_json_logs = Gitlab.config.sidekiq.log_format == 'json'
|
||||
enable_sidekiq_memory_killer = ENV['SIDEKIQ_MEMORY_KILLER_MAX_RSS'].to_i.nonzero?
|
||||
use_sidekiq_daemon_memory_killer = ENV["SIDEKIQ_DAEMON_MEMORY_KILLER"].to_i.nonzero?
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Preloads Sidekiq configurations that don't require application references.
|
||||
#
|
||||
# It ensures default settings are loaded before any other file references
|
||||
# (directly or indirectly) Sidekiq workers.
|
||||
#
|
||||
|
||||
require 'sidekiq/web'
|
||||
|
||||
# Disable the Sidekiq Rack session since GitLab already has its own session store.
|
||||
# CSRF protection still works (https://github.com/mperham/sidekiq/commit/315504e766c4fd88a29b7772169060afc4c40329).
|
||||
Sidekiq::Web.set :sessions, false
|
||||
|
||||
# Default is to retry 25 times with exponential backoff. That's too much.
|
||||
Sidekiq.default_worker_options = { retry: 3 }
|
||||
|
||||
if Rails.env.development?
|
||||
Sidekiq.default_worker_options[:backtrace] = true
|
||||
end
|
|
@ -119,6 +119,9 @@ following.
|
|||
To take advantage of group sync, group owners or maintainers will need to [create one
|
||||
or more LDAP group links](#adding-group-links).
|
||||
|
||||
NOTE: **Note:**
|
||||
If an LDAP user is a group member when LDAP Synchronization is added, and they are not part of the LDAP group, they will be removed from the group.
|
||||
|
||||
### Adding group links
|
||||
|
||||
Once [group sync has been configured](#group-sync) on the instance, one or more LDAP
|
||||
|
|
|
@ -32,12 +32,13 @@ You can customize the payload by sending the following parameters. All fields ar
|
|||
|
||||
| Property | Type | Description |
|
||||
| -------- | ---- | ----------- |
|
||||
| `title` | String | The title of the incident. If none is provided, then `New: Incident #N` will be used, where `#N` is the number of incident |
|
||||
| `title` | String | The title of the incident. Required. |
|
||||
| `description` | String | A high-level summary of the problem. |
|
||||
| `start_time` | DateTime | The time of the incident. If none is provided, a timestamp of the issue will be used. |
|
||||
| `service` | String | The affected service. |
|
||||
| `monitoring_tool` | String | The name of the associated monitoring tool. |
|
||||
| `hosts` | String or Array | One or more hosts, as to where this incident occurred. |
|
||||
| `severity` | String | The severity of the alert. Must be one of `critical`, `high`, `medium`, `low`, `info`, `unknown`. Default is `critical`. |
|
||||
|
||||
Example request:
|
||||
|
||||
|
|
|
@ -123,6 +123,26 @@ documentation.
|
|||
TIP: **Tip:**
|
||||
Key metrics are automatically extracted and shown in the merge request widget.
|
||||
|
||||
### Configuring degradation threshold
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/27599) in GitLab 13.0.
|
||||
|
||||
You can configure the sensitivity of degradation alerts to avoid getting alerts for minor drops in metrics.
|
||||
This is done by setting the `DEGRADATION_THRESHOLD` variable. In the example below, the alert will only show up
|
||||
if the `Total Score` metric degrades by 5 points or more:
|
||||
|
||||
```yaml
|
||||
include:
|
||||
template: Verify/Browser-Performance.gitlab-ci.yml
|
||||
|
||||
performance:
|
||||
variables:
|
||||
URL: https://example.com
|
||||
DEGRADATION_THRESHOLD: 5
|
||||
```
|
||||
|
||||
The `Total Score` metric is based on sitespeed.io's [coach performance score](https://www.sitespeed.io/documentation/sitespeed.io/metrics/#performance-score). There is more information in [the coach documentation](https://www.sitespeed.io/documentation/coach/how-to/#what-do-the-coach-do).
|
||||
|
||||
### Performance testing on Review Apps
|
||||
|
||||
The above CI YAML configuration is great for testing against static environments, and it can
|
||||
|
|
|
@ -12,10 +12,16 @@ NOTE: **Note:**
|
|||
You will need at least Maintainer [permissions](../../permissions.md) to enable the Alert Management feature.
|
||||
|
||||
1. Follow the [instructions for toggling generic alerts](../integrations/generic_alerts.md#setting-up-generic-alerts)
|
||||
1. You can now visit **{cloud-gear}** **Operations > Alert Management** in your project's sidebar to [view a list](#alert-management-list) of alerts.
|
||||
1. You can now visit **{cloud-gear}** **Operations > Alerts** in your project's sidebar to [view a list](#alert-management-list) of alerts.
|
||||
|
||||
![Alert Management Toggle](img/alert_management_1_v13_1.png)
|
||||
|
||||
## Populate Alert data
|
||||
|
||||
To populate data, see the instructions for
|
||||
[customizing the payload](../integrations/generic_alerts.md) and making a
|
||||
request to the alerts endpoint.
|
||||
|
||||
## Alert Management severity
|
||||
|
||||
Each level of alert contains a uniquely shaped and color-coded icon to help
|
||||
|
|
|
@ -82,6 +82,13 @@ Projects can be exported and imported only between versions of GitLab with match
|
|||
For example, 8.10.3 and 8.11 have the same Import/Export version (0.1.3)
|
||||
and the exports between them will be compatible.
|
||||
|
||||
## Between CE and EE
|
||||
|
||||
You can export projects from the [Community Edition to the Enterprise Edition](https://about.gitlab.com/install/ce-or-ee/) and vice versa.
|
||||
This assumes [version history](#version-history) requirements are met.
|
||||
|
||||
If you're exporting a project from the Enterprise Edition to the Community Edition, you may lose data that is retained only in the Enterprise Edition. For more information, see [downgrading from EE to CE](../../../README.md).
|
||||
|
||||
## Exported contents
|
||||
|
||||
The following items will be exported:
|
||||
|
|
|
@ -2335,12 +2335,6 @@ msgstr ""
|
|||
msgid "Any Author"
|
||||
msgstr ""
|
||||
|
||||
msgid "Any Label"
|
||||
msgstr ""
|
||||
|
||||
msgid "Any Milestone"
|
||||
msgstr ""
|
||||
|
||||
msgid "Any branch"
|
||||
msgstr ""
|
||||
|
||||
|
@ -2350,9 +2344,15 @@ msgstr ""
|
|||
msgid "Any encrypted tokens"
|
||||
msgstr ""
|
||||
|
||||
msgid "Any label"
|
||||
msgstr ""
|
||||
|
||||
msgid "Any member with Developer or higher permissions to the project."
|
||||
msgstr ""
|
||||
|
||||
msgid "Any milestone"
|
||||
msgstr ""
|
||||
|
||||
msgid "Any namespace"
|
||||
msgstr ""
|
||||
|
||||
|
@ -9142,6 +9142,12 @@ msgstr ""
|
|||
msgid "FeatureFlags|Edit Feature Flag"
|
||||
msgstr ""
|
||||
|
||||
msgid "FeatureFlags|Edit Feature Flag User List"
|
||||
msgstr ""
|
||||
|
||||
msgid "FeatureFlags|Edit list"
|
||||
msgstr ""
|
||||
|
||||
msgid "FeatureFlags|Enable features for specific users and specific environments by defining feature flag strategies. By default, features are available to all users in all environments."
|
||||
msgstr ""
|
||||
|
||||
|
@ -9154,6 +9160,9 @@ msgstr ""
|
|||
msgid "FeatureFlags|Feature Flag"
|
||||
msgstr ""
|
||||
|
||||
msgid "FeatureFlags|Feature Flag User List Details"
|
||||
msgstr ""
|
||||
|
||||
msgid "FeatureFlags|Feature Flag behavior is built up by creating a set of rules to define the status of target environments. A default wildcard rule %{codeStart}*%{codeEnd} for %{boldStart}All Environments%{boldEnd} is set, and you are able to add as many rules as you need by choosing environment specs below. You can toggle the behavior for each of your rules to set them %{boldStart}Active%{boldEnd} or %{boldStart}Inactive%{boldEnd}."
|
||||
msgstr ""
|
||||
|
||||
|
@ -9196,6 +9205,9 @@ msgstr ""
|
|||
msgid "FeatureFlags|Instance ID"
|
||||
msgstr ""
|
||||
|
||||
msgid "FeatureFlags|List details"
|
||||
msgstr ""
|
||||
|
||||
msgid "FeatureFlags|Loading feature flags"
|
||||
msgstr ""
|
||||
|
||||
|
@ -9211,9 +9223,15 @@ msgstr ""
|
|||
msgid "FeatureFlags|New Feature Flag"
|
||||
msgstr ""
|
||||
|
||||
msgid "FeatureFlags|New Feature Flag User List"
|
||||
msgstr ""
|
||||
|
||||
msgid "FeatureFlags|New feature flag"
|
||||
msgstr ""
|
||||
|
||||
msgid "FeatureFlags|New list"
|
||||
msgstr ""
|
||||
|
||||
msgid "FeatureFlags|Percent rollout (logged in users)"
|
||||
msgstr ""
|
||||
|
||||
|
@ -13918,12 +13936,6 @@ msgstr ""
|
|||
msgid "No Epic"
|
||||
msgstr ""
|
||||
|
||||
msgid "No Label"
|
||||
msgstr ""
|
||||
|
||||
msgid "No Milestone"
|
||||
msgstr ""
|
||||
|
||||
msgid "No Scopes"
|
||||
msgstr ""
|
||||
|
||||
|
@ -14020,6 +14032,9 @@ msgstr ""
|
|||
msgid "No jobs to show"
|
||||
msgstr ""
|
||||
|
||||
msgid "No label"
|
||||
msgstr ""
|
||||
|
||||
msgid "No labels with such name or description"
|
||||
msgstr ""
|
||||
|
||||
|
@ -16806,6 +16821,9 @@ msgstr ""
|
|||
msgid "Promoted issue to an epic."
|
||||
msgstr ""
|
||||
|
||||
msgid "Promotions|Burndown Charts are visual representations of the progress of completing a milestone. At a glance, you see the current state for the completion a given milestone. Without them, you would have to organize the data from the milestone and plot it yourself to have the same sense of progress."
|
||||
msgstr ""
|
||||
|
||||
msgid "Promotions|Buy EE"
|
||||
msgstr ""
|
||||
|
||||
|
@ -16821,6 +16839,9 @@ msgstr ""
|
|||
msgid "Promotions|Contact your Administrator to upgrade your license."
|
||||
msgstr ""
|
||||
|
||||
msgid "Promotions|Dismiss burndown charts promotion"
|
||||
msgstr ""
|
||||
|
||||
msgid "Promotions|Don't show me this again"
|
||||
msgstr ""
|
||||
|
||||
|
@ -16830,6 +16851,9 @@ msgstr ""
|
|||
msgid "Promotions|Improve issues management with Issue weight and GitLab Enterprise Edition."
|
||||
msgstr ""
|
||||
|
||||
msgid "Promotions|Improve milestones with Burndown Charts."
|
||||
msgstr ""
|
||||
|
||||
msgid "Promotions|Learn more"
|
||||
msgstr ""
|
||||
|
||||
|
@ -16854,6 +16878,9 @@ msgstr ""
|
|||
msgid "Promotions|Upgrade your plan to activate Contribution Analytics."
|
||||
msgstr ""
|
||||
|
||||
msgid "Promotions|Upgrade your plan to improve milestones with Burndown Charts."
|
||||
msgstr ""
|
||||
|
||||
msgid "Promotions|Weight"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -149,7 +149,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@babel/plugin-transform-modules-commonjs": "^7.8.3",
|
||||
"@gitlab/eslint-plugin": "3.0.0",
|
||||
"@gitlab/eslint-plugin": "3.1.0",
|
||||
"@vue/test-utils": "^1.0.0-beta.30",
|
||||
"axios-mock-adapter": "^1.15.0",
|
||||
"babel-jest": "^24.1.0",
|
||||
|
|
|
@ -32,6 +32,16 @@ FactoryBot.define do
|
|||
wiki_page { create(:wiki_page, container: project) }
|
||||
end
|
||||
end
|
||||
|
||||
trait :for_design do
|
||||
transient do
|
||||
design { create(:design, issue: create(:issue, project: project)) }
|
||||
note { create(:note, author: author, project: project, noteable: design) }
|
||||
end
|
||||
|
||||
action { Event::COMMENTED }
|
||||
target { note }
|
||||
end
|
||||
end
|
||||
|
||||
factory :push_event, class: 'PushEvent' do
|
||||
|
|
|
@ -217,7 +217,7 @@ describe 'Issue Boards', :js do
|
|||
|
||||
wait_for_requests
|
||||
|
||||
click_link "No Milestone"
|
||||
click_link "No milestone"
|
||||
|
||||
wait_for_requests
|
||||
|
||||
|
|
|
@ -186,4 +186,25 @@ describe 'Group issues page' do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'issues pagination' do
|
||||
let(:user_in_group) { create(:group_member, :maintainer, user: create(:user), group: group ).user }
|
||||
|
||||
let!(:issues) do
|
||||
(1..25).to_a.map { |index| create(:issue, project: project, title: "Issue #{index}") }
|
||||
end
|
||||
|
||||
before do
|
||||
sign_in(user_in_group)
|
||||
visit issues_group_path(group)
|
||||
end
|
||||
|
||||
it 'shows the pagination' do
|
||||
expect(page).to have_selector('.gl-pagination')
|
||||
end
|
||||
|
||||
it 'first pagination item is active' do
|
||||
expect(page).to have_css(".js-first-button a.page-link.active")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -274,7 +274,7 @@ describe 'Issues > Labels bulk assignment' do
|
|||
expect(find("#issue_#{issue2.id}")).to have_content 'First Release'
|
||||
|
||||
check 'check-all-issues'
|
||||
open_milestone_dropdown(['No Milestone'])
|
||||
open_milestone_dropdown(['No milestone'])
|
||||
update_issues
|
||||
|
||||
expect(find("#issue_#{issue1.id}")).to have_content 'bug'
|
||||
|
|
|
@ -95,7 +95,7 @@ describe 'Multiple issue updating from issues#index', :js do
|
|||
find('#check-all-issues').click
|
||||
find('.issues-bulk-update .js-milestone-select').click
|
||||
|
||||
find('.dropdown-menu-milestone a', text: "No Milestone").click
|
||||
find('.dropdown-menu-milestone a', text: "No milestone").click
|
||||
click_update_issues_button
|
||||
|
||||
expect(find('.issue:first-child')).not_to have_content milestone.title
|
||||
|
|
|
@ -91,7 +91,7 @@ describe 'Merge requests > User mass updates', :js do
|
|||
end
|
||||
|
||||
it 'removes milestone from the merge request' do
|
||||
change_milestone("No Milestone")
|
||||
change_milestone("No milestone")
|
||||
|
||||
expect(find('.merge-request')).not_to have_content milestone.title
|
||||
end
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe DesignManagement::DesignsFinder do
|
||||
include DesignManagementTestHelpers
|
||||
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:project) { create(:project, :private) }
|
||||
let_it_be(:issue) { create(:issue, project: project) }
|
||||
let_it_be(:design1) { create(:design, :with_file, issue: issue, versions_count: 1) }
|
||||
let_it_be(:design2) { create(:design, :with_file, issue: issue, versions_count: 1) }
|
||||
let_it_be(:design3) { create(:design, :with_file, issue: issue, versions_count: 1) }
|
||||
let(:params) { {} }
|
||||
|
||||
subject(:designs) { described_class.new(issue, user, params).execute }
|
||||
|
||||
describe '#execute' do
|
||||
context 'when user can not read designs of an issue' do
|
||||
it 'returns no results' do
|
||||
is_expected.to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user can read designs of an issue' do
|
||||
before do
|
||||
project.add_developer(user)
|
||||
end
|
||||
|
||||
context 'when design management feature is disabled' do
|
||||
it 'returns no results' do
|
||||
is_expected.to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context 'when design management feature is enabled' do
|
||||
before do
|
||||
enable_design_management
|
||||
end
|
||||
|
||||
it 'returns the designs' do
|
||||
is_expected.to contain_exactly(design1, design2, design3)
|
||||
end
|
||||
|
||||
context 'when argument is the ids of designs' do
|
||||
let(:params) { { ids: [design1.id] } }
|
||||
|
||||
it { is_expected.to eq([design1]) }
|
||||
end
|
||||
|
||||
context 'when argument is the filenames of designs' do
|
||||
let(:params) { { filenames: [design2.filename] } }
|
||||
|
||||
it { is_expected.to eq([design2]) }
|
||||
end
|
||||
|
||||
context 'when passed empty array' do
|
||||
context 'for filenames' do
|
||||
let(:params) { { filenames: [] } }
|
||||
|
||||
it { is_expected.to be_empty }
|
||||
end
|
||||
|
||||
context "for ids" do
|
||||
let(:params) { { ids: [] } }
|
||||
|
||||
it { is_expected.to be_empty }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'returning designs that existed at a particular given version' do
|
||||
let(:all_versions) { issue.design_collection.versions.ordered }
|
||||
let(:first_version) { all_versions.last }
|
||||
let(:second_version) { all_versions.second }
|
||||
|
||||
context 'when argument is the first version' do
|
||||
let(:params) { { visible_at_version: first_version } }
|
||||
|
||||
it { is_expected.to eq([design1]) }
|
||||
end
|
||||
|
||||
context 'when arguments are version and id' do
|
||||
context 'when id is absent at version' do
|
||||
let(:params) { { visible_at_version: first_version, ids: [design2.id] } }
|
||||
|
||||
it { is_expected.to eq([]) }
|
||||
end
|
||||
|
||||
context 'when id is present at version' do
|
||||
let(:params) { { visible_at_version: second_version, ids: [design2.id] } }
|
||||
|
||||
it { is_expected.to eq([design2]) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when argument is the second version' do
|
||||
let(:params) { { visible_at_version: second_version } }
|
||||
|
||||
it { is_expected.to contain_exactly(design1, design2) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,129 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe DesignManagement::VersionsFinder do
|
||||
include DesignManagementTestHelpers
|
||||
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:project) { create(:project, :private) }
|
||||
let_it_be(:issue) { create(:issue, project: project) }
|
||||
let_it_be(:design_1) { create(:design, :with_file, issue: issue, versions_count: 1) }
|
||||
let_it_be(:design_2) { create(:design, :with_file, issue: issue, versions_count: 1) }
|
||||
let(:version_1) { design_1.versions.first }
|
||||
let(:version_2) { design_2.versions.first }
|
||||
let(:design_or_collection) { issue.design_collection }
|
||||
let(:params) { {} }
|
||||
|
||||
let(:finder) { described_class.new(design_or_collection, user, params) }
|
||||
|
||||
subject(:versions) { finder.execute }
|
||||
|
||||
describe '#execute' do
|
||||
shared_examples 'returns no results' do
|
||||
it 'returns no results when passed a DesignCollection' do
|
||||
expect(design_or_collection).is_a?(DesignManagement::DesignCollection)
|
||||
is_expected.to be_empty
|
||||
end
|
||||
|
||||
context 'when passed a Design' do
|
||||
let(:design_or_collection) { design_1 }
|
||||
|
||||
it 'returns no results when passed a Design' do
|
||||
is_expected.to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user cannot read designs of an issue' do
|
||||
include_examples 'returns no results'
|
||||
end
|
||||
|
||||
context 'when user can read designs of an issue' do
|
||||
before do
|
||||
project.add_developer(user)
|
||||
end
|
||||
|
||||
context 'when design management feature is disabled' do
|
||||
include_examples 'returns no results'
|
||||
end
|
||||
|
||||
context 'when design management feature is enabled' do
|
||||
before do
|
||||
enable_design_management
|
||||
end
|
||||
|
||||
describe 'passing a DesignCollection or a Design for the initial scoping' do
|
||||
it 'returns the versions scoped to the DesignCollection' do
|
||||
expect(design_or_collection).is_a?(DesignManagement::DesignCollection)
|
||||
is_expected.to eq(issue.design_collection.versions.ordered)
|
||||
end
|
||||
|
||||
context 'when passed a Design' do
|
||||
let(:design_or_collection) { design_1 }
|
||||
|
||||
it 'returns the versions scoped to the Design' do
|
||||
is_expected.to eq(design_1.versions)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'returning versions earlier or equal to a version' do
|
||||
context 'when argument is the first version' do
|
||||
let(:params) { { earlier_or_equal_to: version_1 }}
|
||||
|
||||
it { is_expected.to eq([version_1]) }
|
||||
end
|
||||
|
||||
context 'when argument is the second version' do
|
||||
let(:params) { { earlier_or_equal_to: version_2 }}
|
||||
|
||||
it { is_expected.to contain_exactly(version_1, version_2) }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'returning versions by SHA' do
|
||||
context 'when argument is the first version' do
|
||||
let(:params) { { sha: version_1.sha } }
|
||||
|
||||
it { is_expected.to contain_exactly(version_1) }
|
||||
end
|
||||
|
||||
context 'when argument is the second version' do
|
||||
let(:params) { { sha: version_2.sha } }
|
||||
|
||||
it { is_expected.to contain_exactly(version_2) }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'returning versions by ID' do
|
||||
context 'when argument is the first version' do
|
||||
let(:params) { { version_id: version_1.id } }
|
||||
|
||||
it { is_expected.to contain_exactly(version_1) }
|
||||
end
|
||||
|
||||
context 'when argument is the second version' do
|
||||
let(:params) { { version_id: version_2.id } }
|
||||
|
||||
it { is_expected.to contain_exactly(version_2) }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'mixing id and sha' do
|
||||
context 'when arguments are consistent' do
|
||||
let(:params) { { version_id: version_1.id, sha: version_1.sha } }
|
||||
|
||||
it { is_expected.to contain_exactly(version_1) }
|
||||
end
|
||||
|
||||
context 'when arguments are in-consistent' do
|
||||
let(:params) { { version_id: version_1.id, sha: version_2.sha } }
|
||||
|
||||
it { is_expected.to be_empty }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -260,9 +260,9 @@ describe TodosFinder do
|
|||
it 'returns the expected types' do
|
||||
expected_result =
|
||||
if Gitlab.ee?
|
||||
%w[DesignManagement::Design Epic Issue MergeRequest]
|
||||
%w[Epic Issue MergeRequest DesignManagement::Design]
|
||||
else
|
||||
%w[Issue MergeRequest]
|
||||
%w[Issue MergeRequest DesignManagement::Design]
|
||||
end
|
||||
|
||||
expect(described_class.todo_types).to contain_exactly(*expected_result)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import { GlButton, GlLoadingIcon } from '@gitlab/ui';
|
||||
import { GlButton } from '@gitlab/ui';
|
||||
|
||||
import PublishToolbar from '~/static_site_editor/components/publish_toolbar.vue';
|
||||
|
||||
|
@ -19,7 +19,6 @@ describe('Static Site Editor Toolbar', () => {
|
|||
|
||||
const findReturnUrlLink = () => wrapper.find({ ref: 'returnUrlLink' });
|
||||
const findSaveChangesButton = () => wrapper.find(GlButton);
|
||||
const findLoadingIndicator = () => wrapper.find(GlLoadingIcon);
|
||||
|
||||
beforeEach(() => {
|
||||
buildWrapper();
|
||||
|
@ -37,8 +36,8 @@ describe('Static Site Editor Toolbar', () => {
|
|||
expect(findSaveChangesButton().attributes('disabled')).toBe('true');
|
||||
});
|
||||
|
||||
it('does not display saving changes indicator', () => {
|
||||
expect(findLoadingIndicator().classes()).toContain('invisible');
|
||||
it('does not render the Submit Changes button with a loader', () => {
|
||||
expect(findSaveChangesButton().props('loading')).toBe(false);
|
||||
});
|
||||
|
||||
it('does not render returnUrl link', () => {
|
||||
|
@ -62,15 +61,11 @@ describe('Static Site Editor Toolbar', () => {
|
|||
|
||||
describe('when saving changes', () => {
|
||||
beforeEach(() => {
|
||||
buildWrapper({ saveable: true, savingChanges: true });
|
||||
buildWrapper({ savingChanges: true });
|
||||
});
|
||||
|
||||
it('disables Submit Changes button', () => {
|
||||
expect(findSaveChangesButton().attributes('disabled')).toBe('true');
|
||||
});
|
||||
|
||||
it('displays saving changes indicator', () => {
|
||||
expect(findLoadingIndicator().classes()).not.toContain('invisible');
|
||||
it('renders the Submit Changes button with a loading indicator', () => {
|
||||
expect(findSaveChangesButton().props('loading')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -1,21 +1,17 @@
|
|||
import Vuex from 'vuex';
|
||||
import { shallowMount, createLocalVue } from '@vue/test-utils';
|
||||
|
||||
import createState from '~/static_site_editor/store/state';
|
||||
|
||||
import { SUCCESS_ROUTE } from '~/static_site_editor/router/constants';
|
||||
import Home from '~/static_site_editor/pages/home.vue';
|
||||
|
||||
import SkeletonLoader from '~/static_site_editor/components/skeleton_loader.vue';
|
||||
import EditArea from '~/static_site_editor/components/edit_area.vue';
|
||||
import InvalidContentMessage from '~/static_site_editor/components/invalid_content_message.vue';
|
||||
import SubmitChangesError from '~/static_site_editor/components/submit_changes_error.vue';
|
||||
import SavedChangesMessage from '~/static_site_editor/components/saved_changes_message.vue';
|
||||
|
||||
import {
|
||||
returnUrl,
|
||||
sourceContent as content,
|
||||
sourceContentTitle as title,
|
||||
savedContentMeta,
|
||||
submitChangesError,
|
||||
} from '../mock_data';
|
||||
|
||||
|
@ -27,6 +23,7 @@ describe('static_site_editor/pages/home', () => {
|
|||
let wrapper;
|
||||
let store;
|
||||
let $apollo;
|
||||
let $router;
|
||||
let setContentActionMock;
|
||||
let submitChangesActionMock;
|
||||
let dismissSubmitChangesErrorActionMock;
|
||||
|
@ -63,12 +60,19 @@ describe('static_site_editor/pages/home', () => {
|
|||
};
|
||||
};
|
||||
|
||||
const buildRouter = () => {
|
||||
$router = {
|
||||
push: jest.fn(),
|
||||
};
|
||||
};
|
||||
|
||||
const buildWrapper = (data = {}) => {
|
||||
wrapper = shallowMount(Home, {
|
||||
localVue,
|
||||
store,
|
||||
mocks: {
|
||||
$apollo,
|
||||
$router,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -83,10 +87,10 @@ describe('static_site_editor/pages/home', () => {
|
|||
const findInvalidContentMessage = () => wrapper.find(InvalidContentMessage);
|
||||
const findSkeletonLoader = () => wrapper.find(SkeletonLoader);
|
||||
const findSubmitChangesError = () => wrapper.find(SubmitChangesError);
|
||||
const findSavedChangesMessage = () => wrapper.find(SavedChangesMessage);
|
||||
|
||||
beforeEach(() => {
|
||||
buildApollo();
|
||||
buildRouter();
|
||||
buildStore();
|
||||
});
|
||||
|
||||
|
@ -96,23 +100,6 @@ describe('static_site_editor/pages/home', () => {
|
|||
$apollo = null;
|
||||
});
|
||||
|
||||
it('renders the saved changes message when changes are submitted successfully', () => {
|
||||
buildStore({ initialState: { returnUrl, savedContentMeta } });
|
||||
buildWrapper();
|
||||
|
||||
expect(findSavedChangesMessage().exists()).toBe(true);
|
||||
expect(findSavedChangesMessage().props()).toEqual({
|
||||
returnUrl,
|
||||
...savedContentMeta,
|
||||
});
|
||||
});
|
||||
|
||||
it('does not render the saved changes message when changes are not submitted', () => {
|
||||
buildWrapper();
|
||||
|
||||
expect(findSavedChangesMessage().exists()).toBe(false);
|
||||
});
|
||||
|
||||
describe('when content is loaded', () => {
|
||||
beforeEach(() => {
|
||||
buildStore({ initialState: { isSavingChanges: true } });
|
||||
|
@ -123,20 +110,14 @@ describe('static_site_editor/pages/home', () => {
|
|||
expect(findEditArea().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('provides source content to the edit area', () => {
|
||||
it('provides source content, returnUrl, and isSavingChanges to the edit area', () => {
|
||||
expect(findEditArea().props()).toMatchObject({
|
||||
title,
|
||||
content,
|
||||
returnUrl,
|
||||
savingChanges: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('provides returnUrl to the edit area', () => {
|
||||
expect(findEditArea().props('returnUrl')).toBe(returnUrl);
|
||||
});
|
||||
|
||||
it('provides isSavingChanges to the edit area', () => {
|
||||
expect(findEditArea().props('savingChanges')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('does not render edit area when content is not loaded', () => {
|
||||
|
@ -210,6 +191,8 @@ describe('static_site_editor/pages/home', () => {
|
|||
const newContent = `new ${content}`;
|
||||
|
||||
beforeEach(() => {
|
||||
submitChangesActionMock.mockResolvedValueOnce();
|
||||
|
||||
buildWrapper({ sourceContent: { title, content } });
|
||||
findEditArea().vm.$emit('submit', { content: newContent });
|
||||
});
|
||||
|
@ -221,5 +204,11 @@ describe('static_site_editor/pages/home', () => {
|
|||
it('dispatches submitChanges action', () => {
|
||||
expect(submitChangesActionMock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('pushes success route when submitting changes succeeds', () => {
|
||||
return wrapper.vm.$nextTick().then(() => {
|
||||
expect($router.push).toHaveBeenCalledWith(SUCCESS_ROUTE);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
import Vuex from 'vuex';
|
||||
import { shallowMount, createLocalVue } from '@vue/test-utils';
|
||||
import createState from '~/static_site_editor/store/state';
|
||||
import Success from '~/static_site_editor/pages/success.vue';
|
||||
import SavedChangesMessage from '~/static_site_editor/components/saved_changes_message.vue';
|
||||
import { savedContentMeta, returnUrl } from '../mock_data';
|
||||
import { HOME_ROUTE } from '~/static_site_editor/router/constants';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
|
||||
localVue.use(Vuex);
|
||||
|
||||
describe('static_site_editor/pages/success', () => {
|
||||
let wrapper;
|
||||
let store;
|
||||
let router;
|
||||
|
||||
const buildRouter = () => {
|
||||
router = {
|
||||
push: jest.fn(),
|
||||
};
|
||||
};
|
||||
|
||||
const buildStore = (initialState = {}) => {
|
||||
store = new Vuex.Store({
|
||||
state: createState({
|
||||
savedContentMeta,
|
||||
returnUrl,
|
||||
...initialState,
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
const buildWrapper = () => {
|
||||
wrapper = shallowMount(Success, {
|
||||
localVue,
|
||||
store,
|
||||
mocks: {
|
||||
$router: router,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const findSavedChangesMessage = () => wrapper.find(SavedChangesMessage);
|
||||
|
||||
beforeEach(() => {
|
||||
buildRouter();
|
||||
buildStore();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
wrapper = null;
|
||||
});
|
||||
|
||||
it('renders saved changes message', () => {
|
||||
buildWrapper();
|
||||
|
||||
expect(findSavedChangesMessage().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('passes returnUrl to the saved changes message', () => {
|
||||
buildWrapper();
|
||||
|
||||
expect(findSavedChangesMessage().props('returnUrl')).toBe(returnUrl);
|
||||
});
|
||||
|
||||
it('passes saved content metadata to the saved changes message', () => {
|
||||
buildWrapper();
|
||||
|
||||
expect(findSavedChangesMessage().props('branch')).toBe(savedContentMeta.branch);
|
||||
expect(findSavedChangesMessage().props('commit')).toBe(savedContentMeta.commit);
|
||||
expect(findSavedChangesMessage().props('mergeRequest')).toBe(savedContentMeta.mergeRequest);
|
||||
});
|
||||
|
||||
it('redirects to the HOME route when content has not been submitted', () => {
|
||||
buildStore({ savedContentMeta: null });
|
||||
buildWrapper();
|
||||
|
||||
expect(router.push).toHaveBeenCalledWith(HOME_ROUTE);
|
||||
});
|
||||
});
|
|
@ -199,5 +199,17 @@ describe EventsHelper do
|
|||
|
||||
expect(subject).to eq("#{project_base_url}/-/merge_requests/#{event.note_target.iid}#note_#{event.target.id}")
|
||||
end
|
||||
|
||||
context 'for design note events' do
|
||||
let(:event) { create(:event, :for_design, project: project) }
|
||||
|
||||
it 'returns an appropriate URL' do
|
||||
iid = event.note_target.issue.iid
|
||||
filename = event.note_target.filename
|
||||
note_id = event.target.id
|
||||
|
||||
expect(subject).to eq("#{project_base_url}/-/issues/#{iid}/designs/#{filename}#note_#{note_id}")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe ExportHelper do
|
||||
describe '#project_export_descriptions' do
|
||||
it 'includes design management' do
|
||||
expect(project_export_descriptions).to include('Design Management files and data')
|
||||
end
|
||||
end
|
||||
end
|
|
@ -117,4 +117,27 @@ describe NavHelper, :do_not_mock_admin_mode do
|
|||
|
||||
it { is_expected.to all(be_a(String)) }
|
||||
end
|
||||
|
||||
describe '#page_has_markdown?' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
where path: %w(
|
||||
merge_requests#show
|
||||
projects/merge_requests/conflicts#show
|
||||
issues#show
|
||||
milestones#show
|
||||
issues#designs
|
||||
)
|
||||
|
||||
with_them do
|
||||
before do
|
||||
allow(helper).to receive(:current_path?).and_call_original
|
||||
allow(helper).to receive(:current_path?).with(path).and_return(true)
|
||||
end
|
||||
|
||||
subject { helper.page_has_markdown? }
|
||||
|
||||
it { is_expected.to eq(true) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,8 +1,26 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "spec_helper"
|
||||
require 'spec_helper'
|
||||
|
||||
describe TodosHelper do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:author) { create(:user) }
|
||||
let_it_be(:issue) { create(:issue) }
|
||||
let_it_be(:design) { create(:design, issue: issue) }
|
||||
let_it_be(:note) do
|
||||
create(:note,
|
||||
project: issue.project,
|
||||
note: 'I am note, hear me roar')
|
||||
end
|
||||
let_it_be(:design_todo) do
|
||||
create(:todo, :mentioned,
|
||||
user: user,
|
||||
project: issue.project,
|
||||
target: design,
|
||||
author: author,
|
||||
note: note)
|
||||
end
|
||||
|
||||
describe '#todos_count_format' do
|
||||
it 'shows fuzzy count for 100 or more items' do
|
||||
expect(helper.todos_count_format(100)).to eq '99+'
|
||||
|
@ -35,4 +53,53 @@ describe TodosHelper do
|
|||
expect(Gitlab::Json.parse(helper.todo_projects_options)).to match_array(expected_results)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#todo_target_link' do
|
||||
context 'when given a design' do
|
||||
let(:todo) { design_todo }
|
||||
|
||||
it 'produces a good link' do
|
||||
path = helper.todo_target_path(todo)
|
||||
link = helper.todo_target_link(todo)
|
||||
expected = "<a href=\"#{path}\">design #{design.to_reference}</a>"
|
||||
|
||||
expect(link).to eq(expected)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#todo_target_path' do
|
||||
context 'when given a design' do
|
||||
let(:todo) { design_todo }
|
||||
|
||||
it 'responds with an appropriate path' do
|
||||
path = helper.todo_target_path(todo)
|
||||
issue_path = Gitlab::Routing.url_helpers
|
||||
.project_issue_path(issue.project, issue)
|
||||
|
||||
expect(path).to eq("#{issue_path}/designs/#{design.filename}##{dom_id(design_todo.note)}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#todo_target_type_name' do
|
||||
context 'when given a design todo' do
|
||||
let(:todo) { design_todo }
|
||||
|
||||
it 'responds with an appropriate target type name' do
|
||||
name = helper.todo_target_type_name(todo)
|
||||
|
||||
expect(name).to eq('design')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#todo_types_options' do
|
||||
it 'includes a match for a design todo' do
|
||||
options = helper.todo_types_options
|
||||
design_option = options.find { |o| o[:id] == design_todo.target_type }
|
||||
|
||||
expect(design_option).to include(text: 'Design')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4414,4 +4414,31 @@ describe Ci::Build do
|
|||
it { is_expected.to be_nil }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#degradation_threshold' do
|
||||
subject { build.degradation_threshold }
|
||||
|
||||
context 'when threshold variable is defined' do
|
||||
before do
|
||||
build.yaml_variables = [
|
||||
{ key: 'SOME_VAR_1', value: 'SOME_VAL_1' },
|
||||
{ key: 'DEGRADATION_THRESHOLD', value: '5' },
|
||||
{ key: 'SOME_VAR_2', value: 'SOME_VAL_2' }
|
||||
]
|
||||
end
|
||||
|
||||
it { is_expected.to eq(5) }
|
||||
end
|
||||
|
||||
context 'when threshold variable is not defined' do
|
||||
before do
|
||||
build.yaml_variables = [
|
||||
{ key: 'SOME_VAR_1', value: 'SOME_VAL_1' },
|
||||
{ key: 'SOME_VAR_2', value: 'SOME_VAL_2' }
|
||||
]
|
||||
end
|
||||
|
||||
it { is_expected.to be_nil }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Binary file not shown.
|
@ -766,10 +766,10 @@
|
|||
resolved "https://registry.yarnpkg.com/@gitlab/at.js/-/at.js-1.5.5.tgz#5f6bfe6baaef360daa9b038fa78798d7a6a916b4"
|
||||
integrity sha512-282Dn3SPVsUHVDhMsXgfnv+Rzog0uxecjttxGRQvxh25es1+xvkGQFsvJfkSKJ3X1kHVkSjKf+Tt5Rra+Jhp9g==
|
||||
|
||||
"@gitlab/eslint-plugin@3.0.0":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@gitlab/eslint-plugin/-/eslint-plugin-3.0.0.tgz#1bf361af2e7d293cc10637d182b655328e0a9014"
|
||||
integrity sha512-7mwJEO63GNfiSk2Jtvk0TlH2OZPCJ45lHd0s7c1+oNPUy18rquyYu0Iv/mtpwOcfnx8R2fSl0aD+d0lWDuHUNQ==
|
||||
"@gitlab/eslint-plugin@3.1.0":
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@gitlab/eslint-plugin/-/eslint-plugin-3.1.0.tgz#18e03630d10788defbb4c2d746620aec09517295"
|
||||
integrity sha512-M5bCk5vD0d65COeYtWoc7p43bvvsT9885t6DONI7q5aQVg7GBk3J4on8XjnWTLI4dFZNQGS6aw8+PkRD8NqByQ==
|
||||
dependencies:
|
||||
babel-eslint "^10.0.3"
|
||||
eslint-config-airbnb-base "^14.0.0"
|
||||
|
|
Loading…
Reference in New Issue