Merge branch 'master' into 44149-issue-comment-buttons

* master: (29 commits)
  Fix provider server URL used when listing repos to import
  Fix inconsistent punctuation on MR form
  Update dependency for svgs
  Fix timestamp to include %M instead of %I for post-deploy migrations.
  Use Gitaly 0.89.0
  Resolve "Hover style for sidebar dropdowns is wrong"
  fixed spec
  Respect the protocol in `expose_url`
  Fix removes source branch text being rendered in merged state
  Fix code and wiki search results when filename is non-ASCII
  Include the ee/ directory in backtraces
  Use GitLab fork of zaproxy
  Updates file extensions on Vue docs
  fixed note polling not sending updated last fetched at date added spec for polling
  Add changelog entry
  Use `list.id` for `:key`
  added mutation spec
  Bump parser and unparser gems to remove warnings
  fix polling not working correctly
  Fixed issue notes being duplicated
  ...
This commit is contained in:
Filipa Lacerda 2018-03-13 09:19:41 +00:00
commit 939c87233e
60 changed files with 631 additions and 199 deletions

View File

@ -1 +1 @@
0.88.0
0.89.0

View File

@ -63,7 +63,7 @@ GEM
fog-core
mime-types (>= 2.99)
unf
ast (2.3.0)
ast (2.4.0)
atomic (1.1.99)
attr_encrypted (3.0.3)
encryptor (~> 3.0.0)
@ -586,8 +586,8 @@ GEM
orm_adapter (0.5.0)
os (0.9.6)
parallel (1.12.1)
parser (2.4.0.2)
ast (~> 2.3)
parser (2.5.0.3)
ast (~> 2.4.0)
parslet (1.5.0)
blankslate (~> 2.0)
path_expander (1.0.2)
@ -951,13 +951,13 @@ GEM
get_process_mem (~> 0)
unicorn (>= 4, < 6)
uniform_notifier (1.10.0)
unparser (0.2.6)
unparser (0.2.7)
abstract_type (~> 0.0.7)
adamantium (~> 0.2.0)
concord (~> 0.1.5)
diff-lcs (~> 1.3)
equalizer (~> 0.0.9)
parser (>= 2.3.1.2, < 2.5)
parser (>= 2.3.1.2, < 2.6)
procto (~> 0.0.2)
url_safe_base64 (0.2.2)
validates_hostname (1.0.6)

View File

