Commit 939c8723 authored by Filipa Lacerda's avatar Filipa Lacerda

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
  ...
parents e84c943f 51f91537
...@@ -63,7 +63,7 @@ GEM ...@@ -63,7 +63,7 @@ GEM
fog-core fog-core
mime-types (>= 2.99) mime-types (>= 2.99)
unf unf
ast (2.3.0) ast (2.4.0)
atomic (1.1.99) atomic (1.1.99)
attr_encrypted (3.0.3) attr_encrypted (3.0.3)
encryptor (~> 3.0.0) encryptor (~> 3.0.0)
...@@ -586,8 +586,8 @@ GEM ...@@ -586,8 +586,8 @@ GEM
orm_adapter (0.5.0) orm_adapter (0.5.0)
os (0.9.6) os (0.9.6)
parallel (1.12.1) parallel (1.12.1)
parser (2.4.0.2) parser (2.5.0.3)
ast (~> 2.3) ast (~> 2.4.0)
parslet (1.5.0) parslet (1.5.0)
blankslate (~> 2.0) blankslate (~> 2.0)
path_expander (1.0.2) path_expander (1.0.2)
...@@ -951,13 +951,13 @@ GEM ...@@ -951,13 +951,13 @@ GEM
get_process_mem (~> 0) get_process_mem (~> 0)
unicorn (>= 4, < 6) unicorn (>= 4, < 6)
uniform_notifier (1.10.0) uniform_notifier (1.10.0)
unparser (0.2.6) unparser (0.2.7)
abstract_type (~> 0.0.7) abstract_type (~> 0.0.7)
adamantium (~> 0.2.0) adamantium (~> 0.2.0)
concord (~> 0.1.5) concord (~> 0.1.5)
diff-lcs (~> 1.3) diff-lcs (~> 1.3)
equalizer (~> 0.0.9) equalizer (~> 0.0.9)
parser (>= 2.3.1.2, < 2.5) parser (>= 2.3.1.2, < 2.6)
procto (~> 0.0.2) procto (~> 0.0.2)
url_safe_base64 (0.2.2) url_safe_base64 (0.2.2)
validates_hostname (1.0.6) validates_hostname (1.0.6)
......
...@@ -27,10 +27,11 @@ export default { ...@@ -27,10 +27,11 @@ export default {
return Vue.http[method](endpoint); return Vue.http[method](endpoint);
}, },
poll(data = {}) { poll(data = {}) {
const { endpoint, lastFetchedAt } = data; const endpoint = data.notesData.notesPath;
const lastFetchedAt = data.lastFetchedAt;
const options = { const options = {
headers: { headers: {
'X-Last-Fetched-At': lastFetchedAt, 'X-Last-Fetched-At': lastFetchedAt ? `${lastFetchedAt}` : undefined,
}, },
}; };
......
...@@ -209,18 +209,16 @@ const pollSuccessCallBack = (resp, commit, state, getters) => { ...@@ -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; return resp;
}; };
export const poll = ({ commit, state, getters }) => { export const poll = ({ commit, state, getters }) => {
const requestData = { endpoint: state.notesData.notesPath, lastFetchedAt: state.lastFetchedAt };
eTagPoll = new Poll({ eTagPoll = new Poll({
resource: service, resource: service,
method: 'poll', method: 'poll',
data: requestData, data: state,
successCallback: resp => resp.json() successCallback: resp => resp.json()
.then(data => pollSuccessCallBack(data, commit, state, getters)), .then(data => pollSuccessCallBack(data, commit, state, getters)),
errorCallback: () => Flash('Something went wrong while fetching latest comments.'), errorCallback: () => Flash('Something went wrong while fetching latest comments.'),
...@@ -229,7 +227,7 @@ export const poll = ({ commit, state, getters }) => { ...@@ -229,7 +227,7 @@ export const poll = ({ commit, state, getters }) => {
if (!Visibility.hidden()) { if (!Visibility.hidden()) {
eTagPoll.makeRequest(); eTagPoll.makeRequest();
} else { } else {
service.poll(requestData); service.poll(state);
} }
Visibility.change(() => { Visibility.change(() => {
......
...@@ -90,19 +90,21 @@ export default { ...@@ -90,19 +90,21 @@ export default {
const notes = []; const notes = [];
notesData.forEach((note) => { notesData.forEach((note) => {
const nn = Object.assign({}, note);
// To support legacy notes, should be very rare case. // To support legacy notes, should be very rare case.
if (note.individual_note && note.notes.length > 1) { if (note.individual_note && note.notes.length > 1) {
note.notes.forEach((n) => { note.notes.forEach((n) => {
nn.notes = [n]; // override notes array to only have one item to mimick individual_note notes.push({
notes.push(nn); ...note,
notes: [n], // override notes array to only have one item to mimick individual_note
});
}); });
} else { } else {
const oldNote = utils.findNoteObjectById(state.notes, note.id); 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),
});
} }
}); });
......
...@@ -63,7 +63,7 @@ ...@@ -63,7 +63,7 @@
}; };
this.isRemovingSourceBranch = true; this.isRemovingSourceBranch = true;
this.service.mergeResource.save(options) this.service.merge(options)
.then(res => res.data) .then(res => res.data)
.then((data) => { .then((data) => {
if (data.status === 'merge_when_pipeline_succeeds') { if (data.status === 'merge_when_pipeline_succeeds') {
......
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>
`,
};
<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>
...@@ -24,7 +24,7 @@ export { default as MergingState } from './components/states/mr_widget_merging.v ...@@ -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 WipState } from './components/states/mr_widget_wip';
export { default as ArchivedState } from './components/states/mr_widget_archived.vue'; 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 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 MissingBranchState } from './components/states/mr_widget_missing_branch.vue';
export { default as NotAllowedState } from './components/states/mr_widget_not_allowed.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'; export { default as ReadyToMergeState } from './components/states/mr_widget_ready_to_merge';
......
...@@ -71,7 +71,8 @@ export default { ...@@ -71,7 +71,8 @@ export default {
return this.mr.deployments.length; return this.mr.deployments.length;
}, },
shouldRenderSourceBranchRemovalStatus() { shouldRenderSourceBranchRemovalStatus() {
return !this.mr.canRemoveSourceBranch && this.mr.shouldRemoveSourceBranch; return !this.mr.canRemoveSourceBranch && this.mr.shouldRemoveSourceBranch &&
(!this.mr.isNothingToMergeState && !this.mr.isMergedState);
}, },
}, },
methods: { methods: {
......
...@@ -125,6 +125,10 @@ export default class MergeRequestStore { ...@@ -125,6 +125,10 @@ export default class MergeRequestStore {
return this.state === stateKey.nothingToMerge; return this.state === stateKey.nothingToMerge;
} }
get isMergedState() {
return this.state === stateKey.merged;
}
initRebase(data) { initRebase(data) {
this.canPushToSourceBranch = data.can_push_to_source_branch; this.canPushToSourceBranch = data.can_push_to_source_branch;
this.rebaseInProgress = data.rebase_in_progress; this.rebaseInProgress = data.rebase_in_progress;
......
...@@ -49,6 +49,7 @@ export const stateKey = { ...@@ -49,6 +49,7 @@ export const stateKey = {
notAllowedToMerge: 'notAllowedToMerge', notAllowedToMerge: 'notAllowedToMerge',
readyToMerge: 'readyToMerge', readyToMerge: 'readyToMerge',
rebase: 'rebase', rebase: 'rebase',
merged: 'merged',
}; };
export default { export default {
......
...@@ -137,12 +137,22 @@ ...@@ -137,12 +137,22 @@
z-index: 200; z-index: 200;
overflow: hidden; overflow: hidden;
a:not(.btn-retry), a:not(.btn) {
.btn-link {
color: inherit; color: inherit;
&:hover {
color: $gl-link-hover-color;
.avatar {
border-color: rgba($avatar-border, .2);
}
}
} }
.btn-link { .btn-link {
color: inherit;
outline: none; outline: none;
} }
...@@ -214,7 +224,7 @@ ...@@ -214,7 +224,7 @@
&:hover { &:hover {
text-decoration: underline; text-decoration: underline;
color: $md-link-color; color: $gl-link-hover-color;
} }
} }
} }
...@@ -486,16 +496,6 @@ ...@@ -486,16 +496,6 @@
} }
} }
a:not(.btn-retry) {
&:hover {
color: $md-link-color;
.avatar {
border-color: rgba($avatar-border, .2);
}
}
}
.dropdown-menu-toggle { .dropdown-menu-toggle {
width: 100%; width: 100%;
padding-top: 6px; padding-top: 6px;
...@@ -503,6 +503,20 @@ ...@@ -503,6 +503,20 @@
.dropdown-menu { .dropdown-menu {
width: 100%; 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;
}
}
} }
} }
......
module ImportHelper module ImportHelper
include ::Gitlab::Utils::StrongMemoize
def has_ci_cd_only_params? def has_ci_cd_only_params?
false false
end end
...@@ -75,17 +77,18 @@ module ImportHelper ...@@ -75,17 +77,18 @@ module ImportHelper
private private
def github_project_url(full_path) def github_project_url(full_path)
"#{github_root_url}/#{full_path}" URI.join(github_root_url, full_path).to_s
end end
def github_root_url 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' } provider&.dig('url').presence || 'https://github.com'
@github_url = provider.fetch('url', 'https://github.com') if provider end
end end
def gitea_project_url(full_path) 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
end end
...@@ -169,7 +169,7 @@ module NotesHelper ...@@ -169,7 +169,7 @@ module NotesHelper
reopenPath: reopen_issuable_path(issuable), reopenPath: reopen_issuable_path(issuable),
notesPath: notes_url, notesPath: notes_url,
totalNotes: issuable.discussions.length, totalNotes: issuable.discussions.length,
lastFetchedAt: Time.now lastFetchedAt: Time.now.to_i
}.to_json }.to_json
end end
......
class Compare class Compare
include Gitlab::Utils::StrongMemoize
delegate :same, :head, :base, to: :@compare delegate :same, :head, :base, to: :@compare
attr_reader :project attr_reader :project
...@@ -11,9 +13,10 @@ class Compare ...@@ -11,9 +13,10 @@ class Compare
end end
end end
def initialize(compare, project, straight: false) def initialize(compare, project, base_sha: nil, straight: false)
@compare = compare @compare = compare
@project = project @project = project
@base_sha = base_sha
@straight = straight @straight = straight
end end
...@@ -22,40 +25,36 @@ class Compare ...@@ -22,40 +25,36 @@ class Compare
end end
def start_commit 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 end
def head_commit 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
end
alias_method :commit, :head_commit
def base_commit
return @base_commit if defined?(@base_commit)
@base_commit = if start_commit && head_commit ::Commit.new(commit, project) if commit
project.merge_base_commit(start_commit.id, head_commit.id)
else
nil
end end
end end
alias_method :commit, :head_commit
def start_commit_sha def start_commit_sha
start_commit.try(:sha) start_commit&.sha
end end
def base_commit_sha 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 end
def head_commit_sha def head_commit_sha
commit.try(:sha) commit&.sha
end end
def raw_diffs(*args) def raw_diffs(*args)
......
...@@ -10,9 +10,14 @@ class CompareService ...@@ -10,9 +10,14 @@ class CompareService
@start_ref_name = new_start_ref_name @start_ref_name = new_start_ref_name
end 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) 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
end end
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
":issue-link-base" => "issueLinkBase", ":issue-link-base" => "issueLinkBase",
":root-path" => "rootPath", ":root-path" => "rootPath",
":board-id" => "boardId", ":board-id" => "boardId",
":key" => "_uid" } ":key" => "list.id" }
= render "shared/boards/components/sidebar", group: group = render "shared/boards/components/sidebar", group: group
- if @project - if @project
%board-add-issues-modal{ "new-issue-path" => new_project_issue_path(@project), %board-add-issues-modal{ "new-issue-path" => new_project_issue_path(@project),
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
.checkbox .checkbox
= form.label :allow_maintainer_to_push do = form.label :allow_maintainer_to_push do
= form.check_box :allow_maintainer_to_push, disabled: !issuable.can_allow_maintainer_to_push?(current_user) = 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') = link_to 'About this feature', help_page_path('user/project/merge_requests/maintainer_access')
.help-block .help-block
= allow_maintainer_push_unavailable_reason(issuable) = allow_maintainer_push_unavailable_reason(issuable)
---
title: Fix generated URL when listing repoitories for import
merge_request: 17692
author:
type: fixed
---
title: Fix hover style of dropdown items in the right sidebar
merge_request: 17519
author:
type: fixed
---
title: Fix "Remove source branch" button in Merge request widget during merge when pipeline
succeeds state
merge_request: 17192
author:
type: fixed
---
title: Use object ID to prevent duplicate keys Vue warning on Issue Boards page during
development
merge_request: 17682
author:
type: other
---
title: Add partial indexes on todos to handle users with many todos
merge_request:
author:
type: performance
---
title: Fix code and wiki search results when filename is non-ASCII
merge_request:
author:
type: fixed
---
title: Avoid re-fetching merge-base SHA from Gitaly unnecessarily
merge_request:
author:
type: performance
---
title: Move NothingToMerge vue component
merge_request: 17544
author: George Tsiolis
type: performance
---
title: Ensure the API returns https links when https is configured
merge_request: 17681
author:
type: fixed
# Be sure to restart your server when you modify this file. Rails.backtrace_cleaner.remove_silencers!
Rails.backtrace_cleaner.add_silencer { |line| line !~ Gitlab::APP_DIRS_PATTERN }
# 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!
# 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
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # 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 # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -1778,6 +1778,8 @@ ActiveRecord::Schema.define(version: 20180309121820) do ...@@ -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", ["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", ["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", ["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 add_index "todos", ["user_id"], name: "index_todos_on_user_id", using: :btree
create_table "trending_projects", force: :cascade do |t| create_table "trending_projects", force: :cascade do |t|
......
...@@ -548,6 +548,57 @@ On those a default key should not be provided. ...@@ -548,6 +548,57 @@ On those a default key should not be provided.
1. Properties in a Vue Component: 1. Properties in a Vue Component:
Check [order of properties in components rule][vue-order]. 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 #### Vue and Bootstrap
1. Tooltips: Do not rely on `has-tooltip` class name for Vue components 1. Tooltips: Do not rely on `has-tooltip` class name for Vue components
......
...@@ -53,13 +53,13 @@ you can find a clear separation of concerns: ...@@ -53,13 +53,13 @@ you can find a clear separation of concerns:
``` ```
new_feature new_feature
├── components ├── components
│ └── component.js.es6 │ └── component.vue
│ └── ... │ └── ...
├── store ├── stores
│ └── new_feature_store.js.es6 │ └── new_feature_store.js
├── service ├── services
│ └── new_feature_service.js.es6 │ └── new_feature_service.js
├── new_feature_bundle.js.es6 ├── new_feature_bundle.js
``` ```
_For consistency purposes, we recommend you to follow the same structure._ _For consistency purposes, we recommend you to follow the same structure._
......
...@@ -15,7 +15,7 @@ module API ...@@ -15,7 +15,7 @@ module API
url_options = Gitlab::Application.routes.default_url_options url_options = Gitlab::Application.routes.default_url_options
protocol, host, port = url_options.slice(:protocol, :host, :port).values 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 end
private private
......
...@@ -3,7 +3,7 @@ require 'rails/generators' ...@@ -3,7 +3,7 @@ require 'rails/generators'
module Rails module Rails
class PostDeploymentMigrationGenerator < Rails::Generators::NamedBase class PostDeploymentMigrationGenerator < Rails::Generators::NamedBase
def create_migration_file 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" template "migration.rb", "db/post_migrate/#{timestamp}_#{file_name}.rb"
end end
......
...@@ -2,6 +2,7 @@ require_dependency 'gitlab/git' ...@@ -2,6 +2,7 @@ require_dependency 'gitlab/git'
module Gitlab module Gitlab
COM_URL = 'https://gitlab.com'.freeze COM_URL = 'https://gitlab.com'.freeze
APP_DIRS_PATTERN = %r{^/?(app|config|ee|lib|spec|\(\w*\))}
def self.com? def self.com?
# Check `staging?` as well to keep parity with gitlab.com # Check `staging?` as well to keep parity with gitlab.com
......
...@@ -4,7 +4,7 @@ module Gitlab ...@@ -4,7 +4,7 @@ module Gitlab
class Config class Config
class << self class << self
def options def options
Gitlab.config.omniauth.providers.find { |provider| provider.name == 'saml' } Gitlab::Auth::OAuth::Provider.config_for('saml')
end end
def groups def groups
......
...@@ -44,7 +44,11 @@ module Gitlab ...@@ -44,7 +44,11 @@ module Gitlab
project.commit(head_sha) project.commit(head_sha)
else else
straight = start_sha == base_sha 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 end
end end
......
...@@ -197,10 +197,7 @@ module Gitlab ...@@ -197,10 +197,7 @@ module Gitlab
end end
def github_omniauth_provider def github_omniauth_provider
@github_omniauth_provider ||= @github_omniauth_provider ||= Gitlab::Auth::OAuth::Provider.config_for('github').to_h
Gitlab.config.omniauth.providers
.find { |provider| provider.name == 'github' }
.to_h
end end
def rate_limit_counter def rate_limit_counter
......
...@@ -72,7 +72,7 @@ module Gitlab ...@@ -72,7 +72,7 @@ module Gitlab
end end
def config def config
Gitlab.config.omniauth.providers.find {|provider| provider.name == "gitlab"} Gitlab::Auth::OAuth::Provider.config_for('gitlab')
end end
def gitlab_options def gitlab_options
......
...@@ -83,7 +83,7 @@ module Gitlab ...@@ -83,7 +83,7 @@ module Gitlab
end end
def config def config
Gitlab.config.omniauth.providers.find { |provider| provider.name == "github" } Gitlab::Auth::OAuth::Provider.config_for('github')
end end
def github_options def github_options
......
...@@ -7,8 +7,8 @@ module Gitlab ...@@ -7,8 +7,8 @@ module Gitlab
def initialize(opts = {}) def initialize(opts = {})
@id = opts.fetch(:id, nil) @id = opts.fetch(:id, nil)
@filename = opts.fetch(:filename, nil) @filename = encode_utf8(opts.fetch(:filename, nil))
@basename = opts.fetch(:basename, nil) @basename = encode_utf8(opts.fetch(:basename, nil))
@ref = opts.fetch(:ref, nil) @ref = opts.fetch(:ref, nil)
@startline = opts.fetch(:startline, nil) @startline = opts.fetch(:startline, nil)
@data = encode_utf8(opts.fetch(:data, nil)) @data = encode_utf8(opts.fetch(:data, nil))
......
...@@ -32,7 +32,7 @@ module GoogleApi ...@@ -32,7 +32,7 @@ module GoogleApi
private private
def config def config
Gitlab.config.omniauth.providers.find { |provider| provider.name == "google_oauth2" } Gitlab::Auth::OAuth::Provider.config_for('google_oauth2')
end end
def client def client
......
...@@ -8,8 +8,8 @@ msgid "" ...@@ -8,8 +8,8 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: gitlab 1.0.0\n" "Project-Id-Version: gitlab 1.0.0\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-03-06 17:36+0100\n" "POT-Creation-Date: 2018-03-12 19:50+0100\n"
"PO-Revision-Date: 2018-03-06 17:36+0100\n" "PO-Revision-Date: 2018-03-12 19:50+0100\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n" "Language: \n"
...@@ -28,6 +28,11 @@ msgid_plural "%d commits behind" ...@@ -28,6 +28,11 @@ msgid_plural "%d commits behind"
msgstr[0] "" msgstr[0] ""
msgstr[1] "" msgstr[1] ""
msgid "%d exporter"
msgid_plural "%d exporters"
msgstr[0] ""
msgstr[1] ""
msgid "%d issue" msgid "%d issue"
msgid_plural "%d issues" msgid_plural "%d issues"
msgstr[0] "" msgstr[0] ""
...@@ -43,6 +48,11 @@ msgid_plural "%d merge requests" ...@@ -43,6 +48,11 @@ msgid_plural "%d merge requests"
msgstr[0] "" msgstr[0] ""
msgstr[1] "" 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 "%s additional commit has been omitted to prevent performance issues."
msgid_plural "%s additional commits have been omitted to prevent performance issues." msgid_plural "%s additional commits have been omitted to prevent performance issues."
msgstr[0] "" msgstr[0] ""
...@@ -102,6 +112,9 @@ msgstr "" ...@@ -102,6 +112,9 @@ msgstr ""
msgid "2FA enabled" msgid "2FA enabled"
msgstr "" msgstr ""
msgid "<strong>Removes</strong> source branch"
msgstr ""
msgid "A collection of graphs regarding Continuous Integration" msgid "A collection of graphs regarding Continuous Integration"
msgstr "" msgstr ""
...@@ -111,6 +124,9 @@ 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}." msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
msgstr "" msgstr ""
msgid "A user with write access to the source branch selected this option"
msgstr ""
msgid "About auto deploy" msgid "About auto deploy"
msgstr "" msgstr ""
...@@ -213,7 +229,7 @@ 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." msgid "All features are enabled for blank projects, from templates, or when importing, but you can disable them afterward in the project settings."
msgstr "" msgstr ""
msgid "Allow edits from maintainers" msgid "Allow edits from maintainers."
msgstr "" msgstr ""
msgid "Allows you to add and manage Kubernetes clusters." msgid "Allows you to add and manage Kubernetes clusters."
...@@ -857,6 +873,9 @@ msgstr "" ...@@ -857,6 +873,9 @@ msgstr ""
msgid "ClusterIntegration|Learn more about environments" msgid "ClusterIntegration|Learn more about environments"
msgstr "" msgstr ""
msgid "ClusterIntegration|Learn more about security configuration"
msgstr ""
msgid "ClusterIntegration|Machine type" msgid "ClusterIntegration|Machine type"
msgstr "" msgstr ""
...@@ -914,6 +933,9 @@ msgstr "" ...@@ -914,6 +933,9 @@ msgstr ""
msgid "ClusterIntegration|Save changes" msgid "ClusterIntegration|Save changes"
msgstr "" msgstr ""
msgid "ClusterIntegration|Security"
msgstr ""
msgid "ClusterIntegration|See and edit the details for your Kubernetes cluster" msgid "ClusterIntegration|See and edit the details for your Kubernetes cluster"
msgstr "" msgstr ""
...@@ -941,6 +963,9 @@ msgstr "" ...@@ -941,6 +963,9 @@ msgstr ""
msgid "ClusterIntegration|Something went wrong while installing %{title}" msgid "ClusterIntegration|Something went wrong while installing %{title}"
msgstr "" 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" msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
msgstr "" msgstr ""
...@@ -1182,6 +1207,9 @@ msgstr "" ...@@ -1182,6 +1207,9 @@ msgstr ""
msgid "Create empty bare repository" msgid "Create empty bare repository"
msgstr "" msgstr ""
msgid "Create group label"
msgstr ""
msgid "Create lists from labels. Issues with that label appear in that list." msgid "Create lists from labels. Issues with that label appear in that list."
msgstr "" msgstr ""
...@@ -1197,6 +1225,9 @@ msgstr "" ...@@ -1197,6 +1225,9 @@ msgstr ""
msgid "Create new..." msgid "Create new..."
msgstr "" msgstr ""
msgid "Create project label"
msgstr ""
msgid "CreateNewFork|Fork" msgid "CreateNewFork|Fork"
msgstr "" msgstr ""
...@@ -1776,9 +1807,18 @@ msgstr "" ...@@ -1776,9 +1807,18 @@ msgstr ""
msgid "Labels" msgid "Labels"
msgstr "" 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." msgid "Labels can be applied to issues and merge requests to categorize them."
msgstr "" msgstr ""
msgid "Labels|Promote Label"
msgstr ""
msgid "Labels|Promote label %{labelTitle} to Group Label?"
msgstr ""
msgid "Last %d day" msgid "Last %d day"
msgid_plural "Last %d days" msgid_plural "Last %d days"
msgstr[0] "" msgstr[0] ""
...@@ -1850,9 +1890,15 @@ msgstr "" ...@@ -1850,9 +1890,15 @@ msgstr ""
msgid "Login" msgid "Login"
msgstr "" msgstr ""
msgid "Manage group labels"
msgstr ""
msgid "Manage labels" msgid "Manage labels"
msgstr "" msgstr ""
msgid "Manage project labels"
msgstr ""
msgid "Mar" msgid "Mar"
msgstr "" msgstr ""
...@@ -1907,6 +1953,12 @@ msgstr "" ...@@ -1907,6 +1953,12 @@ msgstr ""
msgid "Milestones|Milestone %{milestoneTitle} was not found" msgid "Milestones|Milestone %{milestoneTitle} was not found"
msgstr "" msgstr ""
msgid "Milestones|Promote %{milestoneTitle} to group milestone?"
msgstr ""
msgid "Milestones|Promote Milestone"
msgstr ""
msgid "MissingSSHKeyWarningLink|add an SSH key" msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr "" msgstr ""
...@@ -2002,6 +2054,9 @@ msgstr "" ...@@ -2002,6 +2054,9 @@ msgstr ""
msgid "No file chosen" msgid "No file chosen"
msgstr "" msgstr ""
msgid "No labels created yet."
msgstr ""
msgid "No repository" msgid "No repository"
msgstr "" msgstr ""
...@@ -2251,9 +2306,15 @@ msgstr "" ...@@ -2251,9 +2306,15 @@ msgstr ""
msgid "Pipelines|Loading Pipelines" msgid "Pipelines|Loading Pipelines"
msgstr "" msgstr ""
msgid "Pipelines|Project cache successfully reset."
msgstr ""
msgid "Pipelines|Run Pipeline" msgid "Pipelines|Run Pipeline"
msgstr "" msgstr ""
msgid "Pipelines|Something went wrong while cleaning runners cache."
msgstr ""
msgid "Pipelines|There are currently no %{scope} pipelines." msgid "Pipelines|There are currently no %{scope} pipelines."
msgstr "" msgstr ""
...@@ -2380,9 +2441,6 @@ msgstr "" ...@@ -2380,9 +2441,6 @@ msgstr ""
msgid "Project avatar in repository: %{link}" msgid "Project avatar in repository: %{link}"
msgstr "" msgstr ""
msgid "Project cache successfully reset."
msgstr ""
msgid "Project details" msgid "Project details"
msgstr "" msgstr ""
...@@ -2446,6 +2504,12 @@ msgstr "" ...@@ -2446,6 +2504,12 @@ msgstr ""
msgid "ProjectsDropdown|This feature requires browser localStorage support" msgid "ProjectsDropdown|This feature requires browser localStorage support"
msgstr "" 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" msgid "PrometheusService|Active"
msgstr "" msgstr ""
...@@ -2458,6 +2522,9 @@ msgstr "" ...@@ -2458,6 +2522,9 @@ msgstr ""
msgid "PrometheusService|By default, Prometheus listens on ‘http://localhost:9090’. It’s not recommended to change the default address and port as this might affect or conflict with other services running on the GitLab server." msgid "PrometheusService|By default, Prometheus listens on ‘http://localhost:9090’. It’s not recommended to change the default address and port as this might affect or conflict with other services running on the GitLab server."
msgstr "" msgstr ""
msgid "PrometheusService|Common metrics"
msgstr ""
msgid "PrometheusService|Finding and configuring metrics..." msgid "PrometheusService|Finding and configuring metrics..."
msgstr "" msgstr ""
...@@ -2479,15 +2546,9 @@ msgstr "" ...@@ -2479,15 +2546,9 @@ msgstr ""
msgid "PrometheusService|Missing environment variable" msgid "PrometheusService|Missing environment variable"
msgstr "" msgstr ""
msgid "PrometheusService|Monitored"
msgstr ""
msgid "PrometheusService|More information" msgid "PrometheusService|More information"
msgstr "" 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/" msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/"
msgstr "" msgstr ""
...@@ -2503,7 +2564,16 @@ msgstr "" ...@@ -2503,7 +2564,16 @@ msgstr ""
msgid "PrometheusService|To enable the installation of Prometheus on your clusters, deactivate the manual configuration below" msgid "PrometheusService|To enable the installation of Prometheus on your clusters, deactivate the manual configuration below"
msgstr "" 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 "" msgstr ""
msgid "Protip:" msgid "Protip:"
...@@ -2515,9 +2585,6 @@ msgstr "" ...@@ -2515,9 +2585,6 @@ msgstr ""
msgid "Public - The project can be accessed without any authentication." msgid "Public - The project can be accessed without any authentication."
msgstr "" msgstr ""
msgid "Push access to this project is necessary in order to enable this option"
msgstr ""
msgid "Push events" msgid "Push events"
msgstr "" msgstr ""
...@@ -3338,9 +3405,6 @@ msgstr "" ...@@ -3338,9 +3405,6 @@ msgstr ""
msgid "Trigger this manual action" msgid "Trigger this manual action"
msgstr "" msgstr ""
msgid "Unable to reset project cache."
msgstr ""
msgid "Unlock" msgid "Unlock"
msgstr "" msgstr ""
...@@ -3383,12 +3447,18 @@ msgstr "" ...@@ -3383,12 +3447,18 @@ msgstr ""
msgid "View file @ " msgid "View file @ "
msgstr "" msgstr ""
msgid "View group labels"
msgstr ""
msgid "View labels" msgid "View labels"
msgstr "" msgstr ""
msgid "View open merge request" msgid "View open merge request"
msgstr "" msgstr ""
msgid "View project labels"
msgstr ""
msgid "View replaced file @ " msgid "View replaced file @ "
msgstr "" msgstr ""
......
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
"webpack-prod": "NODE_ENV=production webpack --config config/webpack.config.js" "webpack-prod": "NODE_ENV=production webpack --config config/webpack.config.js"
}, },
"dependencies": { "dependencies": {
"@gitlab-org/gitlab-svgs": "^1.13.0", "@gitlab-org/gitlab-svgs": "^1.14.0",
"autosize": "^4.0.0", "autosize": "^4.0.0",
"axios": "^0.17.1", "axios": "^0.17.1",
"babel-core": "^6.26.0", "babel-core": "^6.26.0",
......
...@@ -125,6 +125,12 @@ describe 'Merge request > User merges when pipeline succeeds', :js do ...@@ -125,6 +125,12 @@ describe 'Merge request > User merges when pipeline succeeds', :js do
expect(page).to have_content "canceled the automatic merge" expect(page).to have_content "canceled the automatic merge"
end 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 context 'when pipeline succeeds' do
before do before do
build.success build.success
......
...@@ -27,25 +27,48 @@ describe ImportHelper do ...@@ -27,25 +27,48 @@ describe ImportHelper do
describe '#provider_project_link' do describe '#provider_project_link' do
context 'when provider is "github"' 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 context 'when provider does not specify a custom URL' do
it 'uses default GitHub 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')) expect(helper.provider_project_link('github', 'octocat/Hello-World'))
.to include('href="https://github.com/octocat/Hello-World"') .to include('href="https://github.com/octocat/Hello-World"')
end end
end end
context 'when provider specify a custom URL' do context 'when provider specify a custom URL' do
let(:github_server_url) { 'https://github.company.com' }
it 'uses custom URL' do it 'uses custom URL' do
allow(Gitlab.config.omniauth).to receive(:providers) expect(helper.provider_project_link('github', 'octocat/Hello-World'))
.and_return([Settingslogic.new('name' => 'github', 'url' => 'https://github.company.com')]) .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')) expect(helper.provider_project_link('github', 'octocat/Hello-World'))
.to include('href="https://github.company.com/octocat/Hello-World"') .to include('href="https://github.company.com/octocat/Hello-World"')
end end
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 end
context 'when provider is "gitea"' do context 'when provider is "gitea"' do
......
/* eslint-disable */ /* eslint-disable */
export const notesDataMock = { export const notesDataMock = {
discussionsPath: '/gitlab-org/gitlab-ce/issues/26/discussions.json', discussionsPath: '/gitlab-org/gitlab-ce/issues/26/discussions.json',
lastFetchedAt: '1501862675', lastFetchedAt: 1501862675,
markdownDocsPath: '/help/user/markdown', markdownDocsPath: '/help/user/markdown',
newSessionPath: '/users/sign_in?redirect_to_referer=yes', newSessionPath: '/users/sign_in?redirect_to_referer=yes',
notesPath: '/gitlab-org/gitlab-ce/noteable/issue/98/notes', notesPath: '/gitlab-org/gitlab-ce/noteable/issue/98/notes',
......
import Vue from 'vue'; import Vue from 'vue';
import _ from 'underscore'; import _ from 'underscore';
import { headersInterceptor } from 'spec/helpers/vue_resource_helper';
import * as actions from '~/notes/stores/actions'; import * as actions from '~/notes/stores/actions';
import store from '~/notes/stores'; import store from '~/notes/stores';
import testAction from '../../helpers/vuex_action_helper'; import testAction from '../../helpers/vuex_action_helper';
...@@ -145,4 +146,68 @@ describe('Actions Notes Store', () => { ...@@ -145,4 +146,68 @@ describe('Actions Notes Store', () => {
], done); ], 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);
});
});
}); });
...@@ -101,10 +101,21 @@ describe('Notes Store mutations', () => { ...@@ -101,10 +101,21 @@ describe('Notes Store mutations', () => {
const state = { const state = {
notes: [], 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[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);
}); });
}); });
......
import Vue from 'vue'; import Vue from 'vue';
import mwpsComponent from '~/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.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 eventHub from '~/vue_merge_request_widget/event_hub';
import mountComponent from 'spec/helpers/vue_mount_component_helper'; import mountComponent from 'spec/helpers/vue_mount_component_helper';
...@@ -25,12 +26,7 @@ describe('MRWidgetMergeWhenPipelineSucceeds', () => { ...@@ -25,12 +26,7 @@ describe('MRWidgetMergeWhenPipelineSucceeds', () => {
targetBranchPath, targetBranchPath,
targetBranch, targetBranch,
}, },
service: { service: new MRWidgetService({}),
cancelAutomaticMerge() {},
mergeResource: {
save() {},
},
},
}); });
}); });
...@@ -90,18 +86,16 @@ describe('MRWidgetMergeWhenPipelineSucceeds', () => { ...@@ -90,18 +86,16 @@ describe('MRWidgetMergeWhenPipelineSucceeds', () => {
describe('removeSourceBranch', () => { describe('removeSourceBranch', () => {
it('should set flag and call service then request main component to update the widget', (done) => { 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) => { spyOn(vm.service, 'merge').and.returnValue(Promise.resolve({
resolve({
data: { data: {
status: 'merge_when_pipeline_succeeds', status: 'merge_when_pipeline_succeeds',
}, },
});
})); }));
vm.removeSourceBranch(); vm.removeSourceBranch();
setTimeout(() => { setTimeout(() => {
expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested'); expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested');
expect(vm.service.mergeResource.save).toHaveBeenCalledWith({ expect(vm.service.merge).toHaveBeenCalledWith({
sha, sha,
merge_when_pipeline_succeeds: true, merge_when_pipeline_succeeds: true,
should_remove_source_branch: true, should_remove_source_branch: true,
......
import Vue from 'vue'; 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', () => { describe('template', () => {
const Component = Vue.extend(nothingToMergeComponent); const Component = Vue.extend(NothingToMerge);
const newBlobPath = '/foo'; const newBlobPath = '/foo';
const vm = new Component({ const vm = new Component({
el: document.createElement('div'), el: document.createElement('div'),
......
...@@ -82,6 +82,10 @@ describe('mrWidgetOptions', () => { ...@@ -82,6 +82,10 @@ describe('mrWidgetOptions', () => {
}); });
describe('shouldRenderSourceBranchRemovalStatus', () => { describe('shouldRenderSourceBranchRemovalStatus', () => {
beforeEach(() => {
vm.mr.state = 'readyToMerge';
});
it('should return true when cannot remove source branch and branch will be removed', () => { it('should return true when cannot remove source branch and branch will be removed', () => {
vm.mr.canRemoveSourceBranch = false; vm.mr.canRemoveSourceBranch = false;
vm.mr.shouldRemoveSourceBranch = true; vm.mr.shouldRemoveSourceBranch = true;
...@@ -102,6 +106,22 @@ describe('mrWidgetOptions', () => { ...@@ -102,6 +106,22 @@ describe('mrWidgetOptions', () => {
expect(vm.shouldRenderSourceBranchRemovalStatus).toEqual(false); 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', () => { describe('shouldRenderDeployments', () => {
...@@ -407,6 +427,7 @@ describe('mrWidgetOptions', () => { ...@@ -407,6 +427,7 @@ describe('mrWidgetOptions', () => {
it('renders when user cannot remove branch and branch should be removed', (done) => { it('renders when user cannot remove branch and branch should be removed', (done) => {
vm.mr.canRemoveSourceBranch = false; vm.mr.canRemoveSourceBranch = false;
vm.mr.shouldRemoveSourceBranch = true; vm.mr.shouldRemoveSourceBranch = true;
vm.mr.state = 'readyToMerge';
vm.$nextTick(() => { vm.$nextTick(() => {
const tooltip = vm.$el.querySelector('.fa-question-circle'); const tooltip = vm.$el.querySelector('.fa-question-circle');
...@@ -419,5 +440,18 @@ describe('mrWidgetOptions', () => { ...@@ -419,5 +440,18 @@ describe('mrWidgetOptions', () => {
done(); 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();
});
});
}); });
}); });
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
...@@ -94,10 +94,12 @@ describe Gitlab::Profiler do ...@@ -94,10 +94,12 @@ describe Gitlab::Profiler do
it 'strips out the private token' do it 'strips out the private token' do
expect(custom_logger).to receive(:add) do |severity, _progname, message| expect(custom_logger).to receive(:add) do |severity, _progname, message|
next if message.include?('spec/')
expect(severity).to eq(Logger::DEBUG) expect(severity).to eq(Logger::DEBUG)
expect(message).to include('public').and include(described_class::FILTERED_STRING) expect(message).to include('public').and include(described_class::FILTERED_STRING)
expect(message).not_to include(private_token) expect(message).not_to include(private_token)
end end.twice
custom_logger.debug("public #{private_token}") custom_logger.debug("public #{private_token}")
end end
......
...@@ -108,14 +108,26 @@ describe Gitlab::ProjectSearchResults do ...@@ -108,14 +108,26 @@ describe Gitlab::ProjectSearchResults do
context 'when the search returns non-ASCII data' do context 'when the search returns non-ASCII data' do
context 'with UTF-8' 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 it 'returns results as UTF-8' do
expect(subject.filename).to eq('encoding/russian.rb') expect(subject.filename).to eq('encoding/russian.rb')
expect(subject.basename).to eq('encoding/russian') expect(subject.basename).to eq('encoding/russian')
expect(subject.ref).to eq('master') expect(subject.ref).to eq('master')
expect(subject.startline).to eq(1) 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
end end
......
...@@ -37,33 +37,51 @@ describe Compare do ...@@ -37,33 +37,51 @@ describe Compare do
end end
end end
describe '#base_commit' do describe '#base_commit_sha' do
let(:base_commit) { Commit.new(another_sample_commit, project) } it 'returns @base_sha if it is present' do
expect(project).not_to receive(:merge_base_commit)
it 'returns project merge base commit' do sha = double
expect(project).to receive(:merge_base_commit).with(start_commit.id, head_commit.id).and_return(base_commit) 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 end
it 'returns nil if there is no start_commit' do it 'returns nil if there is no start_commit' do
expect(subject).to receive(:start_commit).and_return(nil) 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 end
it 'returns nil if there is no head commit' do it 'returns nil if there is no head commit' do
expect(subject).to receive(:head_commit).and_return(nil) 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
end end
describe '#diff_refs' do describe '#diff_refs' do
it 'uses base_commit sha as base_sha' do it 'uses base_commit_sha 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_sha)
expect(subject.diff_refs.base_sha).to eq(subject.base_commit.id)
end end
it 'uses start_commit sha as start_sha' do it 'uses start_commit sha as start_sha' do
......
...@@ -137,7 +137,7 @@ sast:container: ...@@ -137,7 +137,7 @@ sast:container:
dast: dast:
stage: dast stage: dast
allow_failure: true allow_failure: true
image: owasp/zap2docker-stable image: registry.gitlab.com/gitlab-org/security-products/zaproxy
variables: variables:
POSTGRES_DB: "false" POSTGRES_DB: "false"
script: script:
......
...@@ -54,9 +54,9 @@ ...@@ -54,9 +54,9 @@
lodash "^4.2.0" lodash "^4.2.0"
to-fast-properties "^2.0.0" to-fast-properties "^2.0.0"
"@gitlab-org/gitlab-svgs@^1.13.0": "@gitlab-org/gitlab-svgs@^1.14.0":
version "1.13.0" version "1.14.0"
resolved "https://registry.yarnpkg.com/@gitlab-org/gitlab-svgs/-/gitlab-svgs-1.13.0.tgz#9e856ef9fa7bbe49b2dce9789187a89e11311215" resolved "https://registry.yarnpkg.com/@gitlab-org/gitlab-svgs/-/gitlab-svgs-1.14.0.tgz#b4a5cca3106f33224c5486cf674ba3b70cee727e"
"@types/jquery@^2.0.40": "@types/jquery@^2.0.40":
version "2.0.48" version "2.0.48"
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment