Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-05-12 21:08:14 +00:00
parent 742a7f35ac
commit 0df696c5f7
54 changed files with 874 additions and 129 deletions

View File

@ -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"

View File

@ -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;

View File

@ -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) {

View File

@ -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) {

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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' };

View File

@ -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,
},
];

View File

@ -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}`;
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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')

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -1,5 +1,7 @@
# frozen_string_literal: true
require_dependency 'design_management'
module DesignManagement
class Action < ApplicationRecord
include WithUploads

View File

@ -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?)] }

View File

@ -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'

View File

@ -0,0 +1,5 @@
---
title: Update the template for Static Site Editor / Middleman
merge_request: 30642
author:
type: other

View File

@ -0,0 +1,5 @@
---
title: Apply active class on active link element in HAML pagination
merge_request: 31396
author:
type: other

View File

@ -0,0 +1,5 @@
---
title: Make edit board text sentence case
merge_request: 31418
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Fix "how to checkout MR" help link
merge_request: 31688
author:
type: fixed

View File

@ -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?

View File

@ -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

View File

@ -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

View File

@ -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:

View File

@ -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

View File

@ -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

View File

@ -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:

View File

@ -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 ""

View File

@ -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",

View File

@ -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

View File

@ -217,7 +217,7 @@ describe 'Issue Boards', :js do
wait_for_requests
click_link "No Milestone"
click_link "No milestone"
wait_for_requests

View File

@ -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

View File

@ -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'

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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);
});
});

View File

@ -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);
});
});
});
});

View File

@ -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);
});
});

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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"