@ -27,10 +27,11 @@ export default {
return Vue.http[method](endpoint);
},
poll(data = {}) {
const { endpoint, lastFetchedAt } = data;
const endpoint = data.notesData.notesPath;
const lastFetchedAt = data.lastFetchedAt;
const options = {
headers: {
'X-Last-Fetched-At': lastFetchedAt,
'X-Last-Fetched-At': lastFetchedAt ? `${lastFetchedAt}` : undefined,
},
};

View File

@ -209,18 +209,16 @@ const pollSuccessCallBack = (resp, commit, state, getters) => {
});
}
commit(types.SET_LAST_FETCHED_AT, resp.lastFetchedAt);
commit(types.SET_LAST_FETCHED_AT, resp.last_fetched_at);
return resp;
};
export const poll = ({ commit, state, getters }) => {
const requestData = { endpoint: state.notesData.notesPath, lastFetchedAt: state.lastFetchedAt };
eTagPoll = new Poll({
resource: service,
method: 'poll',
data: requestData,
data: state,
successCallback: resp => resp.json()
.then(data => pollSuccessCallBack(data, commit, state, getters)),
errorCallback: () => Flash('Something went wrong while fetching latest comments.'),
@ -229,7 +227,7 @@ export const poll = ({ commit, state, getters }) => {
if (!Visibility.hidden()) {
eTagPoll.makeRequest();
} else {
service.poll(requestData);
service.poll(state);
}
Visibility.change(() => {

View File

@ -90,19 +90,21 @@ export default {
const notes = [];
notesData.forEach((note) => {
const nn = Object.assign({}, note);
// To support legacy notes, should be very rare case.
if (note.individual_note && note.notes.length > 1) {
note.notes.forEach((n) => {
nn.notes = [n]; // override notes array to only have one item to mimick individual_note
notes.push(nn);
notes.push({
...note,
notes: [n], // override notes array to only have one item to mimick individual_note
});
});
} else {
const oldNote = utils.findNoteObjectById(state.notes, note.id);
nn.expanded = oldNote ? oldNote.expanded : note.expanded;
notes.push(nn);
notes.push({
...note,
expanded: (oldNote ? oldNote.expanded : note.expanded),
});
}
});

View File

@ -63,7 +63,7 @@
};
this.isRemovingSourceBranch = true;
this.service.mergeResource.save(options)
this.service.merge(options)
.then(res => res.data)
.then((data) => {
if (data.status === 'merge_when_pipeline_succeeds') {

View File

@ -1,44 +0,0 @@
import emptyStateSVG from 'icons/_mr_widget_empty_state.svg';
export default {
name: 'MRWidgetNothingToMerge',
props: {
mr: {
type: Object,
required: true,
},
},
data() {
return { emptyStateSVG };
},
template: `
<div class="mr-widget-body mr-widget-empty-state">
<div class="row">
<div class="artwork col-sm-5 col-sm-push-7 col-xs-12 text-center">
<span v-html="emptyStateSVG"></span>
</div>
<div class="text col-sm-7 col-sm-pull-5 col-xs-12">
<span>
Merge requests are a place to propose changes you have made to a project
and discuss those changes with others.
</span>
<p>
Interested parties can even contribute by pushing commits if they want to.
</p>
<p>
Currently there are no changes in this merge request's source branch.
Please push new commits or use a different branch.
</p>
<div>
<a
v-if="mr.newBlobPath"
:href="mr.newBlobPath"
class="btn btn-inverted btn-save">
Create file
</a>
</div>
</div>
</div>
</div>
`,
};

View File

@ -0,0 +1,47 @@
<script>
import emptyStateSVG from 'icons/_mr_widget_empty_state.svg';
export default {
name: 'MRWidgetNothingToMerge',
props: {
mr: {
type: Object,
required: true,
},
},
data() {
return { emptyStateSVG };
},
};
</script>
<template>
<div class="mr-widget-body mr-widget-empty-state">
<div class="row">
<div class="artwork col-sm-5 col-sm-push-7 col-xs-12 text-center">
<span v-html="emptyStateSVG"></span>
</div>
<div class="text col-sm-7 col-sm-pull-5 col-xs-12">
<span>
Merge requests are a place to propose changes you have made to a project
and discuss those changes with others.
</span>
<p>
Interested parties can even contribute by pushing commits if they want to.
</p>
<p>
Currently there are no changes in this merge request's source branch.
Please push new commits or use a different branch.
</p>
<div>
<a
v-if="mr.newBlobPath"
:href="mr.newBlobPath"
class="btn btn-inverted btn-save">
Create file
</a>
</div>
</div>
</div>
</div>
</template>

View File

@ -24,7 +24,7 @@ export { default as MergingState } from './components/states/mr_widget_merging.v
export { default as WipState } from './components/states/mr_widget_wip';
export { default as ArchivedState } from './components/states/mr_widget_archived.vue';
export { default as ConflictsState } from './components/states/mr_widget_conflicts.vue';
export { default as NothingToMergeState } from './components/states/mr_widget_nothing_to_merge';
export { default as NothingToMergeState } from './components/states/nothing_to_merge.vue';
export { default as MissingBranchState } from './components/states/mr_widget_missing_branch.vue';
export { default as NotAllowedState } from './components/states/mr_widget_not_allowed.vue';
export { default as ReadyToMergeState } from './components/states/mr_widget_ready_to_merge';

View File

@ -71,7 +71,8 @@ export default {
return this.mr.deployments.length;
},
shouldRenderSourceBranchRemovalStatus() {
return !this.mr.canRemoveSourceBranch && this.mr.shouldRemoveSourceBranch;
return !this.mr.canRemoveSourceBranch && this.mr.shouldRemoveSourceBranch &&
(!this.mr.isNothingToMergeState && !this.mr.isMergedState);
},
},
methods: {

View File

@ -125,6 +125,10 @@ export default class MergeRequestStore {
return this.state === stateKey.nothingToMerge;
}
get isMergedState() {
return this.state === stateKey.merged;
}
initRebase(data) {
this.canPushToSourceBranch = data.can_push_to_source_branch;
this.rebaseInProgress = data.rebase_in_progress;

View File

@ -49,6 +49,7 @@ export const stateKey = {
notAllowedToMerge: 'notAllowedToMerge',
readyToMerge: 'readyToMerge',
rebase: 'rebase',
merged: 'merged',
};
export default {

View File

@ -137,12 +137,22 @@
z-index: 200;
overflow: hidden;
a:not(.btn-retry),
.btn-link {
a:not(.btn) {
color: inherit;
&:hover {
color: $gl-link-hover-color;
.avatar {
border-color: rgba($avatar-border, .2);
}
}
}
.btn-link {
color: inherit;
outline: none;
}
@ -214,7 +224,7 @@
&:hover {
text-decoration: underline;
color: $md-link-color;
color: $gl-link-hover-color;
}
}
}
@ -486,16 +496,6 @@
}
}
a:not(.btn-retry) {
&:hover {
color: $md-link-color;
.avatar {
border-color: rgba($avatar-border, .2);
}
}
}
.dropdown-menu-toggle {
width: 100%;
padding-top: 6px;
@ -503,6 +503,20 @@
.dropdown-menu {
width: 100%;
/*
* Overwrite hover style for dropdown items, so that they are not blue
* This should be removed during dev of https://gitlab.com/gitlab-org/gitlab-ce/issues/44040
*/
li a {
&:hover,
&:active,
&:focus,
&.is-focused {
@include dropdown-item-hover;
}
}
}
}

View File

@ -1,4 +1,6 @@
module ImportHelper
include ::Gitlab::Utils::StrongMemoize
def has_ci_cd_only_params?
false
end
@ -75,17 +77,18 @@ module ImportHelper
private
def github_project_url(full_path)
"#{github_root_url}/#{full_path}"
URI.join(github_root_url, full_path).to_s
end
def github_root_url
return @github_url if defined?(@github_url)
strong_memoize(:github_url) do
provider = Gitlab::Auth::OAuth::Provider.config_for('github')
provider = Gitlab.config.omniauth.providers.find { |p| p.name == 'github' }
@github_url = provider.fetch('url', 'https://github.com') if provider
provider&.dig('url').presence || 'https://github.com'
end
end
def gitea_project_url(full_path)
"#{@gitea_host_url.sub(%r{/+\z}, '')}/#{full_path}"
URI.join(@gitea_host_url, full_path).to_s
end
end

View File

@ -169,7 +169,7 @@ module NotesHelper
reopenPath: reopen_issuable_path(issuable),
notesPath: notes_url,
totalNotes: issuable.discussions.length,
lastFetchedAt: Time.now
lastFetchedAt: Time.now.to_i
}.to_json
end

View File

@ -1,4 +1,6 @@
class Compare
include Gitlab::Utils::StrongMemoize
delegate :same, :head, :base, to: :@compare
attr_reader :project
@ -11,9 +13,10 @@ class Compare
end
end
def initialize(compare, project, straight: false)
def initialize(compare, project, base_sha: nil, straight: false)
@compare = compare
@project = project
@base_sha = base_sha
@straight = straight
end
@ -22,40 +25,36 @@ class Compare
end
def start_commit
return @start_commit if defined?(@start_commit)
strong_memoize(:start_commit) do
commit = @compare.base
commit = @compare.base
@start_commit = commit ? ::Commit.new(commit, project) : nil
::Commit.new(commit, project) if commit
end
end
def head_commit
return @head_commit if defined?(@head_commit)
strong_memoize(:head_commit) do
commit = @compare.head
commit = @compare.head
@head_commit = commit ? ::Commit.new(commit, project) : nil
::Commit.new(commit, project) if commit
end
end
alias_method :commit, :head_commit
def base_commit
return @base_commit if defined?(@base_commit)
@base_commit = if start_commit && head_commit
project.merge_base_commit(start_commit.id, head_commit.id)
else
nil
end
end
def start_commit_sha
start_commit.try(:sha)
start_commit&.sha
end
def base_commit_sha
base_commit.try(:sha)
strong_memoize(:base_commit) do
next unless start_commit && head_commit
@base_sha || project.merge_base_commit(start_commit.id, head_commit.id)&.sha
end
end
def head_commit_sha
commit.try(:sha)
commit&.sha
end
def raw_diffs(*args)

View File

@ -10,9 +10,14 @@ class CompareService
@start_ref_name = new_start_ref_name
end
def execute(target_project, target_ref, straight: false)
def execute(target_project, target_ref, base_sha: nil, straight: false)
raw_compare = target_project.repository.compare_source_branch(target_ref, start_project.repository, start_ref_name, straight: straight)
Compare.new(raw_compare, target_project, straight: straight) if raw_compare
return unless raw_compare
Compare.new(raw_compare,
target_project,
base_sha: base_sha,
straight: straight)
end
end

View File

@ -27,7 +27,7 @@
":issue-link-base" => "issueLinkBase",
":root-path" => "rootPath",
":board-id" => "boardId",
":key" => "_uid" }
":key" => "list.id" }
= render "shared/boards/components/sidebar", group: group
- if @project
%board-add-issues-modal{ "new-issue-path" => new_project_issue_path(@project),

View File

@ -14,7 +14,7 @@
.checkbox
= form.label :allow_maintainer_to_push do
= form.check_box :allow_maintainer_to_push, disabled: !issuable.can_allow_maintainer_to_push?(current_user)
= _('Allow edits from maintainers')
= _('Allow edits from maintainers.')
= link_to 'About this feature', help_page_path('user/project/merge_requests/maintainer_access')
.help-block
= allow_maintainer_push_unavailable_reason(issuable)

View File

@ -0,0 +1,5 @@
---
title: Fix generated URL when listing repoitories for import
merge_request: 17692
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Fix hover style of dropdown items in the right sidebar
merge_request: 17519
author:
type: fixed

View File

@ -0,0 +1,6 @@
---
title: Fix "Remove source branch" button in Merge request widget during merge when pipeline
succeeds state
merge_request: 17192
author:
type: fixed

View File

@ -0,0 +1,6 @@
---
title: Use object ID to prevent duplicate keys Vue warning on Issue Boards page during
development
merge_request: 17682
author:
type: other

View File

@ -0,0 +1,5 @@
---
title: Add partial indexes on todos to handle users with many todos
merge_request:
author:
type: performance

View File

@ -0,0 +1,5 @@
---
title: Fix code and wiki search results when filename is non-ASCII
merge_request:
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Avoid re-fetching merge-base SHA from Gitaly unnecessarily
merge_request:
author:
type: performance

View File

@ -0,0 +1,5 @@
---
title: Move NothingToMerge vue component
merge_request: 17544
author: George Tsiolis
type: performance

View File

@ -0,0 +1,5 @@
---
title: Ensure the API returns https links when https is configured
merge_request: 17681
author:
type: fixed

View File

@ -1,7 +1,2 @@
# Be sure to restart your server when you modify this file.
# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
# Rails.backtrace_cleaner.remove_silencers!
Rails.backtrace_cleaner.remove_silencers!
Rails.backtrace_cleaner.add_silencer { |line| line !~ Gitlab::APP_DIRS_PATTERN }

View File

@ -0,0 +1,28 @@
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class AddPartialIndexesOnTodos < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
# Set this constant to true if this migration requires downtime.
DOWNTIME = false
disable_ddl_transaction!
INDEX_NAME_PENDING="index_todos_on_user_id_and_id_pending"
INDEX_NAME_DONE="index_todos_on_user_id_and_id_done"
def up
unless index_exists?(:todos, [:user_id, :id], name: INDEX_NAME_PENDING)
add_concurrent_index(:todos, [:user_id, :id], where: "state='pending'", name: INDEX_NAME_PENDING)
end
unless index_exists?(:todos, [:user_id, :id], name: INDEX_NAME_DONE)
add_concurrent_index(:todos, [:user_id, :id], where: "state='done'", name: INDEX_NAME_DONE)
end
end
def down
remove_concurrent_index(:todos, [:user_id, :id], where: "state='pending'", name: INDEX_NAME_PENDING)
remove_concurrent_index(:todos, [:user_id, :id], where: "state='done'", name: INDEX_NAME_DONE)
end
end

View File

@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20180309121820) do
ActiveRecord::Schema.define(version: 20180309160427) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -1778,6 +1778,8 @@ ActiveRecord::Schema.define(version: 20180309121820) do
add_index "todos", ["note_id"], name: "index_todos_on_note_id", using: :btree
add_index "todos", ["project_id"], name: "index_todos_on_project_id", using: :btree
add_index "todos", ["target_type", "target_id"], name: "index_todos_on_target_type_and_target_id", using: :btree
add_index "todos", ["user_id", "id"], name: "index_todos_on_user_id_and_id_done", where: "((state)::text = 'done'::text)", using: :btree
add_index "todos", ["user_id", "id"], name: "index_todos_on_user_id_and_id_pending", where: "((state)::text = 'pending'::text)", using: :btree
add_index "todos", ["user_id"], name: "index_todos_on_user_id", using: :btree
create_table "trending_projects", force: :cascade do |t|

View File

@ -548,6 +548,57 @@ On those a default key should not be provided.
1. Properties in a Vue Component:
Check [order of properties in components rule][vue-order].
#### `:key`
When using `v-for` you need to provide a *unique* `:key` attribute for each item.
1. If the elements of the array being iterated have an unique `id` it is advised to use it:
```html
<div
v-for="item in items"
:key="item.id"
>
<!-- content -->
</div>
```
1. When the elements being iterated don't have a unique id, you can use the array index as the `:key` attribute
```html
<div
v-for="(item, index) in items"
:key="index"
>
<!-- content -->
</div>
```
1. When using `v-for` with `template` and there is more than one child element, the `:key` values must be unique. It's advised to use `kebab-case` namespaces.
```html
<template v-for="(item, index) in items">
<span :key="`span-${index}`"></span>
<button :key="`button-${index}`"></button>
</template>
```
1. When dealing with nested `v-for` use the same guidelines as above.
```html
<div
v-for="item in items"
:key="item.id"
>
<span
v-for="element in array"
:key="element.id"
>
<!-- content -->
</span>
</div>
```
Useful links:
1. [`key`](https://vuejs.org/v2/guide/list.html#key)
1. [Vue Style Guide: Keyed v-for](https://vuejs.org/v2/style-guide/#Keyed-v-for-essential )
#### Vue and Bootstrap
1. Tooltips: Do not rely on `has-tooltip` class name for Vue components

View File

@ -53,13 +53,13 @@ you can find a clear separation of concerns:
```
new_feature
├── components
│ └── component.js.es6
│ └── component.vue
│ └── ...
├── store
│ └── new_feature_store.js.es6
├── service
│ └── new_feature_service.js.es6
├── new_feature_bundle.js.es6
├── stores
│ └── new_feature_store.js
├── services
│ └── new_feature_service.js
├── new_feature_bundle.js
```
_For consistency purposes, we recommend you to follow the same structure._

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 48 KiB

View File

@ -15,7 +15,7 @@ module API
url_options = Gitlab::Application.routes.default_url_options
protocol, host, port = url_options.slice(:protocol, :host, :port).values
URI::HTTP.build(scheme: protocol, host: host, port: port, path: path).to_s
URI::Generic.build(scheme: protocol, host: host, port: port, path: path).to_s
end
private

View File

@ -3,7 +3,7 @@ require 'rails/generators'
module Rails
class PostDeploymentMigrationGenerator < Rails::Generators::NamedBase
def create_migration_file
timestamp = Time.now.strftime('%Y%m%d%H%I%S')
timestamp = Time.now.strftime('%Y%m%d%H%M%S')
template "migration.rb", "db/post_migrate/#{timestamp}_#{file_name}.rb"
end

View File

@ -2,6 +2,7 @@ require_dependency 'gitlab/git'
module Gitlab
COM_URL = 'https://gitlab.com'.freeze
APP_DIRS_PATTERN = %r{^/?(app|config|ee|lib|spec|\(\w*\))}
def self.com?
# Check `staging?` as well to keep parity with gitlab.com

View File

@ -4,7 +4,7 @@ module Gitlab
class Config
class << self
def options
Gitlab.config.omniauth.providers.find { |provider| provider.name == 'saml' }
Gitlab::Auth::OAuth::Provider.config_for('saml')
end
def groups

View File

@ -44,7 +44,11 @@ module Gitlab
project.commit(head_sha)
else
straight = start_sha == base_sha
CompareService.new(project, head_sha).execute(project, start_sha, straight: straight)
CompareService.new(project, head_sha).execute(project,
start_sha,
base_sha: base_sha,
straight: straight)
end
end
end

View File

@ -197,10 +197,7 @@ module Gitlab
end
def github_omniauth_provider
@github_omniauth_provider ||=
Gitlab.config.omniauth.providers
.find { |provider| provider.name == 'github' }
.to_h
@github_omniauth_provider ||= Gitlab::Auth::OAuth::Provider.config_for('github').to_h
end
def rate_limit_counter

View File

@ -72,7 +72,7 @@ module Gitlab
end
def config
Gitlab.config.omniauth.providers.find {|provider| provider.name == "gitlab"}
Gitlab::Auth::OAuth::Provider.config_for('gitlab')
end
def gitlab_options

View File

@ -83,7 +83,7 @@ module Gitlab
end
def config
Gitlab.config.omniauth.providers.find { |provider| provider.name == "github" }
Gitlab::Auth::OAuth::Provider.config_for('github')
end
def github_options

View File

@ -7,8 +7,8 @@ module Gitlab
def initialize(opts = {})
@id = opts.fetch(:id, nil)
@filename = opts.fetch(:filename, nil)
@basename = opts.fetch(:basename, nil)
@filename = encode_utf8(opts.fetch(:filename, nil))
@basename = encode_utf8(opts.fetch(:basename, nil))
@ref = opts.fetch(:ref, nil)
@startline = opts.fetch(:startline, nil)
@data = encode_utf8(opts.fetch(:data, nil))

View File

@ -32,7 +32,7 @@ module GoogleApi
private
def config
Gitlab.config.omniauth.providers.find { |provider| provider.name == "google_oauth2" }
Gitlab::Auth::OAuth::Provider.config_for('google_oauth2')
end
def client

View File

@ -8,8 +8,8 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-03-06 17:36+0100\n"
"PO-Revision-Date: 2018-03-06 17:36+0100\n"
"POT-Creation-Date: 2018-03-12 19:50+0100\n"
"PO-Revision-Date: 2018-03-12 19:50+0100\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
@ -28,6 +28,11 @@ msgid_plural "%d commits behind"
msgstr[0] ""
msgstr[1] ""
msgid "%d exporter"
msgid_plural "%d exporters"
msgstr[0] ""
msgstr[1] ""
msgid "%d issue"
msgid_plural "%d issues"
msgstr[0] ""
@ -43,6 +48,11 @@ msgid_plural "%d merge requests"
msgstr[0] ""
msgstr[1] ""
msgid "%d metric"
msgid_plural "%d metrics"
msgstr[0] ""
msgstr[1] ""
msgid "%s additional commit has been omitted to prevent performance issues."
msgid_plural "%s additional commits have been omitted to prevent performance issues."
msgstr[0] ""
@ -102,6 +112,9 @@ msgstr ""
msgid "2FA enabled"
msgstr ""
msgid "<strong>Removes</strong> source branch"
msgstr ""
msgid "A collection of graphs regarding Continuous Integration"
msgstr ""
@ -111,6 +124,9 @@ msgstr ""
msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
msgstr ""
msgid "A user with write access to the source branch selected this option"
msgstr ""
msgid "About auto deploy"
msgstr ""
@ -213,7 +229,7 @@ msgstr ""
msgid "All features are enabled for blank projects, from templates, or when importing, but you can disable them afterward in the project settings."
msgstr ""
msgid "Allow edits from maintainers"
msgid "Allow edits from maintainers."
msgstr ""
msgid "Allows you to add and manage Kubernetes clusters."
@ -857,6 +873,9 @@ msgstr ""
msgid "ClusterIntegration|Learn more about environments"
msgstr ""
msgid "ClusterIntegration|Learn more about security configuration"
msgstr ""
msgid "ClusterIntegration|Machine type"
msgstr ""
@ -914,6 +933,9 @@ msgstr ""
msgid "ClusterIntegration|Save changes"
msgstr ""
msgid "ClusterIntegration|Security"
msgstr ""
msgid "ClusterIntegration|See and edit the details for your Kubernetes cluster"
msgstr ""
@ -941,6 +963,9 @@ msgstr ""
msgid "ClusterIntegration|Something went wrong while installing %{title}"
msgstr ""
msgid "ClusterIntegration|The default cluster configuration grants access to a wide set of functionalities needed to successfully build and deploy a containerised application."
msgstr ""
msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
msgstr ""
@ -1182,6 +1207,9 @@ msgstr ""
msgid "Create empty bare repository"
msgstr ""
msgid "Create group label"
msgstr ""
msgid "Create lists from labels. Issues with that label appear in that list."
msgstr ""
@ -1197,6 +1225,9 @@ msgstr ""
msgid "Create new..."
msgstr ""
msgid "Create project label"
msgstr ""
msgid "CreateNewFork|Fork"
msgstr ""
@ -1776,9 +1807,18 @@ msgstr ""
msgid "Labels"
msgstr ""
msgid "Labels can be applied to %{features}. Group labels are available for any project within the group."
msgstr ""
msgid "Labels can be applied to issues and merge requests to categorize them."
msgstr ""
msgid "Labels|Promote Label"
msgstr ""
msgid "Labels|Promote label %{labelTitle} to Group Label?"
msgstr ""
msgid "Last %d day"
msgid_plural "Last %d days"
msgstr[0] ""
@ -1850,9 +1890,15 @@ msgstr ""
msgid "Login"
msgstr ""
msgid "Manage group labels"
msgstr ""
msgid "Manage labels"
msgstr ""
msgid "Manage project labels"
msgstr ""
msgid "Mar"
msgstr ""
@ -1907,6 +1953,12 @@ msgstr ""
msgid "Milestones|Milestone %{milestoneTitle} was not found"
msgstr ""
msgid "Milestones|Promote %{milestoneTitle} to group milestone?"
msgstr ""
msgid "Milestones|Promote Milestone"
msgstr ""
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr ""
@ -2002,6 +2054,9 @@ msgstr ""
msgid "No file chosen"
msgstr ""
msgid "No labels created yet."
msgstr ""
msgid "No repository"
msgstr ""
@ -2251,9 +2306,15 @@ msgstr ""
msgid "Pipelines|Loading Pipelines"
msgstr ""
msgid "Pipelines|Project cache successfully reset."
msgstr ""
msgid "Pipelines|Run Pipeline"
msgstr ""
msgid "Pipelines|Something went wrong while cleaning runners cache."
msgstr ""
msgid "Pipelines|There are currently no %{scope} pipelines."
msgstr ""
@ -2380,9 +2441,6 @@ msgstr ""
msgid "Project avatar in repository: %{link}"
msgstr ""
msgid "Project cache successfully reset."
msgstr ""
msgid "Project details"
msgstr ""
@ -2446,6 +2504,12 @@ msgstr ""
msgid "ProjectsDropdown|This feature requires browser localStorage support"
msgstr ""
msgid "PrometheusService|%{exporters} with %{metrics} were found"
msgstr ""
msgid "PrometheusService|<p class=\"text-tertiary\">No <a href=\"%{docsUrl}\">common metrics</a> were found</p>"
msgstr ""
msgid "PrometheusService|Active"
msgstr ""
@ -2458,6 +2522,9 @@ msgstr ""
msgid "PrometheusService|By default, Prometheus listens on http://localhost:9090. Its not recommended to change the default address and port as this might affect or conflict with other services running on the GitLab server."
msgstr ""
msgid "PrometheusService|Common metrics"
msgstr ""
msgid "PrometheusService|Finding and configuring metrics..."
msgstr ""
@ -2479,15 +2546,9 @@ msgstr ""
msgid "PrometheusService|Missing environment variable"
msgstr ""
msgid "PrometheusService|Monitored"
msgstr ""
msgid "PrometheusService|More information"
msgstr ""
msgid "PrometheusService|No metrics are being monitored. To start monitoring, deploy to an environment."
msgstr ""
msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/"
msgstr ""
@ -2503,7 +2564,16 @@ msgstr ""
msgid "PrometheusService|To enable the installation of Prometheus on your clusters, deactivate the manual configuration below"
msgstr ""
msgid "PrometheusService|View environments"
msgid "PrometheusService|Waiting for your first deployment to an environment to find common metrics"
msgstr ""
msgid "Promote"
msgstr ""
msgid "Promote to Group Label"
msgstr ""
msgid "Promote to Group Milestone"
msgstr ""
msgid "Protip:"
@ -2515,9 +2585,6 @@ msgstr ""
msgid "Public - The project can be accessed without any authentication."
msgstr ""
msgid "Push access to this project is necessary in order to enable this option"
msgstr ""
msgid "Push events"
msgstr ""
@ -3338,9 +3405,6 @@ msgstr ""
msgid "Trigger this manual action"
msgstr ""
msgid "Unable to reset project cache."
msgstr ""
msgid "Unlock"
msgstr ""
@ -3383,12 +3447,18 @@ msgstr ""
msgid "View file @ "
msgstr ""
msgid "View group labels"
msgstr ""
msgid "View labels"
msgstr ""
msgid "View open merge request"
msgstr ""
msgid "View project labels"
msgstr ""
msgid "View replaced file @ "
msgstr ""

View File

@ -12,7 +12,7 @@
"webpack-prod": "NODE_ENV=production webpack --config config/webpack.config.js"
},
"dependencies": {
"@gitlab-org/gitlab-svgs": "^1.13.0",
"@gitlab-org/gitlab-svgs": "^1.14.0",
"autosize": "^4.0.0",
"axios": "^0.17.1",
"babel-core": "^6.26.0",

View File

@ -125,6 +125,12 @@ describe 'Merge request > User merges when pipeline succeeds', :js do
expect(page).to have_content "canceled the automatic merge"
end
it 'allows to remove source branch' do
click_link "Remove source branch"
expect(page).to have_content "The source branch will be removed"
end
context 'when pipeline succeeds' do
before do
build.success

View File

@ -27,25 +27,48 @@ describe ImportHelper do
describe '#provider_project_link' do
context 'when provider is "github"' do
let(:github_server_url) { nil }
before do
setting = Settingslogic.new('name' => 'github')
setting['url'] = github_server_url if github_server_url
allow(Gitlab.config.omniauth).to receive(:providers).and_return([setting])
end
context 'when provider does not specify a custom URL' do
it 'uses default GitHub URL' do
allow(Gitlab.config.omniauth).to receive(:providers)
.and_return([Settingslogic.new('name' => 'github')])
expect(helper.provider_project_link('github', 'octocat/Hello-World'))
.to include('href="https://github.com/octocat/Hello-World"')
end
end
context 'when provider specify a custom URL' do
it 'uses custom URL' do
allow(Gitlab.config.omniauth).to receive(:providers)
.and_return([Settingslogic.new('name' => 'github', 'url' => 'https://github.company.com')])
let(:github_server_url) { 'https://github.company.com' }
it 'uses custom URL' do
expect(helper.provider_project_link('github', 'octocat/Hello-World'))
.to include('href="https://github.company.com/octocat/Hello-World"')
end
end
context "when custom URL contains a '/' char at the end" do
let(:github_server_url) { 'https://github.company.com/' }
it "doesn't render double slash" do
expect(helper.provider_project_link('github', 'octocat/Hello-World'))
.to include('href="https://github.company.com/octocat/Hello-World"')
end
end
context 'when provider is missing' do
it 'uses the default URL' do
allow(Gitlab.config.omniauth).to receive(:providers).and_return([])
expect(helper.provider_project_link('github', 'octocat/Hello-World'))
.to include('href="https://github.com/octocat/Hello-World"')
end
end
end
context 'when provider is "gitea"' do

View File

@ -1,7 +1,7 @@
/* eslint-disable */
export const notesDataMock = {
discussionsPath: '/gitlab-org/gitlab-ce/issues/26/discussions.json',
lastFetchedAt: '1501862675',
lastFetchedAt: 1501862675,
markdownDocsPath: '/help/user/markdown',
newSessionPath: '/users/sign_in?redirect_to_referer=yes',
notesPath: '/gitlab-org/gitlab-ce/noteable/issue/98/notes',

View File

@ -1,5 +1,6 @@
import Vue from 'vue';
import _ from 'underscore';
import { headersInterceptor } from 'spec/helpers/vue_resource_helper';
import * as actions from '~/notes/stores/actions';
import store from '~/notes/stores';
import testAction from '../../helpers/vuex_action_helper';
@ -145,4 +146,68 @@ describe('Actions Notes Store', () => {
], done);
});
});
describe('poll', () => {
beforeEach((done) => {
jasmine.clock().install();
spyOn(Vue.http, 'get').and.callThrough();
store.dispatch('setNotesData', notesDataMock)
.then(done)
.catch(done.fail);
});
afterEach(() => {
jasmine.clock().uninstall();
});
it('calls service with last fetched state', (done) => {
const interceptor = (request, next) => {
next(request.respondWith(JSON.stringify({
notes: [],
last_fetched_at: '123456',
}), {
status: 200,
headers: {
'poll-interval': '1000',
},
}));
};
Vue.http.interceptors.push(interceptor);
Vue.http.interceptors.push(headersInterceptor);
store.dispatch('poll')
.then(() => new Promise(resolve => requestAnimationFrame(resolve)))
.then(() => {
expect(Vue.http.get).toHaveBeenCalledWith(jasmine.anything(), {
url: jasmine.anything(),
method: 'get',
headers: {
'X-Last-Fetched-At': undefined,
},
});
expect(store.state.lastFetchedAt).toBe('123456');
jasmine.clock().tick(1500);
})
.then(() => new Promise((resolve) => {
requestAnimationFrame(resolve);
}))
.then(() => {
expect(Vue.http.get.calls.count()).toBe(2);
expect(Vue.http.get.calls.mostRecent().args[1].headers).toEqual({
'X-Last-Fetched-At': '123456',
});
})
.then(() => store.dispatch('stopPolling'))
.then(() => {
Vue.http.interceptors = _.without(Vue.http.interceptors, interceptor);
Vue.http.interceptors = _.without(Vue.http.interceptors, headersInterceptor);
})
.then(done)
.catch(done.fail);
});
});
});

View File

@ -101,10 +101,21 @@ describe('Notes Store mutations', () => {
const state = {
notes: [],
};
const legacyNote = {
id: 2,
individual_note: true,
notes: [{
note: '1',
}, {
note: '2',
}],
};
mutations.SET_INITIAL_NOTES(state, [note]);
mutations.SET_INITIAL_NOTES(state, [note, legacyNote]);
expect(state.notes[0].id).toEqual(note.id);
expect(state.notes.length).toEqual(1);
expect(state.notes[1].notes[0].note).toBe(legacyNote.notes[0].note);
expect(state.notes[2].notes[0].note).toBe(legacyNote.notes[1].note);
expect(state.notes.length).toEqual(3);
});
});

View File

@ -1,5 +1,6 @@
import Vue from 'vue';
import mwpsComponent from '~/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.vue';
import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service';
import eventHub from '~/vue_merge_request_widget/event_hub';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
@ -25,12 +26,7 @@ describe('MRWidgetMergeWhenPipelineSucceeds', () => {
targetBranchPath,
targetBranch,
},
service: {
cancelAutomaticMerge() {},
mergeResource: {
save() {},
},
},
service: new MRWidgetService({}),
});
});
@ -90,18 +86,16 @@ describe('MRWidgetMergeWhenPipelineSucceeds', () => {
describe('removeSourceBranch', () => {
it('should set flag and call service then request main component to update the widget', (done) => {
spyOn(vm.service.mergeResource, 'save').and.returnValue(new Promise((resolve) => {
resolve({
data: {
status: 'merge_when_pipeline_succeeds',
},
});
spyOn(vm.service, 'merge').and.returnValue(Promise.resolve({
data: {
status: 'merge_when_pipeline_succeeds',
},
}));
vm.removeSourceBranch();
setTimeout(() => {
expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested');
expect(vm.service.mergeResource.save).toHaveBeenCalledWith({
expect(vm.service.merge).toHaveBeenCalledWith({
sha,
merge_when_pipeline_succeeds: true,
should_remove_source_branch: true,

View File

@ -1,9 +1,9 @@
import Vue from 'vue';
import nothingToMergeComponent from '~/vue_merge_request_widget/components/states/mr_widget_nothing_to_merge';
import NothingToMerge from '~/vue_merge_request_widget/components/states/nothing_to_merge.vue';
describe('MRWidgetNothingToMerge', () => {
describe('NothingToMerge', () => {
describe('template', () => {
const Component = Vue.extend(nothingToMergeComponent);
const Component = Vue.extend(NothingToMerge);
const newBlobPath = '/foo';
const vm = new Component({
el: document.createElement('div'),

View File

@ -82,6 +82,10 @@ describe('mrWidgetOptions', () => {
});
describe('shouldRenderSourceBranchRemovalStatus', () => {
beforeEach(() => {
vm.mr.state = 'readyToMerge';
});
it('should return true when cannot remove source branch and branch will be removed', () => {
vm.mr.canRemoveSourceBranch = false;
vm.mr.shouldRemoveSourceBranch = true;
@ -102,6 +106,22 @@ describe('mrWidgetOptions', () => {
expect(vm.shouldRenderSourceBranchRemovalStatus).toEqual(false);
});
it('should return false when in merged state', () => {
vm.mr.canRemoveSourceBranch = false;
vm.mr.shouldRemoveSourceBranch = true;
vm.mr.state = 'merged';
expect(vm.shouldRenderSourceBranchRemovalStatus).toEqual(false);
});
it('should return false when in nothing to merge state', () => {
vm.mr.canRemoveSourceBranch = false;
vm.mr.shouldRemoveSourceBranch = true;
vm.mr.state = 'nothingToMerge';
expect(vm.shouldRenderSourceBranchRemovalStatus).toEqual(false);
});
});
describe('shouldRenderDeployments', () => {
@ -407,6 +427,7 @@ describe('mrWidgetOptions', () => {
it('renders when user cannot remove branch and branch should be removed', (done) => {
vm.mr.canRemoveSourceBranch = false;
vm.mr.shouldRemoveSourceBranch = true;
vm.mr.state = 'readyToMerge';
vm.$nextTick(() => {
const tooltip = vm.$el.querySelector('.fa-question-circle');
@ -419,5 +440,18 @@ describe('mrWidgetOptions', () => {
done();
});
});
it('does not render in merged state', (done) => {
vm.mr.canRemoveSourceBranch = false;
vm.mr.shouldRemoveSourceBranch = true;
vm.mr.state = 'merged';
vm.$nextTick(() => {
expect(vm.$el.textContent).toContain('The source branch has been removed');
expect(vm.$el.textContent).not.toContain('Removes source branch');
done();
});
});
});
});

View File

@ -0,0 +1,41 @@
require 'spec_helper'
describe API::Helpers::RelatedResourcesHelpers do
subject(:helpers) do
Class.new.include(described_class).new
end
describe '#expose_url' do
let(:path) { '/api/v4/awesome_endpoint' }
subject(:url) { helpers.expose_url(path) }
def stub_default_url_options(protocol: 'http', host: 'example.com', port: nil)
expect(Gitlab::Application.routes).to receive(:default_url_options)
.and_return(protocol: protocol, host: host, port: port)
end
it 'respects the protocol if it is HTTP' do
stub_default_url_options(protocol: 'http')
is_expected.to start_with('http://')
end
it 'respects the protocol if it is HTTPS' do
stub_default_url_options(protocol: 'https')
is_expected.to start_with('https://')
end
it 'accepts port to be nil' do
stub_default_url_options(port: nil)
is_expected.to start_with('http://example.com/')
end
it 'includes port if provided' do
stub_default_url_options(port: 8080)
is_expected.to start_with('http://example.com:8080/')
end
end
end

View File

@ -94,10 +94,12 @@ describe Gitlab::Profiler do
it 'strips out the private token' do
expect(custom_logger).to receive(:add) do |severity, _progname, message|
next if message.include?('spec/')
expect(severity).to eq(Logger::DEBUG)
expect(message).to include('public').and include(described_class::FILTERED_STRING)
expect(message).not_to include(private_token)
end
end.twice
custom_logger.debug("public #{private_token}")
end

View File

@ -108,14 +108,26 @@ describe Gitlab::ProjectSearchResults do
context 'when the search returns non-ASCII data' do
context 'with UTF-8' do
let(:results) { project.repository.search_files_by_content("файл", 'master') }
let(:results) { project.repository.search_files_by_content('файл', 'master') }
it 'returns results as UTF-8' do
expect(subject.filename).to eq('encoding/russian.rb')
expect(subject.basename).to eq('encoding/russian')
expect(subject.ref).to eq('master')
expect(subject.startline).to eq(1)
expect(subject.data).to eq("Хороший файл")
expect(subject.data).to eq('Хороший файл')
end
end
context 'with UTF-8 in the filename' do
let(:results) { project.repository.search_files_by_content('webhook', 'master') }
it 'returns results as UTF-8' do
expect(subject.filename).to eq('encoding/テスト.txt')
expect(subject.basename).to eq('encoding/テスト')
expect(subject.ref).to eq('master')
expect(subject.startline).to eq(3)
expect(subject.data).to include('WebHookの確認')
end
end

View File

@ -37,33 +37,51 @@ describe Compare do
end
end
describe '#base_commit' do
let(:base_commit) { Commit.new(another_sample_commit, project) }
describe '#base_commit_sha' do
it 'returns @base_sha if it is present' do
expect(project).not_to receive(:merge_base_commit)
it 'returns project merge base commit' do
expect(project).to receive(:merge_base_commit).with(start_commit.id, head_commit.id).and_return(base_commit)
sha = double
service = described_class.new(raw_compare, project, base_sha: sha)
expect(subject.base_commit).to eq(base_commit)
expect(service.base_commit_sha).to eq(sha)
end
it 'fetches merge base SHA from repo when @base_sha is nil' do
expect(project).to receive(:merge_base_commit)
.with(start_commit.id, head_commit.id)
.once
.and_call_original
expect(subject.base_commit_sha)
.to eq(project.repository.merge_base(start_commit.id, head_commit.id))
end
it 'is memoized on first call' do
expect(project).to receive(:merge_base_commit)
.with(start_commit.id, head_commit.id)
.once
.and_call_original
3.times { subject.base_commit_sha }
end
it 'returns nil if there is no start_commit' do
expect(subject).to receive(:start_commit).and_return(nil)
expect(subject.base_commit).to eq(nil)
expect(subject.base_commit_sha).to eq(nil)
end
it 'returns nil if there is no head commit' do
expect(subject).to receive(:head_commit).and_return(nil)
expect(subject.base_commit).to eq(nil)
expect(subject.base_commit_sha).to eq(nil)
end
end
describe '#diff_refs' do
it 'uses base_commit sha as base_sha' do
expect(subject).to receive(:base_commit).at_least(:once).and_call_original
expect(subject.diff_refs.base_sha).to eq(subject.base_commit.id)
it 'uses base_commit_sha sha as base_sha' do
expect(subject.diff_refs.base_sha).to eq(subject.base_commit_sha)
end
it 'uses start_commit sha as start_sha' do

View File

@ -137,7 +137,7 @@ sast:container:
dast:
stage: dast
allow_failure: true
image: owasp/zap2docker-stable
image: registry.gitlab.com/gitlab-org/security-products/zaproxy
variables:
POSTGRES_DB: "false"
script:

View File

@ -54,9 +54,9 @@
lodash "^4.2.0"
to-fast-properties "^2.0.0"
"@gitlab-org/gitlab-svgs@^1.13.0":
version "1.13.0"
resolved "https://registry.yarnpkg.com/@gitlab-org/gitlab-svgs/-/gitlab-svgs-1.13.0.tgz#9e856ef9fa7bbe49b2dce9789187a89e11311215"
"@gitlab-org/gitlab-svgs@^1.14.0":
version "1.14.0"
resolved "https://registry.yarnpkg.com/@gitlab-org/gitlab-svgs/-/gitlab-svgs-1.14.0.tgz#b4a5cca3106f33224c5486cf674ba3b70cee727e"
"@types/jquery@^2.0.40":
version "2.0.48"